@harperfast/harper 5.2.10 → 5.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (554) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +46 -9
  4. package/bin/copyDb.ts +282 -59
  5. package/bin/deploySetup.ts +16 -5
  6. package/bin/harper.ts +1 -1
  7. package/bin/help.ts +4 -1
  8. package/bin/lite.ts +4 -1
  9. package/bin/restart.ts +59 -3
  10. package/bin/run.ts +6 -11
  11. package/bin/upgrade.js +7 -3
  12. package/bin/workloadIdentity.ts +119 -0
  13. package/components/Application.ts +2414 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +150 -74
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +37 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +340 -30
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/mcp/adapters/harperHttp.ts +4 -0
  23. package/components/mcp/listChanged.ts +4 -0
  24. package/components/mcp/toolRegistry.ts +2 -0
  25. package/components/mcp/tools/operations.ts +9 -0
  26. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  27. package/components/operations.js +270 -78
  28. package/components/operationsValidation.js +49 -1
  29. package/components/status/ComponentStatusRegistry.ts +59 -0
  30. package/config/RootConfigWatcher.ts +80 -34
  31. package/config/configUtils.ts +291 -34
  32. package/config/harperConfigEnvVars.ts +170 -27
  33. package/config-root.schema.json +29 -0
  34. package/dataLayer/blobBackup.ts +160 -50
  35. package/dataLayer/delete.ts +6 -1
  36. package/dataLayer/harperBridge/ResourceBridge.ts +52 -8
  37. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  38. package/dataLayer/hdbInfoController.ts +34 -1
  39. package/dataLayer/insert.ts +44 -1
  40. package/dataLayer/rocksdbBackup.ts +53 -10
  41. package/dataLayer/schema.ts +11 -1
  42. package/dataLayer/schemaDescribe.ts +8 -1
  43. package/dist/agent/mcpTools.js +1 -1
  44. package/dist/agent/mcpTools.js.map +1 -1
  45. package/dist/agent/session.d.ts +22 -0
  46. package/dist/agent/session.js +26 -15
  47. package/dist/agent/session.js.map +1 -1
  48. package/dist/bin/cliOperations.js +48 -9
  49. package/dist/bin/cliOperations.js.map +1 -1
  50. package/dist/bin/copyDb.d.ts +12 -1
  51. package/dist/bin/copyDb.js +248 -60
  52. package/dist/bin/copyDb.js.map +1 -1
  53. package/dist/bin/deploySetup.d.ts +2 -0
  54. package/dist/bin/deploySetup.js +11 -3
  55. package/dist/bin/deploySetup.js.map +1 -1
  56. package/dist/bin/harper.js +1 -1
  57. package/dist/bin/harper.js.map +1 -1
  58. package/dist/bin/help.js +4 -1
  59. package/dist/bin/help.js.map +1 -1
  60. package/dist/bin/lite.js +4 -1
  61. package/dist/bin/lite.js.map +1 -1
  62. package/dist/bin/restart.js +54 -5
  63. package/dist/bin/restart.js.map +1 -1
  64. package/dist/bin/run.js +4 -10
  65. package/dist/bin/run.js.map +1 -1
  66. package/dist/bin/upgrade.js +4 -3
  67. package/dist/bin/upgrade.js.map +1 -1
  68. package/dist/bin/workloadIdentity.d.ts +18 -0
  69. package/dist/bin/workloadIdentity.js +100 -0
  70. package/dist/bin/workloadIdentity.js.map +1 -0
  71. package/dist/components/Application.d.ts +139 -16
  72. package/dist/components/Application.js +2215 -267
  73. package/dist/components/Application.js.map +1 -1
  74. package/dist/components/ApplicationScope.d.ts +8 -0
  75. package/dist/components/ApplicationScope.js +7 -0
  76. package/dist/components/ApplicationScope.js.map +1 -1
  77. package/dist/components/EntryHandler.js +26 -10
  78. package/dist/components/EntryHandler.js.map +1 -1
  79. package/dist/components/OptionsWatcher.d.ts +1 -0
  80. package/dist/components/OptionsWatcher.js +141 -74
  81. package/dist/components/OptionsWatcher.js.map +1 -1
  82. package/dist/components/RuntimeModuleTracker.js +40 -6
  83. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  84. package/dist/components/Scope.js +38 -13
  85. package/dist/components/Scope.js.map +1 -1
  86. package/dist/components/awaitRestart.d.ts +33 -0
  87. package/dist/components/awaitRestart.js +61 -0
  88. package/dist/components/awaitRestart.js.map +1 -0
  89. package/dist/components/componentLoader.d.ts +38 -1
  90. package/dist/components/componentLoader.js +279 -22
  91. package/dist/components/componentLoader.js.map +1 -1
  92. package/dist/components/componentPreparationLock.d.ts +5 -0
  93. package/dist/components/componentPreparationLock.js +14 -6
  94. package/dist/components/componentPreparationLock.js.map +1 -1
  95. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  96. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  97. package/dist/components/mcp/listChanged.js +5 -0
  98. package/dist/components/mcp/listChanged.js.map +1 -1
  99. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  100. package/dist/components/mcp/toolRegistry.js.map +1 -1
  101. package/dist/components/mcp/tools/operations.d.ts +5 -0
  102. package/dist/components/mcp/tools/operations.js +9 -0
  103. package/dist/components/mcp/tools/operations.js.map +1 -1
  104. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  105. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  106. package/dist/components/operations.js +231 -77
  107. package/dist/components/operations.js.map +1 -1
  108. package/dist/components/operationsValidation.js +49 -1
  109. package/dist/components/operationsValidation.js.map +1 -1
  110. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  111. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  112. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  113. package/dist/config/RootConfigWatcher.d.ts +7 -1
  114. package/dist/config/RootConfigWatcher.js +64 -27
  115. package/dist/config/RootConfigWatcher.js.map +1 -1
  116. package/dist/config/configUtils.d.ts +9 -1
  117. package/dist/config/configUtils.js +254 -33
  118. package/dist/config/configUtils.js.map +1 -1
  119. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  120. package/dist/config/harperConfigEnvVars.js +162 -25
  121. package/dist/config/harperConfigEnvVars.js.map +1 -1
  122. package/dist/dataLayer/blobBackup.d.ts +49 -20
  123. package/dist/dataLayer/blobBackup.js +139 -50
  124. package/dist/dataLayer/blobBackup.js.map +1 -1
  125. package/dist/dataLayer/delete.js +1 -1
  126. package/dist/dataLayer/delete.js.map +1 -1
  127. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  128. package/dist/dataLayer/harperBridge/ResourceBridge.js +51 -10
  129. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  130. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  131. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  132. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  133. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  134. package/dist/dataLayer/hdbInfoController.js +30 -1
  135. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  136. package/dist/dataLayer/insert.d.ts +9 -1
  137. package/dist/dataLayer/insert.js +30 -0
  138. package/dist/dataLayer/insert.js.map +1 -1
  139. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  140. package/dist/dataLayer/rocksdbBackup.js +45 -8
  141. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  142. package/dist/dataLayer/schema.js +8 -0
  143. package/dist/dataLayer/schema.js.map +1 -1
  144. package/dist/dataLayer/schemaDescribe.js +8 -1
  145. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  146. package/dist/index.d.ts +1 -1
  147. package/dist/index.js.map +1 -1
  148. package/dist/json/systemSchema.json +52 -0
  149. package/dist/resources/DatabaseTransaction.d.ts +55 -3
  150. package/dist/resources/DatabaseTransaction.js +460 -134
  151. package/dist/resources/DatabaseTransaction.js.map +1 -1
  152. package/dist/resources/LMDBTransaction.js +22 -4
  153. package/dist/resources/LMDBTransaction.js.map +1 -1
  154. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  155. package/dist/resources/PrimaryRocksDatabase.js +30 -2
  156. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  157. package/dist/resources/RecordEncoder.d.ts +20 -0
  158. package/dist/resources/RecordEncoder.js +94 -9
  159. package/dist/resources/RecordEncoder.js.map +1 -1
  160. package/dist/resources/RequestTarget.d.ts +2 -0
  161. package/dist/resources/RequestTarget.js.map +1 -1
  162. package/dist/resources/Resource.js +20 -11
  163. package/dist/resources/Resource.js.map +1 -1
  164. package/dist/resources/ResourceInterface.d.ts +20 -1
  165. package/dist/resources/ResourceInterface.js.map +1 -1
  166. package/dist/resources/RocksIndexStore.d.ts +6 -1
  167. package/dist/resources/RocksIndexStore.js +24 -10
  168. package/dist/resources/RocksIndexStore.js.map +1 -1
  169. package/dist/resources/RocksTransactionLogStore.d.ts +14 -1
  170. package/dist/resources/RocksTransactionLogStore.js +57 -17
  171. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  172. package/dist/resources/Table.d.ts +112 -8
  173. package/dist/resources/Table.js +1071 -197
  174. package/dist/resources/Table.js.map +1 -1
  175. package/dist/resources/auditStore.d.ts +11 -2
  176. package/dist/resources/auditStore.js +183 -18
  177. package/dist/resources/auditStore.js.map +1 -1
  178. package/dist/resources/blob.d.ts +129 -9
  179. package/dist/resources/blob.js +992 -114
  180. package/dist/resources/blob.js.map +1 -1
  181. package/dist/resources/branchDatabase.d.ts +48 -0
  182. package/dist/resources/branchDatabase.js +892 -0
  183. package/dist/resources/branchDatabase.js.map +1 -0
  184. package/dist/resources/crdt.js +50 -12
  185. package/dist/resources/crdt.js.map +1 -1
  186. package/dist/resources/databases.d.ts +150 -9
  187. package/dist/resources/databases.js +1247 -525
  188. package/dist/resources/databases.js.map +1 -1
  189. package/dist/resources/defineTable.d.ts +10 -2
  190. package/dist/resources/defineTable.js +9 -1
  191. package/dist/resources/defineTable.js.map +1 -1
  192. package/dist/resources/graphql.d.ts +1 -1
  193. package/dist/resources/graphql.js +50 -15
  194. package/dist/resources/graphql.js.map +1 -1
  195. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +1 -0
  196. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +6 -2
  197. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  198. package/dist/resources/longLivedTransactions.d.ts +71 -0
  199. package/dist/resources/longLivedTransactions.js +358 -0
  200. package/dist/resources/longLivedTransactions.js.map +1 -0
  201. package/dist/resources/models/backendRegistry.d.ts +26 -0
  202. package/dist/resources/models/backendRegistry.js +60 -2
  203. package/dist/resources/models/backendRegistry.js.map +1 -1
  204. package/dist/resources/models/bootstrap.d.ts +33 -1
  205. package/dist/resources/models/bootstrap.js +416 -31
  206. package/dist/resources/models/bootstrap.js.map +1 -1
  207. package/dist/resources/recordLock.d.ts +80 -0
  208. package/dist/resources/recordLock.js +184 -0
  209. package/dist/resources/recordLock.js.map +1 -0
  210. package/dist/resources/replayLogs.d.ts +14 -1
  211. package/dist/resources/replayLogs.js +152 -24
  212. package/dist/resources/replayLogs.js.map +1 -1
  213. package/dist/resources/replayLogsGuards.d.ts +94 -7
  214. package/dist/resources/replayLogsGuards.js +111 -7
  215. package/dist/resources/replayLogsGuards.js.map +1 -1
  216. package/dist/resources/search.js +158 -32
  217. package/dist/resources/search.js.map +1 -1
  218. package/dist/resources/transactionBroadcast.d.ts +1 -1
  219. package/dist/resources/transactionBroadcast.js +2 -2
  220. package/dist/resources/transactionBroadcast.js.map +1 -1
  221. package/dist/security/auth.js +61 -30
  222. package/dist/security/auth.js.map +1 -1
  223. package/dist/security/authn/oidc/claims.d.ts +22 -0
  224. package/dist/security/authn/oidc/claims.js +71 -0
  225. package/dist/security/authn/oidc/claims.js.map +1 -0
  226. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  227. package/dist/security/authn/oidc/identityToken.js +111 -0
  228. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  229. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  230. package/dist/security/authn/oidc/jwks.js +261 -0
  231. package/dist/security/authn/oidc/jwks.js.map +1 -0
  232. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  233. package/dist/security/authn/oidc/providers/generic.js +34 -0
  234. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  235. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  236. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  237. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  238. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  239. package/dist/security/authn/oidc/providers/index.js +24 -0
  240. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  241. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  242. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  243. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  244. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  245. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  246. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  247. package/dist/security/authn/oidc/types.d.ts +38 -0
  248. package/dist/security/authn/oidc/types.js +6 -0
  249. package/dist/security/authn/oidc/types.js.map +1 -0
  250. package/dist/security/certificateVerification/index.js +40 -11
  251. package/dist/security/certificateVerification/index.js.map +1 -1
  252. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  253. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  254. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  255. package/dist/security/certificateVerification/types.d.ts +1 -0
  256. package/dist/security/credentialProvenance.d.ts +35 -0
  257. package/dist/security/credentialProvenance.js +51 -0
  258. package/dist/security/credentialProvenance.js.map +1 -0
  259. package/dist/security/credentialRejection.d.ts +4 -0
  260. package/dist/security/credentialRejection.js +24 -0
  261. package/dist/security/credentialRejection.js.map +1 -0
  262. package/dist/security/deferredAuthentication.d.ts +36 -0
  263. package/dist/security/deferredAuthentication.js +70 -0
  264. package/dist/security/deferredAuthentication.js.map +1 -0
  265. package/dist/security/impersonation.d.ts +21 -0
  266. package/dist/security/impersonation.js +108 -9
  267. package/dist/security/impersonation.js.map +1 -1
  268. package/dist/security/jsLoader.d.ts +6 -0
  269. package/dist/security/jsLoader.js +75 -15
  270. package/dist/security/jsLoader.js.map +1 -1
  271. package/dist/security/keys.js +301 -71
  272. package/dist/security/keys.js.map +1 -1
  273. package/dist/security/operationScope.d.ts +21 -0
  274. package/dist/security/operationScope.js +36 -0
  275. package/dist/security/operationScope.js.map +1 -0
  276. package/dist/security/permissionsTranslator.js +21 -0
  277. package/dist/security/permissionsTranslator.js.map +1 -1
  278. package/dist/security/tokenAuthentication.d.ts +19 -1
  279. package/dist/security/tokenAuthentication.js +191 -10
  280. package/dist/security/tokenAuthentication.js.map +1 -1
  281. package/dist/security/user.js +4 -3
  282. package/dist/security/user.js.map +1 -1
  283. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  284. package/dist/server/DurableSubscriptionsSession.js +67 -10
  285. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  286. package/dist/server/REST.js +73 -0
  287. package/dist/server/REST.js.map +1 -1
  288. package/dist/server/graphqlQuerying.js +4 -0
  289. package/dist/server/graphqlQuerying.js.map +1 -1
  290. package/dist/server/http.d.ts +11 -0
  291. package/dist/server/http.js +88 -15
  292. package/dist/server/http.js.map +1 -1
  293. package/dist/server/itc/serverHandlers.js +8 -1
  294. package/dist/server/itc/serverHandlers.js.map +1 -1
  295. package/dist/server/jobs/jobProcess.js +6 -2
  296. package/dist/server/jobs/jobProcess.js.map +1 -1
  297. package/dist/server/jobs/jobs.js +4 -1
  298. package/dist/server/jobs/jobs.js.map +1 -1
  299. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  300. package/dist/server/liveSubscriptionAuth.js +105 -39
  301. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  302. package/dist/server/loadRootComponents.js +49 -10
  303. package/dist/server/loadRootComponents.js.map +1 -1
  304. package/dist/server/mqtt.d.ts +2 -0
  305. package/dist/server/mqtt.js +165 -30
  306. package/dist/server/mqtt.js.map +1 -1
  307. package/dist/server/nodeName.d.ts +2 -0
  308. package/dist/server/nodeName.js +107 -23
  309. package/dist/server/nodeName.js.map +1 -1
  310. package/dist/server/serverHelpers/Headers.d.ts +25 -0
  311. package/dist/server/serverHelpers/Headers.js +139 -1
  312. package/dist/server/serverHelpers/Headers.js.map +1 -1
  313. package/dist/server/serverHelpers/contentTypes.d.ts +9 -0
  314. package/dist/server/serverHelpers/contentTypes.js +32 -25
  315. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  316. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  317. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  318. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  319. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  320. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  321. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  322. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  323. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  324. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  325. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  326. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  327. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  328. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  329. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  330. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  331. package/dist/server/serverHelpers/serverUtilities.js +90 -20
  332. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  333. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  334. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  335. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  336. package/dist/server/serverHelpers/uwsServer.js +19 -1
  337. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  338. package/dist/server/static.js +24 -28
  339. package/dist/server/static.js.map +1 -1
  340. package/dist/server/storageReclamation.d.ts +5 -0
  341. package/dist/server/storageReclamation.js +16 -0
  342. package/dist/server/storageReclamation.js.map +1 -1
  343. package/dist/server/threads/itc.d.ts +7 -2
  344. package/dist/server/threads/itc.js +5 -1
  345. package/dist/server/threads/itc.js.map +1 -1
  346. package/dist/server/threads/manageThreads.d.ts +26 -4
  347. package/dist/server/threads/manageThreads.js +517 -72
  348. package/dist/server/threads/manageThreads.js.map +1 -1
  349. package/dist/server/threads/socketRouter.js +89 -26
  350. package/dist/server/threads/socketRouter.js.map +1 -1
  351. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  352. package/dist/server/threads/threadHeapMemory.js +31 -0
  353. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  354. package/dist/server/threads/threadServer.js +46 -15
  355. package/dist/server/threads/threadServer.js.map +1 -1
  356. package/dist/sqlEngine/config.d.ts +1 -3
  357. package/dist/sqlEngine/config.js +19 -16
  358. package/dist/sqlEngine/config.js.map +1 -1
  359. package/dist/sqlTranslator/index.d.ts +1 -1
  360. package/dist/sqlTranslator/index.js +30 -7
  361. package/dist/sqlTranslator/index.js.map +1 -1
  362. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  363. package/dist/upgrade/directives/5-3-0.js +148 -0
  364. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  365. package/dist/upgrade/directives/directivesController.js +2 -1
  366. package/dist/upgrade/directives/directivesController.js.map +1 -1
  367. package/dist/utility/OperationFunctionCaller.js +2 -1
  368. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  369. package/dist/utility/common_utils.d.ts +16 -0
  370. package/dist/utility/common_utils.js +32 -6
  371. package/dist/utility/common_utils.js.map +1 -1
  372. package/dist/utility/componentNames.d.ts +8 -0
  373. package/dist/utility/componentNames.js +12 -1
  374. package/dist/utility/componentNames.js.map +1 -1
  375. package/dist/utility/environment/environmentManager.js +3 -6
  376. package/dist/utility/environment/environmentManager.js.map +1 -1
  377. package/dist/utility/environment/systemInformation.d.ts +1 -0
  378. package/dist/utility/environment/systemInformation.js +1 -0
  379. package/dist/utility/environment/systemInformation.js.map +1 -1
  380. package/dist/utility/errors/commonErrors.d.ts +2 -0
  381. package/dist/utility/errors/commonErrors.js +2 -0
  382. package/dist/utility/errors/commonErrors.js.map +1 -1
  383. package/dist/utility/errors/hdbError.d.ts +17 -0
  384. package/dist/utility/errors/hdbError.js +30 -1
  385. package/dist/utility/errors/hdbError.js.map +1 -1
  386. package/dist/utility/globalSchema.d.ts +18 -0
  387. package/dist/utility/hdbTerms.d.ts +16 -0
  388. package/dist/utility/hdbTerms.js +18 -2
  389. package/dist/utility/hdbTerms.js.map +1 -1
  390. package/dist/utility/logging/harper_logger.d.ts +2 -0
  391. package/dist/utility/logging/harper_logger.js +91 -14
  392. package/dist/utility/logging/harper_logger.js.map +1 -1
  393. package/dist/utility/logging/logRotator.js +76 -49
  394. package/dist/utility/logging/logRotator.js.map +1 -1
  395. package/dist/utility/nodeIdentity.d.ts +9 -0
  396. package/dist/utility/nodeIdentity.js +58 -0
  397. package/dist/utility/nodeIdentity.js.map +1 -0
  398. package/dist/utility/npmUtilities.js +9 -7
  399. package/dist/utility/npmUtilities.js.map +1 -1
  400. package/dist/utility/operationPermissions.d.ts +3 -1
  401. package/dist/utility/operationPermissions.js +16 -1
  402. package/dist/utility/operationPermissions.js.map +1 -1
  403. package/dist/utility/operation_authorization.d.ts +10 -7
  404. package/dist/utility/operation_authorization.js +212 -41
  405. package/dist/utility/operation_authorization.js.map +1 -1
  406. package/dist/utility/watchPath.d.ts +29 -0
  407. package/dist/utility/watchPath.js +68 -0
  408. package/dist/utility/watchPath.js.map +1 -0
  409. package/dist/utility/watcherFallback.d.ts +86 -0
  410. package/dist/utility/watcherFallback.js +278 -1
  411. package/dist/utility/watcherFallback.js.map +1 -1
  412. package/dist/validation/configValidator.d.ts +12 -0
  413. package/dist/validation/configValidator.js +199 -72
  414. package/dist/validation/configValidator.js.map +1 -1
  415. package/dist/validation/installValidator.js +12 -0
  416. package/dist/validation/installValidator.js.map +1 -1
  417. package/dist/validation/validationWrapper.d.ts +11 -0
  418. package/dist/validation/validationWrapper.js +16 -3
  419. package/dist/validation/validationWrapper.js.map +1 -1
  420. package/index.ts +2 -0
  421. package/json/systemSchema.json +52 -0
  422. package/npm-shrinkwrap.json +235 -233
  423. package/package.json +8 -7
  424. package/resources/DESIGN.md +114 -52
  425. package/resources/DatabaseTransaction.ts +522 -130
  426. package/resources/LMDBTransaction.ts +26 -4
  427. package/resources/PrimaryRocksDatabase.ts +29 -3
  428. package/resources/RecordEncoder.ts +103 -8
  429. package/resources/RequestTarget.ts +2 -0
  430. package/resources/Resource.ts +17 -9
  431. package/resources/ResourceInterface.ts +23 -0
  432. package/resources/RocksIndexStore.ts +30 -10
  433. package/resources/RocksTransactionLogStore.ts +78 -21
  434. package/resources/Table.ts +1107 -164
  435. package/resources/auditStore.ts +187 -21
  436. package/resources/blob.ts +1029 -110
  437. package/resources/branchDatabase.ts +941 -0
  438. package/resources/crdt.ts +76 -12
  439. package/resources/databases.ts +1354 -522
  440. package/resources/defineTable.ts +18 -2
  441. package/resources/graphql.ts +70 -16
  442. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +6 -2
  443. package/resources/longLivedTransactions.ts +360 -0
  444. package/resources/models/backendRegistry.ts +84 -2
  445. package/resources/models/bootstrap.ts +473 -28
  446. package/resources/recordLock.ts +253 -0
  447. package/resources/replayLogs.ts +147 -26
  448. package/resources/replayLogsGuards.ts +171 -8
  449. package/resources/search.ts +154 -32
  450. package/resources/transactionBroadcast.ts +3 -3
  451. package/security/auth.ts +68 -29
  452. package/security/authn/oidc/claims.ts +72 -0
  453. package/security/authn/oidc/identityToken.ts +129 -0
  454. package/security/authn/oidc/jwks.ts +260 -0
  455. package/security/authn/oidc/providers/generic.ts +40 -0
  456. package/security/authn/oidc/providers/githubActions.ts +137 -0
  457. package/security/authn/oidc/providers/index.ts +52 -0
  458. package/security/authn/oidc/tokenExchange.ts +300 -0
  459. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  460. package/security/authn/oidc/types.ts +41 -0
  461. package/security/certificateVerification/index.ts +54 -13
  462. package/security/certificateVerification/trustedIssuers.ts +76 -0
  463. package/security/certificateVerification/types.ts +1 -0
  464. package/security/credentialProvenance.ts +47 -0
  465. package/security/credentialRejection.ts +22 -0
  466. package/security/deferredAuthentication.ts +71 -0
  467. package/security/impersonation.ts +117 -12
  468. package/security/jsLoader.ts +81 -18
  469. package/security/keys.ts +298 -72
  470. package/security/operationScope.ts +33 -0
  471. package/security/permissionsTranslator.js +23 -0
  472. package/security/tokenAuthentication.ts +233 -12
  473. package/security/user.ts +4 -3
  474. package/server/DESIGN.md +183 -16
  475. package/server/DurableSubscriptionsSession.ts +71 -11
  476. package/server/REST.ts +79 -1
  477. package/server/graphqlQuerying.ts +4 -0
  478. package/server/http.ts +99 -18
  479. package/server/itc/serverHandlers.js +8 -1
  480. package/server/jobs/jobProcess.ts +8 -2
  481. package/server/jobs/jobs.ts +4 -1
  482. package/server/liveSubscriptionAuth.ts +129 -46
  483. package/server/loadRootComponents.js +50 -8
  484. package/server/mqtt.ts +179 -38
  485. package/server/nodeName.ts +103 -21
  486. package/server/serverHelpers/Headers.ts +131 -0
  487. package/server/serverHelpers/contentTypes.ts +29 -21
  488. package/server/serverHelpers/deployValidationState.ts +24 -13
  489. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  490. package/server/serverHelpers/registeredOperations.ts +79 -22
  491. package/server/serverHelpers/requestSanitization.ts +15 -0
  492. package/server/serverHelpers/serverHandlers.js +6 -3
  493. package/server/serverHelpers/serverUtilities.ts +104 -31
  494. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  495. package/server/serverHelpers/uwsServer.ts +17 -2
  496. package/server/static.ts +23 -29
  497. package/server/storageReclamation.ts +13 -0
  498. package/server/threads/itc.js +11 -1
  499. package/server/threads/manageThreads.js +526 -63
  500. package/server/threads/socketRouter.ts +81 -26
  501. package/server/threads/threadHeapMemory.ts +26 -0
  502. package/server/threads/threadServer.js +43 -15
  503. package/sqlTranslator/index.ts +31 -8
  504. package/studio/web/assets/{Chat-Br06zdMA.js → Chat-BnCBegQz.js} +1 -1
  505. package/studio/web/assets/{FloatingChat-BWImX5fA.js → FloatingChat-CoDW1ySS.js} +3 -3
  506. package/studio/web/assets/{apiToken-DN0nmDsq.js → apiToken-Bwk5BLXW.js} +1 -1
  507. package/studio/web/assets/{applications-kSxVoyeU.js → applications-DHxGi7JH.js} +1 -1
  508. package/studio/web/assets/{cssMode-C1JeufH5.js → cssMode-s0cWI-_M.js} +1 -1
  509. package/studio/web/assets/{editor-qoo9CrGO.js → editor-DNcRHK54.js} +1 -1
  510. package/studio/web/assets/{html-Dt4IIy04.js → html-Bdssedlg.js} +1 -1
  511. package/studio/web/assets/{htmlMode-DXgKKr4C.js → htmlMode-CoDlJ3fw.js} +1 -1
  512. package/studio/web/assets/{index-6onkYFOG.js → index-D6sxmFLR.js} +5 -5
  513. package/studio/web/assets/{index.lazy-BrCFnpNJ.js → index.lazy-tmU5BS8s.js} +1 -1
  514. package/studio/web/assets/{javascript-DNCQGUBc.js → javascript-B8meVSTH.js} +1 -1
  515. package/studio/web/assets/{jsonMode-CR6HWruP.js → jsonMode-DpIPd35T.js} +1 -1
  516. package/studio/web/assets/{languageServices-BM4fI4rS.js → languageServices-C_5FMJzQ.js} +1 -1
  517. package/studio/web/assets/{lspLanguageFeatures-DSa1ttcD.js → lspLanguageFeatures-BIzNBzPK.js} +1 -1
  518. package/studio/web/assets/{notifications-BHXLnh6x.js → notifications-CQf18QKb.js} +1 -1
  519. package/studio/web/assets/{notifications-CUmtIA6z.js → notifications-CvZivSbh.js} +1 -1
  520. package/studio/web/assets/{profile-8BeFSF3j.js → profile-DdOwtntb.js} +1 -1
  521. package/studio/web/assets/{regions-C8qR0HhD.js → regions-n69fwagr.js} +1 -1
  522. package/studio/web/assets/{register-B4n5i0SD.js → register-PfWTCXWB.js} +2 -2
  523. package/studio/web/assets/{setComponentFile-g0_B0lgX.js → setComponentFile-Bg6O7X0S.js} +1 -1
  524. package/studio/web/assets/{setup-B0CTj_Q5.js → setup-CUx_aUDl.js} +2 -2
  525. package/studio/web/assets/{status-C6Yib7-K.js → status-D7BVKqX9.js} +1 -1
  526. package/studio/web/assets/{toggleHighContrast-Dgta7bVi.js → toggleHighContrast-DBSyXzMr.js} +1 -1
  527. package/studio/web/assets/{tsMode-CH_jHvU-.js → tsMode-BByKCjBS.js} +1 -1
  528. package/studio/web/assets/{typescript-Co9LCXd5.js → typescript-DDLnLpw9.js} +1 -1
  529. package/studio/web/assets/{useEntityRestURL-wKC8NsC_.js → useEntityRestURL-31CHGaHk.js} +1 -1
  530. package/studio/web/assets/{workers-CWeLxCXA.js → workers-pR3jRY9D.js} +1 -1
  531. package/studio/web/assets/{xml-BadC-0Rk.js → xml-2iRnMhQO.js} +1 -1
  532. package/studio/web/assets/{yaml-BiUfxPbC.js → yaml-Bf92gJpd.js} +1 -1
  533. package/studio/web/index.html +1 -1
  534. package/upgrade/directives/5-3-0.ts +132 -0
  535. package/upgrade/directives/directivesController.ts +2 -1
  536. package/utility/OperationFunctionCaller.ts +2 -1
  537. package/utility/common_utils.ts +30 -5
  538. package/utility/componentNames.ts +12 -0
  539. package/utility/environment/environmentManager.ts +3 -7
  540. package/utility/environment/systemInformation.ts +7 -0
  541. package/utility/errors/commonErrors.ts +4 -0
  542. package/utility/errors/hdbError.ts +29 -0
  543. package/utility/hdbTerms.ts +17 -0
  544. package/utility/logging/harper_logger.ts +87 -14
  545. package/utility/logging/logRotator.ts +72 -45
  546. package/utility/nodeIdentity.ts +45 -0
  547. package/utility/npmUtilities.ts +10 -8
  548. package/utility/operationPermissions.ts +18 -1
  549. package/utility/operation_authorization.ts +231 -42
  550. package/utility/watchPath.ts +63 -0
  551. package/utility/watcherFallback.ts +270 -0
  552. package/validation/configValidator.ts +209 -72
  553. package/validation/installValidator.ts +15 -0
  554. package/validation/validationWrapper.ts +18 -4
@@ -25,13 +25,26 @@
25
25
  import type { Logger } from '../utility/logging/logger.ts';
26
26
  import * as fs from 'fs-extra';
27
27
  import * as path from 'node:path';
28
+ import { isMainThread } from 'node:worker_threads';
28
29
  import * as crypto from 'node:crypto';
29
30
  import { cloneDeep } from 'lodash';
30
31
  import { getBackupDirPath } from './configHelpers.ts';
31
- import { atomicWriteFile } from './configUtils.ts';
32
+ import { atomicWriteFile, renameWithRetry } from './configUtils.ts';
32
33
  import * as hdbTerms from '../utility/hdbTerms.ts';
33
34
 
34
35
  const STATE_FILE_NAME = '.harper-config-state.json';
36
+ // Staged beside the confirmed state while a config-file write is in flight, then renamed over it.
37
+ // Per-process, because every CLI invocation and worker runs this and one shared name would let one
38
+ // clear another's in-flight commit. See DESIGN.md, boot-path config persistence.
39
+ const PENDING_STATE_PREFIX = '.harper-config-state.pending.';
40
+ const PENDING_STATE_SUFFIX = '.json';
41
+ const pendingStateFileName = () => `${PENDING_STATE_PREFIX}${process.pid}${PENDING_STATE_SUFFIX}`;
42
+ // Recovery from a recycled pid only has to be eventual, and deleting a slow-but-live writer's
43
+ // sidecar is the worse error - it strands that writer's config file against an unpromoted state. So
44
+ // the age-out is far longer than a commit (three synchronous steps) could ever legitimately take:
45
+ // long enough that a stalled writer or clock skew between containers sharing a volume cannot reach
46
+ // it, short enough that leaked wreckage does not suspend drift detection indefinitely.
47
+ const PENDING_STATE_STALE_MS = 60 * 60 * 1000;
35
48
 
36
49
  /**
37
50
  * Get logger instance with tag - lazy loaded to avoid circular dependencies
@@ -546,15 +559,7 @@ function parseConfigEnvVar(envVarValue: string | undefined, envVarName: string):
546
559
  function loadConfigState(rootPath: string): ConfigState {
547
560
  const statePath = path.join(getBackupDirPath(rootPath), STATE_FILE_NAME);
548
561
 
549
- if (!fs.existsSync(statePath)) {
550
- return {
551
- version: '1.0',
552
- sources: {},
553
- originalValues: {},
554
- emptyScopeOriginals: {},
555
- snapshots: {},
556
- };
557
- }
562
+ if (!fs.existsSync(statePath)) return freshConfigState();
558
563
 
559
564
  try {
560
565
  const state = fs.readJsonSync(statePath) as ConfigState;
@@ -573,20 +578,85 @@ function loadConfigState(rootPath: string): ConfigState {
573
578
  // If state file is corrupted, start fresh
574
579
  const logger = getLogger();
575
580
  logger.warn(`Failed to load config state file, starting fresh: ${(error as Error).message}`);
576
- return {
577
- version: '1.0',
578
- sources: {},
579
- originalValues: {},
580
- emptyScopeOriginals: {},
581
- snapshots: {},
582
- };
581
+ return freshConfigState();
583
582
  }
584
583
  }
585
584
 
585
+ /**
586
+ * Clear a sidecar left by an interrupted commit, reporting whether one was there. The caller skips
587
+ * drift detection for that boot: it cannot tell a manual user edit from the write that was in
588
+ * flight, and calling it an edit hands those paths to 'user' for good.
589
+ */
590
+ function takeInterruptedCommit(rootPath: string): boolean {
591
+ const backupDir = getBackupDirPath(rootPath);
592
+ let entries: string[];
593
+ try {
594
+ entries = fs.readdirSync(backupDir);
595
+ } catch {
596
+ return false;
597
+ }
598
+ let interrupted = false;
599
+ for (const entry of entries) {
600
+ if (!entry.startsWith(PENDING_STATE_PREFIX) || !entry.endsWith(PENDING_STATE_SUFFIX)) continue;
601
+ const pid = Number(entry.slice(PENDING_STATE_PREFIX.length, -PENDING_STATE_SUFFIX.length));
602
+ const entryPath = path.join(backupDir, entry);
603
+ if (pid !== process.pid && isProcessAlive(pid) && !isStale(entryPath)) {
604
+ // A live owner is mid-commit, so its sidecar is not wreckage to clear - but the pair it is
605
+ // halfway through is no more comparable than an interrupted one, so drift detection is off
606
+ // for this boot either way.
607
+ interrupted = true;
608
+ continue;
609
+ }
610
+ try {
611
+ fs.removeSync(entryPath);
612
+ } catch (error) {
613
+ // Keep drift detection on rather than disabling it every boot over a sidecar we cannot clear
614
+ getLogger().warn(`Could not remove an interrupted env config commit (${entry}): ${(error as Error).message}`);
615
+ continue;
616
+ }
617
+ interrupted = true;
618
+ }
619
+ if (interrupted) getLogger().warn('An env config commit was interrupted; skipping drift detection for this boot');
620
+ return interrupted;
621
+ }
622
+
623
+ function isStale(entryPath: string): boolean {
624
+ try {
625
+ return Date.now() - fs.statSync(entryPath).mtimeMs > PENDING_STATE_STALE_MS;
626
+ } catch {
627
+ return false;
628
+ }
629
+ }
630
+
631
+ function isProcessAlive(pid: number): boolean {
632
+ if (!Number.isInteger(pid) || pid <= 0) return false;
633
+ try {
634
+ process.kill(pid, 0);
635
+ return true;
636
+ } catch (error) {
637
+ return (error as NodeJS.ErrnoException).code === 'EPERM';
638
+ }
639
+ }
640
+
641
+ function freshConfigState(): ConfigState {
642
+ return {
643
+ version: '1.0',
644
+ sources: {},
645
+ originalValues: {},
646
+ emptyScopeOriginals: {},
647
+ snapshots: {},
648
+ };
649
+ }
650
+
586
651
  /**
587
652
  * Save configuration state to file
588
653
  */
589
- function saveConfigState(rootPath: string, state: ConfigState): void {
654
+ function serializeConfigState(state: ConfigState): string {
655
+ return JSON.stringify(state, null, 2) + '\n';
656
+ }
657
+
658
+ // Returns true when the file was rewritten, false when it already held this state.
659
+ function saveConfigState(rootPath: string, state: ConfigState): boolean {
590
660
  const backupDir = getBackupDirPath(rootPath);
591
661
  const statePath = path.join(backupDir, STATE_FILE_NAME);
592
662
 
@@ -595,7 +665,49 @@ function saveConfigState(rootPath: string, state: ConfigState): void {
595
665
 
596
666
  // Atomic write: a torn state file resets to fresh on the next load, losing every
597
667
  // restoration record — the blast radius is user config-file content
598
- atomicWriteFile(statePath, JSON.stringify(state, null, 2) + '\n');
668
+ return atomicWriteFile(statePath, serializeConfigState(state), { skipIfUnchanged: true });
669
+ }
670
+
671
+ function configStateMatchesDisk(rootPath: string, state: ConfigState): boolean {
672
+ try {
673
+ const statePath = path.join(getBackupDirPath(rootPath), STATE_FILE_NAME);
674
+ return fs.readFileSync(statePath, 'utf8') === serializeConfigState(state);
675
+ } catch {
676
+ return false;
677
+ }
678
+ }
679
+
680
+ function stageConfigState(rootPath: string, state: ConfigState): boolean {
681
+ const backupDir = getBackupDirPath(rootPath);
682
+ fs.ensureDirSync(backupDir);
683
+ return atomicWriteFile(path.join(backupDir, pendingStateFileName()), serializeConfigState(state));
684
+ }
685
+
686
+ // A rename, so an exhausted volume cannot refuse it and leave the config file described by nothing.
687
+
688
+ export function commitStagedConfigState(rootPath: string): boolean {
689
+ const backupDir = getBackupDirPath(rootPath);
690
+ const pendingPath = path.join(backupDir, pendingStateFileName());
691
+ try {
692
+ renameWithRetry(pendingPath, path.join(backupDir, STATE_FILE_NAME));
693
+ return true;
694
+ } catch (error) {
695
+ // Loud: the config file is already on disk, so the confirmed state now describes values it no
696
+ // longer has, and the next boot reads that difference as a manual user edit.
697
+ getLogger().error(`Could not promote the staged env config state at ${pendingPath}: ${(error as Error).message}`);
698
+ return false;
699
+ }
700
+ }
701
+
702
+ // Drops the staged state for a config-file write that did not happen; the confirmed record stays.
703
+
704
+ export function discardConfigState(rootPath: string): void {
705
+ const pendingPath = path.join(getBackupDirPath(rootPath), pendingStateFileName());
706
+ try {
707
+ fs.removeSync(pendingPath);
708
+ } catch (error) {
709
+ getLogger().warn(`Could not remove the staged env config state at ${pendingPath}: ${(error as Error).message}`);
710
+ }
599
711
  }
600
712
 
601
713
  /**
@@ -957,20 +1069,49 @@ export function applyRuntimeEnvConfig(
957
1069
  rootPath: string,
958
1070
  options: { isInstall?: boolean } = {}
959
1071
  ): ConfigObject {
1072
+ const { config, commitState } = prepareRuntimeEnvConfig(fileConfig, rootPath, options);
1073
+ commitState();
1074
+ return config;
1075
+ }
1076
+
1077
+ /**
1078
+ * Apply the env layers and hand the state writes back to the caller, so a caller that also persists
1079
+ * the merged config file can commit the pair as a unit (#847): saveState() stages the new state
1080
+ * beside the confirmed one, confirmConfigWritten() renames it over the confirmed one after the
1081
+ * config file lands, and discardConfigState() drops the staged copy if that write never happens.
1082
+ */
1083
+ export function prepareRuntimeEnvConfig(
1084
+ fileConfig: ConfigObject,
1085
+ rootPath: string,
1086
+ options: { isInstall?: boolean } = {}
1087
+ ): {
1088
+ config: ConfigObject;
1089
+ saveState: () => boolean;
1090
+ confirmConfigWritten: () => boolean;
1091
+ commitState: () => boolean;
1092
+ } {
960
1093
  const defaultEnvValue = process.env.HARPER_DEFAULT_CONFIG;
961
1094
  const configEnvValue = process.env.HARPER_CONFIG;
962
1095
  const setEnvValue = process.env.HARPER_SET_CONFIG;
963
1096
 
964
- // Load existing state
1097
+ // Load existing state. Only the main thread persists, so only the main thread has wreckage to
1098
+ // clear - and a worker shares its pid, so letting one scan would delete the main thread's
1099
+ // in-flight sidecar as if it were last boot's.
1100
+ const interruptedCommit = isMainThread && takeInterruptedCommit(rootPath);
965
1101
  const state = loadConfigState(rootPath);
966
1102
 
967
1103
  // No env vars set and no previous state, nothing to do
968
1104
  if (!defaultEnvValue && !configEnvValue && !setEnvValue && Object.keys(state.snapshots).length === 0) {
969
- return fileConfig;
1105
+ return { config: fileConfig, saveState: () => false, confirmConfigWritten: () => false, commitState: () => false };
970
1106
  }
971
1107
 
972
- // Detect drift (user manual edits) - only at runtime, not install
973
- if (!options.isInstall) {
1108
+ // Detect drift (user manual edits) - only at runtime, not install, not on a boot that found an
1109
+ // interrupted commit (where a difference could equally be the write that was in flight), and only
1110
+ // on the main thread: a worker never owns the state, and one re-deriving inside the main thread's
1111
+ // commit window would call its half-written config file a user edit and drop the env-supplied
1112
+ // value for itself alone - serving different config than its siblings, with nothing on disk to
1113
+ // show why.
1114
+ if (!options.isInstall && !interruptedCommit && isMainThread) {
974
1115
  const driftedPaths = detectConfigDrift(fileConfig, state);
975
1116
  for (const path of driftedPaths) {
976
1117
  state.sources[path] = 'user';
@@ -998,8 +1139,10 @@ export function applyRuntimeEnvConfig(
998
1139
  processEnvVar(fileConfig, state, 'HARPER_CONFIG', 'HARPER_CONFIG', options);
999
1140
  processEnvVar(fileConfig, state, 'HARPER_SET_CONFIG', 'HARPER_SET_CONFIG', options);
1000
1141
 
1001
- // Save updated state
1002
- saveConfigState(rootPath, state);
1003
-
1004
- return fileConfig;
1142
+ return {
1143
+ config: fileConfig,
1144
+ saveState: () => (configStateMatchesDisk(rootPath, state) ? false : stageConfigState(rootPath, state)),
1145
+ confirmConfigWritten: () => commitStagedConfigState(rootPath),
1146
+ commitState: () => saveConfigState(rootPath, state),
1147
+ };
1005
1148
  }
@@ -519,6 +519,10 @@
519
519
  "type": "string",
520
520
  "description": "Max write queue time before rejecting (e.g. '45s')."
521
521
  },
522
+ "longTransactionReportThreshold": {
523
+ "type": ["string", "number"],
524
+ "description": "How old a RocksDB transaction handle must be before it is reported in the log (e.g. '5m'). A handle this old can hold write intents that park other writers' commits and pin a read snapshot. Set to 0 to disable the reporting. Default: '5m'"
525
+ },
522
526
  "noReadAhead": { "type": "boolean", "description": "Advise OS to not read ahead. Default: false" },
523
527
  "randomAccessFields": {
524
528
  "type": "boolean",
@@ -534,6 +538,31 @@
534
538
  "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }],
535
539
  "description": "Path or array of paths for blob storage. Default: <rootPath>/blobs"
536
540
  },
541
+ "blobs": {
542
+ "type": "object",
543
+ "description": "File-backed blob storage settings.",
544
+ "properties": {
545
+ "compression": {
546
+ "type": "object",
547
+ "description": "Opt-in compression for newly written file-backed blobs, keyed by content type: an exact type ('application/json'), a wildcard ('text/*'), or 'default'. Absent means no compression. Each entry is false (never compress the type) or an object { codec, threshold }: codec currently only 'deflate' (the default); threshold is the minimum content size in bytes to compress (default 8192). Matching precedence is exact type, then 'type/*', then 'default'. Types that are already compressed (application/gzip, application/zip, application/zstd, image/*, video/*, audio/*) ship as false and stay uncompressed unless explicitly overridden. Blobs streamed without a known size are not compressed, and a threshold below 8192 only affects streamed writes — buffers under 8KB are stored inline in the record (covered by record-level compression) and never reach blob files. Applies to new writes only; existing blob files are never rewritten.",
548
+ "propertyNames": { "pattern": "^([\\w.+-]+/(\\*|[\\w.+-]+)|default)$" },
549
+ "additionalProperties": {
550
+ "oneOf": [
551
+ { "const": false },
552
+ {
553
+ "type": "object",
554
+ "properties": {
555
+ "codec": { "const": "deflate" },
556
+ "threshold": { "type": "number", "minimum": 0 }
557
+ },
558
+ "additionalProperties": false
559
+ }
560
+ ]
561
+ }
562
+ }
563
+ },
564
+ "additionalProperties": false
565
+ },
537
566
  "blobReadTimeout": {
538
567
  "type": "number",
539
568
  "description": "Max time (ms) a blob read will wait for an in-progress write to finish before failing with a 503, rather than holding the connection. Default: 20000"
@@ -5,6 +5,12 @@ import { copyFile, link, mkdir, readdir, rename, rm, unlink, writeFile } from 'n
5
5
  import { dirname, join, relative } from 'node:path';
6
6
  import { ClientError } from '../utility/errors/hdbError.ts';
7
7
  import logger from '../utility/logging/harper_logger.ts';
8
+ import {
9
+ type BlobCaptureDisposition,
10
+ classifyBlobFileForCapture,
11
+ createCaptureMarker,
12
+ isSystemicIoError,
13
+ } from '../resources/blob.ts';
8
14
 
9
15
  /**
10
16
  * Managed-backup snapshotting of a database's file-backed blobs.
@@ -21,17 +27,17 @@ import logger from '../utility/logging/harper_logger.ts';
21
27
  * possible (cheap, no extra space on the same filesystem) and copied otherwise (never symlinked, so
22
28
  * a snapshot is a standalone set of files that survives independent of the live blob).
23
29
  *
24
- * Consistency is best-effort and point-in-time-ish, matching how the engine treats the transaction
25
- * log: the walk captures whatever files exist at snapshot time. A blob deleted mid-walk is skipped;
26
- * a blob being written mid-walk is captured as-is (a hard link shares the inode, so it reflects the
27
- * writer's final bytes; a cross-filesystem copy captures the bytes present at copy time). Harper
28
- * does not freeze blob writes for the duration of a backup.
30
+ * Harper does not freeze blob writes for a backup, and a blob is written in place at its final path,
31
+ * so the walk can meet one that is still growing. Hard-linking that inode would put bytes in the
32
+ * snapshot that keep changing after it was taken. Every entry returned by the walk is therefore
33
+ * classified (`captureBlobFile`): a complete blob is linked, and one that becomes unavailable or is
34
+ * not yet complete is replaced by a marker rather than dropped. This closes the classify-to-capture
35
+ * race and keeps that file id reserved; a file reclaimed before its parent directory is read remains
36
+ * outside the snapshot.
29
37
  *
30
- * Hard-linking is safe against later mutation because Harper blobs are content-addressed and
31
- * write-once: each write allocates a fresh monotonic file id (a new path), and an update or delete
32
- * unlinks the old path rather than rewriting it in place — so a snapshot's hard link keeps the exact
33
- * bytes alive even after the live blob is deleted, and no in-place overwrite can retroactively alter
34
- * a snapshot.
38
+ * Past that gate, hard-linking is safe against later mutation: each write takes a fresh monotonic
39
+ * file id, and an update, delete, or in-place repair replaces or unlinks the path rather than
40
+ * rewriting the inode.
35
41
  */
36
42
 
37
43
  /** Directory holding all blob snapshots for a backup repository. */
@@ -46,17 +52,17 @@ export function blobSnapshotDir(backupDir: string, backupId: number): string {
46
52
 
47
53
  /**
48
54
  * Hard-link `src` to `dest`, falling back to a copy when the two are on different filesystems (or
49
- * the filesystem does not support additional hard links). Never creates a symlink. A source that
50
- * vanished mid-walk (a concurrent blob delete) is skipped rather than failing the whole snapshot.
55
+ * the filesystem does not support additional hard links). Never creates a symlink. Returns false
56
+ * when the source vanishes so the caller can substitute a marker for it. `counts.copied` is what a
57
+ * caller on a latency budget needs: the fallback turns a constant-time clone into an O(bytes) one.
51
58
  */
52
- async function linkOrCopy(src: string, dest: string): Promise<void> {
59
+ async function linkOrCopy(src: string, dest: string, counts?: { copied: number }): Promise<boolean> {
53
60
  await mkdir(dirname(dest), { recursive: true });
54
61
  try {
55
62
  await link(src, dest);
56
63
  } catch (error: any) {
57
64
  if (error.code === 'ENOENT') {
58
- // src disappeared (concurrent delete) — nothing to snapshot
59
- if (!existsSync(src)) return;
65
+ if (!existsSync(src)) return false; // vanished since it was classified
60
66
  throw error;
61
67
  }
62
68
  if (
@@ -66,24 +72,86 @@ async function linkOrCopy(src: string, dest: string): Promise<void> {
66
72
  error.code === 'ENOTSUP' ||
67
73
  error.code === 'EOPNOTSUPP'
68
74
  ) {
69
- await copyFile(src, dest);
70
- return;
75
+ try {
76
+ await copyFile(src, dest);
77
+ } catch (copyError: any) {
78
+ if (copyError.code === 'ENOENT') return false;
79
+ throw copyError;
80
+ }
81
+ if (counts) counts.copied++;
82
+ return true;
71
83
  }
72
84
  if (error.code === 'EEXIST') {
73
85
  await unlink(dest);
74
- await linkOrCopy(src, dest);
75
- return;
86
+ return linkOrCopy(src, dest, counts);
76
87
  }
77
88
  throw error;
78
89
  }
90
+ return true;
91
+ }
92
+
93
+ /** What a substituted marker says happened, so a branch clone doesn't report itself as a backup. */
94
+ export interface CaptureMarkerReasons {
95
+ gone: string;
96
+ pending: string;
97
+ }
98
+
99
+ const BACKUP_MARKER_REASONS: CaptureMarkerReasons = {
100
+ gone: 'blob was deleted while this backup was being taken',
101
+ pending: 'blob was not yet complete when this backup was taken',
102
+ };
103
+
104
+ /** Put one blob file into the destination, returning how the entry was captured. */
105
+ async function captureBlobFile(
106
+ srcPath: string,
107
+ destPath: string,
108
+ reasons: CaptureMarkerReasons = BACKUP_MARKER_REASONS,
109
+ counts?: { copied: number }
110
+ ): Promise<BlobCaptureDisposition> {
111
+ let disposition: BlobCaptureDisposition;
112
+ try {
113
+ disposition = await classifyBlobFileForCapture(srcPath);
114
+ } catch (error) {
115
+ // This rethrow is unverified: only the membership of SYSTEMIC_IO_ERRORS is tested, because
116
+ // reaching it needs a real host-level fault no test here can produce.
117
+ if (isSystemicIoError(error)) throw error;
118
+ // Fall back to what this walk did before it classified anything. `link()` needs no read permission
119
+ // on the source, so a blob this failed to *read* may still be perfectly capturable; substituting
120
+ // here would turn an unreadable-but-valid root into a backup of nothing but stubs.
121
+ logger.warn(`Could not verify blob ${srcPath} for snapshot; capturing it unverified`, error);
122
+ disposition = 'capture';
123
+ }
124
+ if (disposition === 'skip') return disposition;
125
+ if (disposition === 'capture') {
126
+ if (await linkOrCopy(srcPath, destPath, counts)) return disposition;
127
+ disposition = 'gone';
128
+ }
129
+ await mkdir(dirname(destPath), { recursive: true });
130
+ await writeFile(destPath, createCaptureMarker(disposition, disposition === 'gone' ? reasons.gone : reasons.pending));
131
+ return disposition;
79
132
  }
80
133
 
81
134
  /**
82
135
  * Recursively copy every file under `srcRoot` into `destRoot` (hard-link-else-copy), preserving the
83
136
  * relative directory structure. Missing `srcRoot` is a no-op (a database with no blobs yet).
137
+ *
138
+ * `classify` substitutes a marker for a file that is mid-write or already gone, so the destination
139
+ * fails loudly on that blob rather than carrying a truncated one. It belongs to the capture direction
140
+ * only; restore must replace exactly what the snapshot holds.
141
+ *
142
+ * Also used by branch materialization (harper#644), which clones a base's blob roots into the
143
+ * branch's own so the OS inode refcount does the reference counting. Its walk runs inside a window
144
+ * other threads wait on, which is what `onProgress` is for.
84
145
  */
85
- async function copyTree(srcRoot: string, destRoot: string): Promise<void> {
86
- if (!existsSync(srcRoot)) return;
146
+ export async function copyTree(
147
+ srcRoot: string,
148
+ destRoot: string,
149
+ classify = false,
150
+ reasons?: CaptureMarkerReasons,
151
+ onProgress?: () => void
152
+ ): Promise<{ substituted: number; captured: number; copied: number }> {
153
+ const counts = { substituted: 0, captured: 0, copied: 0 };
154
+ if (!existsSync(srcRoot)) return counts;
87
155
  const stack: string[] = [srcRoot];
88
156
  while (stack.length > 0) {
89
157
  const dir = stack.pop() as string;
@@ -99,63 +167,103 @@ async function copyTree(srcRoot: string, destRoot: string): Promise<void> {
99
167
  if (entry.isDirectory()) {
100
168
  stack.push(srcPath);
101
169
  } else if (entry.isFile()) {
102
- await linkOrCopy(srcPath, join(destRoot, relative(srcRoot, srcPath)));
170
+ const destPath = join(destRoot, relative(srcRoot, srcPath));
171
+ if (classify) {
172
+ const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
173
+ if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
174
+ else if (disposition === 'capture') counts.captured++;
175
+ } else {
176
+ await linkOrCopy(srcPath, destPath, counts);
177
+ }
178
+ onProgress?.();
103
179
  }
104
180
  // symlinks/other node types in a blob root are not expected and are intentionally skipped
105
181
  }
106
182
  }
183
+ return counts;
107
184
  }
108
185
 
109
186
  /**
110
- * Snapshot a database's blob roots into a backup's blob directory. Writes to a temporary sibling
111
- * and atomically renames into place so a create_backup that fails mid-copy never leaves a partial
112
- * `blobs/<backupId>/` that a later restore would trust. Overwrites any pre-existing snapshot for the
113
- * same id (create_backup always produces a fresh id, so this only matters on a retried offline run).
187
+ * Copy every blob root into `destDir` as `<rootIndex>/<relpath>`, hard-linking where possible.
188
+ * Writes to a temporary sibling and atomically renames into place so a run that fails mid-copy never
189
+ * leaves a partial directory a later restore would trust, and replaces any pre-existing `destDir`.
190
+ * Shared by managed-backup snapshots and `copy-db`'s standalone blob copy (harper#2048).
114
191
  */
115
- export async function snapshotBlobs(backupDir: string, backupId: number, blobRoots: string[]): Promise<void> {
116
- const finalDir = blobSnapshotDir(backupDir, backupId);
117
- const tempDir = join(blobsRootDir(backupDir), `.tmp-${backupId}`);
192
+ export async function copyBlobRootsByIndex(destDir: string, blobRoots: string[]): Promise<void> {
193
+ const tempDir = destDir + '.tmp';
118
194
  await rm(tempDir, { recursive: true, force: true });
119
195
  await mkdir(tempDir, { recursive: true });
120
196
  try {
197
+ let substituted = 0;
198
+ let captured = 0;
121
199
  for (let index = 0; index < blobRoots.length; index++) {
122
- await copyTree(blobRoots[index], join(tempDir, String(index)));
200
+ const counts = await copyTree(blobRoots[index], join(tempDir, String(index)), true);
201
+ substituted += counts.substituted;
202
+ captured += counts.captured;
123
203
  }
124
- await rm(finalDir, { recursive: true, force: true });
125
- await rename(tempDir, finalDir);
204
+ if (substituted > 0) {
205
+ logger.warn(
206
+ `Blob copy into ${destDir} substituted ${substituted} of ${substituted + captured} blob ` +
207
+ `file(s) with PENDING or ERROR markers because they were not capturable whole.`
208
+ );
209
+ }
210
+ await rm(destDir, { recursive: true, force: true });
211
+ await rename(tempDir, destDir);
126
212
  } catch (error) {
127
213
  await rm(tempDir, { recursive: true, force: true }).catch(() => {});
128
214
  throw error;
129
215
  }
216
+ }
217
+
218
+ /**
219
+ * Snapshot a database's blob roots into a backup's blob directory. Overwrites any pre-existing
220
+ * snapshot for the same id (create_backup always produces a fresh id, so this only matters on a
221
+ * retried offline run).
222
+ */
223
+ export async function snapshotBlobs(backupDir: string, backupId: number, blobRoots: string[]): Promise<void> {
224
+ await copyBlobRootsByIndex(blobSnapshotDir(backupDir, backupId), blobRoots);
130
225
  await writeBlobsReadme(backupDir, blobRoots);
131
226
  }
132
227
 
133
228
  /**
134
229
  * Build the `blobs/README.md` documenting the blob snapshot layout, so an operator inspecting or
135
- * hand-recovering a backup can decode the numeric directories. Two variants:
136
- * - managed (default): a create_backup repository, where snapshots are keyed by backup id
230
+ * hand-recovering a backup can decode the numeric directories. Three variants:
231
+ * - `managed` (default): a create_backup repository, where snapshots are keyed by backup id
137
232
  * (`<backupId>/<rootIndex>/…`) and restore is automatic via `restore_backup`.
138
- * - archive (`archive: true`): a downloaded `get_backup` tar, which holds a single snapshot with no
139
- * backup-id level (`<rootIndex>/…`) and is restored by extracting the files back into the roots.
233
+ * - `archive`: a downloaded `get_backup` tar, which holds a single snapshot with no backup-id level
234
+ * (`<rootIndex>/…`) and is restored by extracting the files back into the roots.
235
+ * - `copy`: the companion directory `copy-db` writes beside a database copy, restored by hand.
140
236
  */
141
- export function blobsReadmeContent(blobRoots: string[], { archive = false }: { archive?: boolean } = {}): string {
237
+ export function blobsReadmeContent(
238
+ blobRoots: string[],
239
+ { variant = 'managed' }: { variant?: 'managed' | 'archive' | 'copy' } = {}
240
+ ): string {
142
241
  const rootMapping =
143
242
  blobRoots.length > 0 ? blobRoots.map((root, index) => ` ${index} -> ${root}`).join('\n') : ' (none)';
144
- const layout = archive
145
- ? '<rootIndex>/<shard1>/<shard2>/<fileId>'
146
- : '<backupId>/<rootIndex>/<shard1>/<shard2>/<fileId>';
147
- const intro = archive
148
- ? `This directory holds this database's file-backed blobs within a downloaded \`get_backup\` archive.
243
+ const layout =
244
+ variant === 'managed'
245
+ ? '<backupId>/<rootIndex>/<shard1>/<shard2>/<fileId>'
246
+ : '<rootIndex>/<shard1>/<shard2>/<fileId>';
247
+ const intro =
248
+ variant === 'copy'
249
+ ? `This directory holds the file-backed blobs of a \`copy-db\` database copy; the database file itself
250
+ is the sibling \`.mdb\` this directory is named after. Blobs are addressed by database NAME and the
251
+ configured blob roots — never by the database file's path — so a copy is only restorable with these
252
+ files: put each \`<rootIndex>/\` tree into the matching blob root of whatever database name you
253
+ restore the copy as (mapping below).`
254
+ : variant === 'archive'
255
+ ? `This directory holds this database's file-backed blobs within a downloaded \`get_backup\` archive.
149
256
  To restore them, extract each \`<rootIndex>/\` tree back into the matching blob root (see the mapping
150
257
  below and ../README.md).`
151
- : `This directory holds point-in-time snapshots of this database's file-backed blobs, captured
258
+ : `This directory holds point-in-time snapshots of this database's file-backed blobs, captured
152
259
  alongside each RocksDB managed backup. You do not restore these by hand — \`restore_backup\` puts
153
260
  them back automatically (see ../README.md); this file just documents the layout.`;
154
- const backupIdBullet = archive
155
- ? ''
156
- : `- **<backupId>** matches the RocksDB backup id (\`harper list_backups\`). Each id is a full,
261
+ const backupIdBullet =
262
+ variant === 'managed'
263
+ ? `- **<backupId>** matches the RocksDB backup id (\`harper list_backups\`). Each id is a full,
157
264
  independent snapshot (not incremental).
158
- `;
265
+ `
266
+ : '';
159
267
  return `# Harper blob snapshots
160
268
 
161
269
  ${intro}
@@ -166,7 +274,7 @@ ${intro}
166
274
 
167
275
  ${backupIdBullet}- **<rootIndex>** is which of the database's blob roots the file came from — the index into
168
276
  \`storage.blobPaths[n]\`. When \`storage.blobPaths\` is not configured there is a single default root
169
- (\`<rootPath>/blobs/<db>\`) at index 0. Current mapping for this backup:
277
+ (\`<rootPath>/blobs/<db>\`) at index 0. Current mapping:
170
278
 
171
279
  ${rootMapping}
172
280
 
@@ -175,8 +283,10 @@ ${rootMapping}
175
283
  4096 entries per directory). E.g. a blob with id \`0x12345678\` lives at \`12/345/678\`; a short id
176
284
  like \`0xc1a\` lives at \`0/0/c1a\`.
177
285
 
178
- Files are hard links to the live blobs when the backup is on the same filesystem, and copies
179
- otherwise.
286
+ Complete blobs are hard links to the live blobs when the destination is on the same filesystem, and
287
+ copies otherwise. A blob that was not capturable whole is stored as a marker instead: header type
288
+ \`0xfe\` is retryable (PENDING), while \`0xff\` is terminal (ERROR). The marker preserves the file id
289
+ but does not contain the original blob bytes.
180
290
  `;
181
291
  }
182
292
 
@@ -110,7 +110,12 @@ export async function deleteAuditLogsBefore(deleteObj: any) {
110
110
  harperLogger.info(`Finished deleting audit logs before ${deleteObj.timestamp}`);
111
111
 
112
112
  // `deleteTransactionLogsBefore` returns `entries_deleted`; the legacy result exposes it as `transactions_deleted`.
113
- return new DeleteAuditLogsBeforeResults(results.start_timestamp, results.end_timestamp, results.entries_deleted);
113
+ return new DeleteAuditLogsBeforeResults(
114
+ results.start_timestamp,
115
+ results.end_timestamp,
116
+ results.entries_deleted,
117
+ results.log_files_deleted
118
+ );
114
119
  }
115
120
 
116
121
  /**