@harperfast/harper 5.2.0 → 5.2.2

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 (308) hide show
  1. package/bin/copyDb.ts +40 -11
  2. package/bin/harper.ts +20 -51
  3. package/bin/help.ts +216 -0
  4. package/components/Application.ts +236 -46
  5. package/components/ApplicationScope.ts +26 -0
  6. package/components/EntryHandler.ts +410 -105
  7. package/components/RuntimeModuleTracker.ts +189 -0
  8. package/components/Scope.ts +68 -24
  9. package/components/componentLoader.ts +70 -16
  10. package/components/deployLifecycle.ts +119 -33
  11. package/components/deploymentRecorder.ts +102 -11
  12. package/components/mcp/tools/operations.ts +6 -0
  13. package/components/operations.js +8 -6
  14. package/config/configUtils.ts +78 -20
  15. package/config/harperConfigEnvVars.ts +114 -17
  16. package/config-root.schema.json +10 -0
  17. package/dataLayer/harperBridge/ResourceBridge.ts +26 -5
  18. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
  19. package/dataLayer/hdbInfoController.ts +8 -0
  20. package/dataLayer/schemaDescribe.ts +2 -1
  21. package/dist/bin/copyDb.js +33 -9
  22. package/dist/bin/copyDb.js.map +1 -1
  23. package/dist/bin/harper.d.ts +6 -0
  24. package/dist/bin/harper.js +18 -50
  25. package/dist/bin/harper.js.map +1 -1
  26. package/dist/bin/help.d.ts +8 -0
  27. package/dist/bin/help.js +192 -0
  28. package/dist/bin/help.js.map +1 -0
  29. package/dist/components/Application.d.ts +16 -1
  30. package/dist/components/Application.js +210 -38
  31. package/dist/components/Application.js.map +1 -1
  32. package/dist/components/ApplicationScope.d.ts +7 -0
  33. package/dist/components/ApplicationScope.js +22 -0
  34. package/dist/components/ApplicationScope.js.map +1 -1
  35. package/dist/components/EntryHandler.d.ts +4 -4
  36. package/dist/components/EntryHandler.js +386 -95
  37. package/dist/components/EntryHandler.js.map +1 -1
  38. package/dist/components/RuntimeModuleTracker.d.ts +11 -0
  39. package/dist/components/RuntimeModuleTracker.js +189 -0
  40. package/dist/components/RuntimeModuleTracker.js.map +1 -0
  41. package/dist/components/Scope.d.ts +1 -0
  42. package/dist/components/Scope.js +69 -22
  43. package/dist/components/Scope.js.map +1 -1
  44. package/dist/components/componentLoader.js +69 -10
  45. package/dist/components/componentLoader.js.map +1 -1
  46. package/dist/components/deployLifecycle.d.ts +6 -2
  47. package/dist/components/deployLifecycle.js +109 -31
  48. package/dist/components/deployLifecycle.js.map +1 -1
  49. package/dist/components/deploymentRecorder.d.ts +6 -0
  50. package/dist/components/deploymentRecorder.js +94 -12
  51. package/dist/components/deploymentRecorder.js.map +1 -1
  52. package/dist/components/mcp/tools/operations.js +6 -0
  53. package/dist/components/mcp/tools/operations.js.map +1 -1
  54. package/dist/components/operations.js +8 -6
  55. package/dist/components/operations.js.map +1 -1
  56. package/dist/config/configUtils.d.ts +6 -0
  57. package/dist/config/configUtils.js +78 -18
  58. package/dist/config/configUtils.js.map +1 -1
  59. package/dist/config/harperConfigEnvVars.js +108 -16
  60. package/dist/config/harperConfigEnvVars.js.map +1 -1
  61. package/dist/dataLayer/harperBridge/ResourceBridge.js +12 -5
  62. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  63. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
  64. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  65. package/dist/dataLayer/hdbInfoController.js +4 -0
  66. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  67. package/dist/dataLayer/schemaDescribe.js +2 -1
  68. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  69. package/dist/resources/DatabaseTransaction.d.ts +57 -0
  70. package/dist/resources/DatabaseTransaction.js +284 -104
  71. package/dist/resources/DatabaseTransaction.js.map +1 -1
  72. package/dist/resources/ResourceInterface.d.ts +8 -2
  73. package/dist/resources/ResourceInterface.js.map +1 -1
  74. package/dist/resources/Resources.js +22 -4
  75. package/dist/resources/Resources.js.map +1 -1
  76. package/dist/resources/Table.d.ts +8 -6
  77. package/dist/resources/Table.js +158 -31
  78. package/dist/resources/Table.js.map +1 -1
  79. package/dist/resources/analytics/write.js +6 -6
  80. package/dist/resources/analytics/write.js.map +1 -1
  81. package/dist/resources/auditStore.js +27 -1
  82. package/dist/resources/auditStore.js.map +1 -1
  83. package/dist/resources/blob.d.ts +0 -1
  84. package/dist/resources/blob.js +15 -7
  85. package/dist/resources/blob.js.map +1 -1
  86. package/dist/resources/databases.d.ts +22 -5
  87. package/dist/resources/databases.js +118 -9
  88. package/dist/resources/databases.js.map +1 -1
  89. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +15 -2
  90. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +128 -39
  91. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  92. package/dist/resources/jsResource.d.ts +4 -26
  93. package/dist/resources/jsResource.js +5 -59
  94. package/dist/resources/jsResource.js.map +1 -1
  95. package/dist/resources/models/Models.d.ts +11 -1
  96. package/dist/resources/models/Models.js +10 -1
  97. package/dist/resources/models/Models.js.map +1 -1
  98. package/dist/resources/models/backendRegistry.d.ts +9 -0
  99. package/dist/resources/models/backendRegistry.js +10 -0
  100. package/dist/resources/models/backendRegistry.js.map +1 -1
  101. package/dist/resources/models/openaiStream.d.ts +16 -1
  102. package/dist/resources/models/openaiStream.js +113 -21
  103. package/dist/resources/models/openaiStream.js.map +1 -1
  104. package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
  105. package/dist/resources/models/v1/chatCompletions.js +115 -0
  106. package/dist/resources/models/v1/chatCompletions.js.map +1 -0
  107. package/dist/resources/models/v1/embeddings.d.ts +11 -0
  108. package/dist/resources/models/v1/embeddings.js +71 -0
  109. package/dist/resources/models/v1/embeddings.js.map +1 -0
  110. package/dist/resources/models/v1/errors.d.ts +54 -0
  111. package/dist/resources/models/v1/errors.js +130 -0
  112. package/dist/resources/models/v1/errors.js.map +1 -0
  113. package/dist/resources/models/v1/index.d.ts +36 -0
  114. package/dist/resources/models/v1/index.js +75 -0
  115. package/dist/resources/models/v1/index.js.map +1 -0
  116. package/dist/resources/models/v1/models.d.ts +26 -0
  117. package/dist/resources/models/v1/models.js +44 -0
  118. package/dist/resources/models/v1/models.js.map +1 -0
  119. package/dist/resources/models/v1/translation.d.ts +133 -0
  120. package/dist/resources/models/v1/translation.js +298 -0
  121. package/dist/resources/models/v1/translation.js.map +1 -0
  122. package/dist/resources/roles.d.ts +1 -1
  123. package/dist/resources/roles.js +54 -7
  124. package/dist/resources/roles.js.map +1 -1
  125. package/dist/resources/search.d.ts +1 -1
  126. package/dist/resources/search.js +7 -3
  127. package/dist/resources/search.js.map +1 -1
  128. package/dist/security/jsLoader.js +84 -33
  129. package/dist/security/jsLoader.js.map +1 -1
  130. package/dist/security/role.js +4 -0
  131. package/dist/security/role.js.map +1 -1
  132. package/dist/security/superUserGuard.d.ts +7 -0
  133. package/dist/security/superUserGuard.js +23 -0
  134. package/dist/security/superUserGuard.js.map +1 -0
  135. package/dist/security/tokenAuthentication.d.ts +0 -1
  136. package/dist/security/tokenAuthentication.js +6 -2
  137. package/dist/security/tokenAuthentication.js.map +1 -1
  138. package/dist/security/user.d.ts +6 -1
  139. package/dist/security/user.js +23 -1
  140. package/dist/security/user.js.map +1 -1
  141. package/dist/server/http.d.ts +23 -2
  142. package/dist/server/http.js +99 -12
  143. package/dist/server/http.js.map +1 -1
  144. package/dist/server/operationsServer.d.ts +0 -2
  145. package/dist/server/operationsServer.js.map +1 -1
  146. package/dist/server/serverHelpers/multipartParser.js +9 -0
  147. package/dist/server/serverHelpers/multipartParser.js.map +1 -1
  148. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
  149. package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
  150. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
  151. package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
  152. package/dist/server/serverHelpers/registeredOperations.js +14 -13
  153. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  154. package/dist/server/serverHelpers/serverHandlers.js +19 -3
  155. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  156. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
  157. package/dist/server/serverHelpers/serverUtilities.js +10 -7
  158. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  159. package/dist/server/static.js +57 -18
  160. package/dist/server/static.js.map +1 -1
  161. package/dist/server/storageReclamation.d.ts +17 -0
  162. package/dist/server/storageReclamation.js +90 -6
  163. package/dist/server/storageReclamation.js.map +1 -1
  164. package/dist/server/threads/manageThreads.js +8 -0
  165. package/dist/server/threads/manageThreads.js.map +1 -1
  166. package/dist/server/threads/socketRouter.js +20 -0
  167. package/dist/server/threads/socketRouter.js.map +1 -1
  168. package/dist/server/threads/threadServer.js +12 -0
  169. package/dist/server/threads/threadServer.js.map +1 -1
  170. package/dist/sqlEngine/diff/differential.js +7 -3
  171. package/dist/sqlEngine/diff/differential.js.map +1 -1
  172. package/dist/sqlEngine/parser/normalizer.js +37 -9
  173. package/dist/sqlEngine/parser/normalizer.js.map +1 -1
  174. package/dist/sqlTranslator/index.js +6 -1
  175. package/dist/sqlTranslator/index.js.map +1 -1
  176. package/dist/upgrade/upgradePrompt.d.ts +2 -2
  177. package/dist/upgrade/upgradePrompt.js +22 -3
  178. package/dist/upgrade/upgradePrompt.js.map +1 -1
  179. package/dist/utility/environment/environmentManager.d.ts +11 -0
  180. package/dist/utility/environment/environmentManager.js +154 -0
  181. package/dist/utility/environment/environmentManager.js.map +1 -1
  182. package/dist/utility/errors/commonErrors.d.ts +1 -0
  183. package/dist/utility/errors/commonErrors.js +1 -0
  184. package/dist/utility/errors/commonErrors.js.map +1 -1
  185. package/dist/utility/hdbTerms.d.ts +2 -0
  186. package/dist/utility/hdbTerms.js +2 -0
  187. package/dist/utility/hdbTerms.js.map +1 -1
  188. package/dist/utility/install/installer.js +32 -0
  189. package/dist/utility/install/installer.js.map +1 -1
  190. package/dist/utility/lmdb/environmentUtility.js +2 -1
  191. package/dist/utility/lmdb/environmentUtility.js.map +1 -1
  192. package/dist/utility/logging/harper_logger.d.ts +7 -0
  193. package/dist/utility/logging/harper_logger.js +57 -24
  194. package/dist/utility/logging/harper_logger.js.map +1 -1
  195. package/npm-shrinkwrap.json +208 -198
  196. package/package.json +10 -8
  197. package/resources/DESIGN.md +1 -0
  198. package/resources/DatabaseTransaction.ts +313 -102
  199. package/resources/ResourceInterface.ts +8 -2
  200. package/resources/Resources.ts +22 -4
  201. package/resources/Table.ts +398 -259
  202. package/resources/analytics/write.ts +22 -20
  203. package/resources/auditStore.ts +32 -1
  204. package/resources/blob.ts +15 -8
  205. package/resources/databases.ts +123 -12
  206. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +119 -45
  207. package/resources/jsResource.ts +5 -62
  208. package/resources/models/Models.ts +14 -1
  209. package/resources/models/backendRegistry.ts +10 -0
  210. package/resources/models/openaiStream.ts +131 -19
  211. package/resources/models/v1/chatCompletions.ts +128 -0
  212. package/resources/models/v1/embeddings.ts +70 -0
  213. package/resources/models/v1/errors.ts +141 -0
  214. package/resources/models/v1/index.ts +72 -0
  215. package/resources/models/v1/models.ts +53 -0
  216. package/resources/models/v1/translation.ts +362 -0
  217. package/resources/roles.ts +67 -7
  218. package/resources/search.ts +8 -3
  219. package/security/jsLoader.ts +84 -30
  220. package/security/role.ts +7 -0
  221. package/security/superUserGuard.ts +20 -0
  222. package/security/tokenAuthentication.ts +6 -3
  223. package/security/user.ts +26 -1
  224. package/server/DESIGN.md +45 -34
  225. package/server/http.ts +100 -13
  226. package/server/operationsServer.ts +0 -2
  227. package/server/serverHelpers/multipartParser.ts +9 -0
  228. package/server/serverHelpers/operationAuthorizationState.ts +11 -0
  229. package/server/serverHelpers/registeredOperations.ts +19 -15
  230. package/server/serverHelpers/serverHandlers.js +20 -3
  231. package/server/serverHelpers/serverUtilities.ts +10 -7
  232. package/server/static.ts +75 -20
  233. package/server/storageReclamation.ts +104 -8
  234. package/server/threads/manageThreads.js +8 -0
  235. package/server/threads/socketRouter.ts +20 -0
  236. package/server/threads/threadServer.js +11 -0
  237. package/sqlTranslator/index.ts +6 -1
  238. package/static/defaultConfig.yaml +2 -0
  239. package/studio/web/assets/{Chat-DoVWScmq.js → Chat-BMUjPklt.js} +5 -5
  240. package/studio/web/assets/{Chat-DoVWScmq.js.map → Chat-BMUjPklt.js.map} +1 -1
  241. package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js → FloatingChat-Cbghuxe5.js} +5 -5
  242. package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js.map → FloatingChat-Cbghuxe5.js.map} +1 -1
  243. package/studio/web/assets/{apiToken-BUI_04o7.js → apiToken-CkgTQAqc.js} +2 -2
  244. package/studio/web/assets/{apiToken-BUI_04o7.js.map → apiToken-CkgTQAqc.js.map} +1 -1
  245. package/studio/web/assets/{applications-D03NA7wW.js → applications-WvF4NOTN.js} +13 -13
  246. package/studio/web/assets/applications-WvF4NOTN.js.map +1 -0
  247. package/studio/web/assets/chevron-right-anDJCGlp.js +2 -0
  248. package/studio/web/assets/chevron-right-anDJCGlp.js.map +1 -0
  249. package/studio/web/assets/index-CDeqVx71.css +1 -0
  250. package/studio/web/assets/index-Sevt3MVx.js +819 -0
  251. package/studio/web/assets/index-Sevt3MVx.js.map +1 -0
  252. package/studio/web/assets/index.lazy-Dg5YYPGg.js +14 -0
  253. package/studio/web/assets/{index.lazy-Dx3MpyDC.js.map → index.lazy-Dg5YYPGg.js.map} +1 -1
  254. package/studio/web/assets/{jsonMode-Cjj7dtlr.js → jsonMode-CBSEleE7.js} +2 -2
  255. package/studio/web/assets/{jsonMode-Cjj7dtlr.js.map → jsonMode-CBSEleE7.js.map} +1 -1
  256. package/studio/web/assets/languageServices-gxedeeTo.js +2 -0
  257. package/studio/web/assets/languageServices-gxedeeTo.js.map +1 -0
  258. package/studio/web/assets/{notifications-0edoFTsb.js → notifications-Ap84YyQp.js} +2 -2
  259. package/studio/web/assets/{notifications-0edoFTsb.js.map → notifications-Ap84YyQp.js.map} +1 -1
  260. package/studio/web/assets/notifications-vehqyOLM.js +2 -0
  261. package/studio/web/assets/{notifications-CwKhipK7.js.map → notifications-vehqyOLM.js.map} +1 -1
  262. package/studio/web/assets/{pollUnlessForbidden-HHdQZW1N.js → pollUnlessForbidden-BhzefVRn.js} +2 -2
  263. package/studio/web/assets/pollUnlessForbidden-BhzefVRn.js.map +1 -0
  264. package/studio/web/assets/{profile-DUfEPQtx.js → profile-DzDpRLHY.js} +2 -2
  265. package/studio/web/assets/{profile-DUfEPQtx.js.map → profile-DzDpRLHY.js.map} +1 -1
  266. package/studio/web/assets/queryClient-CgtkoiCY.js +2 -0
  267. package/studio/web/assets/queryClient-CgtkoiCY.js.map +1 -0
  268. package/studio/web/assets/regions-DpJ8s7iN.js +2 -0
  269. package/studio/web/assets/regions-DpJ8s7iN.js.map +1 -0
  270. package/studio/web/assets/register-BDy01-q3.js +3 -0
  271. package/studio/web/assets/register-BDy01-q3.js.map +1 -0
  272. package/studio/web/assets/register-CQHRwNP4.js +3 -0
  273. package/studio/web/assets/register-CQHRwNP4.js.map +1 -0
  274. package/studio/web/assets/{setComponentFile-DMPo4UjC.js → setComponentFile-D0aPcI3W.js} +2 -2
  275. package/studio/web/assets/{setComponentFile-DMPo4UjC.js.map → setComponentFile-D0aPcI3W.js.map} +1 -1
  276. package/studio/web/assets/setup-I15FAF9H.js +3 -0
  277. package/studio/web/assets/{setup-B56Oz1_u.js.map → setup-I15FAF9H.js.map} +1 -1
  278. package/studio/web/assets/status-BF1eJshJ.js +62 -0
  279. package/studio/web/assets/status-BF1eJshJ.js.map +1 -0
  280. package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js → swagger-ui-react-DwSPXXhl.js} +2 -2
  281. package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js.map → swagger-ui-react-DwSPXXhl.js.map} +1 -1
  282. package/studio/web/assets/{textarea-DhLMZ3PA.js → table-DpOjNQHt.js} +2 -10
  283. package/studio/web/assets/table-DpOjNQHt.js.map +1 -0
  284. package/studio/web/assets/{tsMode-CrHCRjTK.js → tsMode-Bu51A-VG.js} +2 -2
  285. package/studio/web/assets/{tsMode-CrHCRjTK.js.map → tsMode-Bu51A-VG.js.map} +1 -1
  286. package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js → useEntityRestURL-DrV1zMrN.js} +2 -2
  287. package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js.map → useEntityRestURL-DrV1zMrN.js.map} +1 -1
  288. package/studio/web/index.html +6 -7
  289. package/upgrade/upgradePrompt.ts +22 -3
  290. package/utility/environment/environmentManager.ts +152 -0
  291. package/utility/errors/commonErrors.ts +2 -0
  292. package/utility/hdbTerms.ts +2 -0
  293. package/utility/install/installer.ts +37 -0
  294. package/utility/lmdb/environmentUtility.ts +2 -1
  295. package/utility/logging/harper_logger.ts +57 -26
  296. package/studio/web/assets/applications-D03NA7wW.js.map +0 -1
  297. package/studio/web/assets/index-Bh_CNAHr.js +0 -809
  298. package/studio/web/assets/index-Bh_CNAHr.js.map +0 -1
  299. package/studio/web/assets/index-C8Mv9kHV.css +0 -1
  300. package/studio/web/assets/index.lazy-Dx3MpyDC.js +0 -14
  301. package/studio/web/assets/notifications-CwKhipK7.js +0 -2
  302. package/studio/web/assets/pollUnlessForbidden-HHdQZW1N.js.map +0 -1
  303. package/studio/web/assets/queryClient-CbA8wM7J.js +0 -2
  304. package/studio/web/assets/queryClient-CbA8wM7J.js.map +0 -1
  305. package/studio/web/assets/setup-B56Oz1_u.js +0 -3
  306. package/studio/web/assets/status-BAod7p3o.js +0 -62
  307. package/studio/web/assets/status-BAod7p3o.js.map +0 -1
  308. package/studio/web/assets/textarea-DhLMZ3PA.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile, realpath } from 'node:fs/promises';
2
2
  import { statfs } from 'node:fs/promises';
3
- import { join } from 'node:path';
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
4
  import { getWorkerIndex, getWorkerCount } from '../server/threads/manageThreads.js';
5
5
  import { logger } from '../utility/logging/logger.ts';
6
6
  import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
@@ -27,6 +27,56 @@ export type QuotaStatusData = {
27
27
  updatedAt: number;
28
28
  };
29
29
 
30
+ /**
31
+ * Resolves `target` to its real (symlink-free) path. `target` itself may not exist yet (e.g. a
32
+ * table's storage directory that hasn't been created), so walk up to the nearest existing
33
+ * ancestor, resolve that, and rejoin the not-yet-existing remainder lexically — this still
34
+ * catches a symlinked *parent* directory, which is the case that actually matters for quota
35
+ * scoping (leaf directories are created by Harper itself, not symlinked).
36
+ */
37
+ async function resolveRealPath(target: string): Promise<string> {
38
+ try {
39
+ return await realpath(target);
40
+ } catch {
41
+ const parent = dirname(target);
42
+ if (parent === target) return resolve(target);
43
+ return join(await resolveRealPath(parent), basename(target));
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Whether `target` resolves to a path inside `root`. quota-status.json is scoped to a single
49
+ * ROOTPATH; a database or table configured with its own STORAGE_PATH (or a STORAGE_BLOBPATHS
50
+ * entry) can live on an entirely different volume, and must not be reported against the root's
51
+ * quota. Compares real (symlink-resolved) paths, not just lexically-normalized ones — a `..`-free
52
+ * path can still be a symlink into another volume, and `resolve()` alone wouldn't catch that.
53
+ */
54
+ async function isPathWithinRoot(root: string, target: string): Promise<boolean> {
55
+ const [resolvedRoot, resolvedTarget] = await Promise.all([resolveRealPath(root), resolveRealPath(target)]);
56
+ const relativePath = relative(resolvedRoot, resolvedTarget);
57
+ if (relativePath === '') return true;
58
+ if (isAbsolute(relativePath)) return false;
59
+ // Check the first segment specifically (`..`), not a `..`-prefix match — a child directory
60
+ // literally named e.g. `..data` (the Kubernetes ConfigMap/Secret atomic-update symlink
61
+ // convention) is not a parent-traversal and must not be rejected as escaping the root.
62
+ const firstSegment = relativePath.split(sep)[0];
63
+ return firstSegment !== '..';
64
+ }
65
+
66
+ /**
67
+ * Guards against a syntactically valid but semantically broken quota-status.json (missing or
68
+ * negative usage, non-positive quota, non-finite timestamp) being read as "100% free" or worse.
69
+ */
70
+ function isValidQuotaStatus(status: QuotaStatusData): boolean {
71
+ return (
72
+ Number.isFinite(status.updatedAt) &&
73
+ Number.isFinite(status.quotaBytes) &&
74
+ status.quotaBytes > 0 &&
75
+ Number.isFinite(status.usedBytes) &&
76
+ status.usedBytes >= 0
77
+ );
78
+ }
79
+
30
80
  /**
31
81
  * Reads the quota-status.json file written by host-manager.
32
82
  * Returns undefined if the file is absent or malformed; does not apply age filtering.
@@ -70,15 +120,61 @@ export function onStorageReclamation(
70
120
  }
71
121
  let reclamationTimer: NodeJS.Timeout;
72
122
 
73
- // If a fresh quota-status.json exists (written by host-manager every ~90s), use quota-based ratio.
74
- // Otherwise fall back to statfs for the registered path.
75
- const defaultGetAvailableSpaceRatio = async (path: string): Promise<number> => {
123
+ export type StorageSpaceStats = {
124
+ available: number;
125
+ free: number;
126
+ size: number;
127
+ // Whether these numbers reflect a quota (accurate for what Harper can actually write) or
128
+ // raw filesystem-level statfs (can be wildly wrong under a quota-limited data directory).
129
+ basis: 'quota' | 'filesystem';
130
+ };
131
+
132
+ /**
133
+ * Get available/free/size storage stats for a path, preferring a fresh quota-status.json (see
134
+ * getQuotaStatus) over statfs. statfs describes the filesystem, which is misleading whenever
135
+ * the data directory is quota-limited on a larger shared volume (#1976).
136
+ */
137
+ export async function getStorageSpaceStats(path: string): Promise<StorageSpaceStats> {
138
+ const rootPath = envMgr.get(CONFIG_PARAMS.ROOTPATH);
76
139
  const status = await getQuotaStatus();
77
- if (status?.quotaBytes && Date.now() - status.updatedAt < QUOTA_STATUS_MAX_AGE_MS) {
78
- return Math.max(0, status.quotaBytes - status.usedBytes) / status.quotaBytes;
140
+ const statusAge = status ? Date.now() - status.updatedAt : undefined;
141
+ if (
142
+ status &&
143
+ isValidQuotaStatus(status) &&
144
+ rootPath &&
145
+ // A negative age (clock skew, or a corrupt/future updatedAt) is not "fresh" — treat it the
146
+ // same as stale rather than trusting quota data indefinitely.
147
+ statusAge >= 0 &&
148
+ statusAge < QUOTA_STATUS_MAX_AGE_MS &&
149
+ // Checked last: it's the only async/IO-bound condition, so short-circuit it behind the
150
+ // cheap synchronous checks above.
151
+ (await isPathWithinRoot(rootPath, path))
152
+ ) {
153
+ const available = Math.max(0, status.quotaBytes - status.usedBytes);
154
+ return { available, free: available, size: status.quotaBytes, basis: 'quota' };
79
155
  }
80
156
  const fsStats = await statfs(path);
81
- return fsStats.bavail / fsStats.blocks;
157
+ return {
158
+ available: fsStats.bavail * fsStats.bsize,
159
+ free: fsStats.bfree * fsStats.bsize,
160
+ size: fsStats.blocks * fsStats.bsize,
161
+ basis: 'filesystem',
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Ratio of available to total size, guarding the 0/0 = NaN case (a zero-size statfs/quota result,
167
+ * e.g. an empty or misconfigured mount) so a full disk still reports a ratio of 0 rather than NaN.
168
+ */
169
+ export function computeAvailableRatio(available: number, size: number): number {
170
+ return size > 0 ? available / size : 0;
171
+ }
172
+
173
+ // If a fresh quota-status.json exists (written by host-manager every ~90s), use quota-based ratio.
174
+ // Otherwise fall back to statfs for the registered path.
175
+ const defaultGetAvailableSpaceRatio = async (path: string): Promise<number> => {
176
+ const { available, size } = await getStorageSpaceStats(path);
177
+ return computeAvailableRatio(available, size);
82
178
  };
83
179
  let getAvailableSpaceRatio: (path: string) => Promise<number> = defaultGetAvailableSpaceRatio;
84
180
 
@@ -232,6 +232,14 @@ function registerWorkerDataProvider(name, provider) {
232
232
  if (workerDataProviders.get(name) === provider) workerDataProviders.delete(name);
233
233
  };
234
234
  }
235
+ // Propagate this thread's in-process config overrides (env.setProperty — installer bootstrap or
236
+ // the unit-test harness's per-run isolation) to every worker spawned from here, so a worker's
237
+ // effective config is never silently whatever happens to be installed on disk. Worker-side replay
238
+ // is environmentManager.ts's applyInheritedConfigOverrides(), invoked from initSync(). Registered
239
+ // on every thread (not just main) so a nested worker-of-a-worker also inherits the full chain.
240
+ // setProperty() clones each value as it records it, so this provider cannot hit the log-and-skip
241
+ // path below — which for this one would mean spawning the worker on the on-disk config.
242
+ registerWorkerDataProvider('configOverrides', () => envMgr.getConfigOverrides());
235
243
  function collectProvidedWorkerData(options) {
236
244
  if (workerDataProviders.size === 0) return undefined;
237
245
  let provided;
@@ -8,6 +8,12 @@ import { join } from 'path';
8
8
 
9
9
  const workers = [];
10
10
  const workersReady = [];
11
+ // startHTTPThreads() can be called more than once in-process (e.g. a test harness adding more
12
+ // worker threads to an already-running server via addThreads()). The crash-path sweep below must
13
+ // run only on the very first call — a later call happens after real mirrors are already bound, and
14
+ // sweeping the sockets directory then would delete their live files, reintroducing the exact outage
15
+ // this guards against.
16
+ let sweptSocketsDirectory = false;
11
17
 
12
18
  if (isMainThread) {
13
19
  process.on('uncaughtException', (error) => {
@@ -23,6 +29,20 @@ if (isMainThread) {
23
29
  }
24
30
 
25
31
  export async function startHTTPThreads(threadCount = 2, dynamicThreads?: boolean) {
32
+ // Crash-path defense: a hard crash can skip a worker's exit-time UDS cleanup and leave stale
33
+ // mirror files behind. This runs before any worker below can start (and thus before any mirror
34
+ // can bind), so it can only ever clear files nothing is using yet — never a live mirror. The
35
+ // inode ownership guard in cleanupUdsFiles()/markUdsBindFailed() (see http.ts) is the matching
36
+ // defense for the in-process rolling-restart case, which this sweep does not cover.
37
+ // Lazy dynamic import (not a top-level one), matching server/status/index.ts's own lazy import of
38
+ // http.ts: http.ts's module graph reaches security/auth.ts, whose module-scope table() call needs
39
+ // config already initialized — pulling that graph in at this file's own top level (which
40
+ // bin/run.ts imports before parsing argv / initializing config) breaks startup with "Unable to
41
+ // determine database storage path" before main() ever runs.
42
+ if (isMainThread && !sweptSocketsDirectory) {
43
+ sweptSocketsDirectory = true;
44
+ (await import('../http.ts')).cleanupSocketsDirectory();
45
+ }
26
46
  recordHostname().catch((err) => harperLogger.error?.('Error recording hostname for analytics:', err));
27
47
  // Drive transaction-log cooling from the main thread (the registry is a
28
48
  // process-global singleton; see startTransactionLogCooling). Runs for all
@@ -275,6 +275,11 @@ function listenOnDomainSocket(port, server) {
275
275
  }
276
276
  function onListening() {
277
277
  server.removeListener('error', onError);
278
+ // Record ownership of the inode we just bound, so cleanupUdsFiles()/markUdsBindFailed()
279
+ // (see http.ts) can tell this worker's own file apart from a replacement's later rebind
280
+ // at the same path (see registerUdsCleanupPaths). A no-op for domain sockets that aren't
281
+ // UDS mirrors (e.g. the operations API's primary socket), which were never registered.
282
+ httpComponent.recordUdsBindSuccess(port);
278
283
  harperLogger.info('Domain socket listening on ' + port);
279
284
  resolve({ port, name: server.name, protocol_name: server.protocol_name });
280
285
  }
@@ -387,6 +392,9 @@ function listenOnPorts() {
387
392
  callback?.();
388
393
  },
389
394
  };
395
+ // createUwsServer() only resolves once uWS's own listen_unix() confirms the bind, so
396
+ // ownership is confirmed now (see http.ts's ownership-aware cleanupUdsFiles()).
397
+ if (cfg.socketPath) httpComponent.recordUdsBindSuccess(cfg.socketPath);
390
398
  harperLogger.info('uWS listening on ' + (cfg.socketPath ?? cfg.port));
391
399
  return { port: key };
392
400
  })
@@ -517,6 +525,9 @@ async function listenOnPortsBun() {
517
525
  });
518
526
  SERVERS[udsPath] = udsServer;
519
527
  httpComponent.registerUdsCleanupPaths(udsPath, yamlPath);
528
+ // Bun.serve() above already succeeded (a bind failure throws synchronously into the
529
+ // surrounding catch), so this worker's ownership of udsPath is confirmed now.
530
+ httpComponent.recordUdsBindSuccess(udsPath);
520
531
 
521
532
  const writeMetadata = () => httpComponent.writeUdsMetadata(yamlPath, port, config.pseudoServer);
522
533
  config.tlsSelector.ready.then(writeMetadata);
@@ -17,6 +17,7 @@ import * as terms from '../utility/hdbTerms.ts';
17
17
  import { handleHDBError } from '../utility/errors/hdbError.ts';
18
18
  import { HTTP_STATUS_CODES } from '../utility/errors/commonErrors.ts';
19
19
  import * as sqlEngineRouter from '../sqlEngine/router.ts';
20
+ import { isOperationAuthorizationBypassed } from '../server/serverHelpers/operationAuthorizationState.ts';
20
21
 
21
22
  //here we call to define and import custom functions to alasql
22
23
  alasqlFunctionImporter(alasql);
@@ -129,7 +130,11 @@ export function processAST(jsonMessage: any, parsedSqlObject: any, callback: any
129
130
  try {
130
131
  let sqlFunction = nullFunction;
131
132
 
132
- if (!jsonMessage.bypass_auth && !parsedSqlObject.permissions_checked) {
133
+ // Trusted bypass is dispatch/async-context state (set by chooseOperation's caller via
134
+ // runWithOperationAuthorizationBypass), never body state — jsonMessage.bypass_auth is
135
+ // caller-controlled and is stripped before operation code sees it (see
136
+ // server/serverHelpers/serverHandlers.js and components/mcp/tools/operations.ts).
137
+ if (!isOperationAuthorizationBypassed() && !parsedSqlObject.permissions_checked) {
133
138
  let permissionsCheck = checkASTPermissions(jsonMessage, parsedSqlObject);
134
139
  if (permissionsCheck && permissionsCheck.length > 0) {
135
140
  return callback(UNAUTHORIZED_RESPONSE, permissionsCheck);
@@ -72,6 +72,8 @@ storage:
72
72
  writeAsync: false
73
73
  caching: true
74
74
  compression: true
75
+ rocks:
76
+ compression: null
75
77
  noReadAhead: false
76
78
  path: null
77
79
  backupPath: null