harper 5.1.20 → 5.2.0-alpha.5

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 (395) hide show
  1. package/agent/operations.ts +13 -16
  2. package/components/Application.ts +188 -7
  3. package/components/ApplicationScope.ts +3 -0
  4. package/components/Scope.ts +12 -0
  5. package/components/anthropic/index.ts +9 -2
  6. package/components/bedrock/index.ts +25 -2
  7. package/components/componentLoader.ts +56 -7
  8. package/components/componentSecrets.ts +451 -0
  9. package/components/deploymentRecorder.ts +7 -1
  10. package/components/mcp/adapters/fastify.ts +3 -0
  11. package/components/mcp/adapters/harperHttp.ts +3 -0
  12. package/components/mcp/audit.ts +21 -6
  13. package/components/mcp/customResourceRegistry.ts +17 -38
  14. package/components/mcp/listChanged.ts +37 -20
  15. package/components/mcp/quota.ts +145 -0
  16. package/components/mcp/rateLimit.ts +152 -14
  17. package/components/mcp/resources.ts +2 -4
  18. package/components/mcp/toolRegistry.ts +73 -4
  19. package/components/mcp/tools/application.ts +77 -8
  20. package/components/mcp/tools/operations.ts +106 -36
  21. package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
  22. package/components/mcp/tools/schemas/operations.ts +5 -0
  23. package/components/mcp/transport.ts +55 -4
  24. package/components/ollama/index.ts +6 -2
  25. package/components/openai/index.ts +9 -2
  26. package/components/operations.js +204 -10
  27. package/components/operationsValidation.js +173 -0
  28. package/components/secretOperations.ts +498 -0
  29. package/config/configUtils.ts +25 -1
  30. package/config-app.schema.json +81 -0
  31. package/config-root.schema.json +8 -1
  32. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  33. package/dataLayer/readAuditLog.ts +14 -0
  34. package/dataLayer/schemaDescribe.ts +1 -0
  35. package/dist/agent/operations.d.ts +7 -4
  36. package/dist/agent/operations.js +13 -15
  37. package/dist/agent/operations.js.map +1 -1
  38. package/dist/components/Application.d.ts +25 -2
  39. package/dist/components/Application.js +166 -7
  40. package/dist/components/Application.js.map +1 -1
  41. package/dist/components/ApplicationScope.d.ts +2 -0
  42. package/dist/components/ApplicationScope.js +3 -0
  43. package/dist/components/ApplicationScope.js.map +1 -1
  44. package/dist/components/Scope.d.ts +8 -0
  45. package/dist/components/Scope.js +11 -0
  46. package/dist/components/Scope.js.map +1 -1
  47. package/dist/components/anthropic/index.d.ts +4 -1
  48. package/dist/components/anthropic/index.js +4 -2
  49. package/dist/components/anthropic/index.js.map +1 -1
  50. package/dist/components/bedrock/index.d.ts +4 -1
  51. package/dist/components/bedrock/index.js +22 -2
  52. package/dist/components/bedrock/index.js.map +1 -1
  53. package/dist/components/componentLoader.d.ts +2 -0
  54. package/dist/components/componentLoader.js +52 -10
  55. package/dist/components/componentLoader.js.map +1 -1
  56. package/dist/components/componentSecrets.d.ts +63 -0
  57. package/dist/components/componentSecrets.js +405 -0
  58. package/dist/components/componentSecrets.js.map +1 -0
  59. package/dist/components/deploymentRecorder.d.ts +3 -0
  60. package/dist/components/deploymentRecorder.js +4 -3
  61. package/dist/components/deploymentRecorder.js.map +1 -1
  62. package/dist/components/mcp/adapters/fastify.d.ts +2 -0
  63. package/dist/components/mcp/adapters/fastify.js +1 -0
  64. package/dist/components/mcp/adapters/fastify.js.map +1 -1
  65. package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
  66. package/dist/components/mcp/adapters/harperHttp.js +1 -0
  67. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  68. package/dist/components/mcp/audit.d.ts +2 -2
  69. package/dist/components/mcp/audit.js +20 -5
  70. package/dist/components/mcp/audit.js.map +1 -1
  71. package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
  72. package/dist/components/mcp/customResourceRegistry.js +15 -30
  73. package/dist/components/mcp/customResourceRegistry.js.map +1 -1
  74. package/dist/components/mcp/listChanged.d.ts +7 -2
  75. package/dist/components/mcp/listChanged.js +28 -0
  76. package/dist/components/mcp/listChanged.js.map +1 -1
  77. package/dist/components/mcp/quota.d.ts +31 -0
  78. package/dist/components/mcp/quota.js +155 -0
  79. package/dist/components/mcp/quota.js.map +1 -0
  80. package/dist/components/mcp/rateLimit.d.ts +19 -3
  81. package/dist/components/mcp/rateLimit.js +129 -13
  82. package/dist/components/mcp/rateLimit.js.map +1 -1
  83. package/dist/components/mcp/resources.js.map +1 -1
  84. package/dist/components/mcp/toolRegistry.d.ts +39 -1
  85. package/dist/components/mcp/toolRegistry.js +60 -5
  86. package/dist/components/mcp/toolRegistry.js.map +1 -1
  87. package/dist/components/mcp/tools/application.d.ts +8 -1
  88. package/dist/components/mcp/tools/application.js +65 -5
  89. package/dist/components/mcp/tools/application.js.map +1 -1
  90. package/dist/components/mcp/tools/operations.d.ts +15 -3
  91. package/dist/components/mcp/tools/operations.js +99 -38
  92. package/dist/components/mcp/tools/operations.js.map +1 -1
  93. package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
  94. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  95. package/dist/components/mcp/tools/schemas/operations.js +4 -0
  96. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  97. package/dist/components/mcp/transport.d.ts +6 -0
  98. package/dist/components/mcp/transport.js +47 -3
  99. package/dist/components/mcp/transport.js.map +1 -1
  100. package/dist/components/ollama/index.d.ts +4 -1
  101. package/dist/components/ollama/index.js +4 -2
  102. package/dist/components/ollama/index.js.map +1 -1
  103. package/dist/components/openai/index.d.ts +4 -1
  104. package/dist/components/openai/index.js +4 -2
  105. package/dist/components/openai/index.js.map +1 -1
  106. package/dist/components/operations.d.ts +39 -0
  107. package/dist/components/operations.js +194 -2
  108. package/dist/components/operations.js.map +1 -1
  109. package/dist/components/operationsValidation.d.ts +38 -0
  110. package/dist/components/operationsValidation.js +155 -0
  111. package/dist/components/operationsValidation.js.map +1 -1
  112. package/dist/components/secretOperations.d.ts +113 -0
  113. package/dist/components/secretOperations.js +475 -0
  114. package/dist/components/secretOperations.js.map +1 -0
  115. package/dist/config/configUtils.d.ts +4 -1
  116. package/dist/config/configUtils.js +18 -1
  117. package/dist/config/configUtils.js.map +1 -1
  118. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  119. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  120. package/dist/dataLayer/readAuditLog.js +11 -0
  121. package/dist/dataLayer/readAuditLog.js.map +1 -1
  122. package/dist/dataLayer/schemaDescribe.js +2 -0
  123. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  124. package/dist/globals.d.ts +1 -1
  125. package/dist/globals.js +1 -0
  126. package/dist/globals.js.map +1 -1
  127. package/dist/index.d.ts +3 -0
  128. package/dist/index.js +1 -0
  129. package/dist/index.js.map +1 -1
  130. package/dist/json/systemSchema.json +38 -0
  131. package/dist/resources/DatabaseTransaction.d.ts +31 -3
  132. package/dist/resources/DatabaseTransaction.js +190 -29
  133. package/dist/resources/DatabaseTransaction.js.map +1 -1
  134. package/dist/resources/LMDBTransaction.d.ts +1 -1
  135. package/dist/resources/LMDBTransaction.js +43 -5
  136. package/dist/resources/LMDBTransaction.js.map +1 -1
  137. package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
  138. package/dist/resources/PrimaryRocksDatabase.js +194 -0
  139. package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
  140. package/dist/resources/RecordEncoder.d.ts +1 -0
  141. package/dist/resources/RecordEncoder.js +81 -75
  142. package/dist/resources/RecordEncoder.js.map +1 -1
  143. package/dist/resources/RequestTarget.d.ts +19 -0
  144. package/dist/resources/RequestTarget.js.map +1 -1
  145. package/dist/resources/Resource.d.ts +0 -1
  146. package/dist/resources/Resource.js +103 -16
  147. package/dist/resources/Resource.js.map +1 -1
  148. package/dist/resources/ResourceInterface.d.ts +6 -2
  149. package/dist/resources/ResourceInterface.js.map +1 -1
  150. package/dist/resources/Resources.js.map +1 -1
  151. package/dist/resources/Table.d.ts +6 -4
  152. package/dist/resources/Table.js +247 -24
  153. package/dist/resources/Table.js.map +1 -1
  154. package/dist/resources/analytics/read.js +25 -28
  155. package/dist/resources/analytics/read.js.map +1 -1
  156. package/dist/resources/blob.js +165 -22
  157. package/dist/resources/blob.js.map +1 -1
  158. package/dist/resources/crdt.d.ts +2 -0
  159. package/dist/resources/crdt.js +45 -36
  160. package/dist/resources/crdt.js.map +1 -1
  161. package/dist/resources/databases.d.ts +1 -0
  162. package/dist/resources/databases.js +134 -7
  163. package/dist/resources/databases.js.map +1 -1
  164. package/dist/resources/graphql.js +8 -0
  165. package/dist/resources/graphql.js.map +1 -1
  166. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
  167. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
  168. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  169. package/dist/resources/jsResource.js +6 -0
  170. package/dist/resources/jsResource.js.map +1 -1
  171. package/dist/resources/loadEnv.js +30 -3
  172. package/dist/resources/loadEnv.js.map +1 -1
  173. package/dist/resources/login.js +2 -2
  174. package/dist/resources/login.js.map +1 -1
  175. package/dist/resources/models/Models.js +14 -3
  176. package/dist/resources/models/Models.js.map +1 -1
  177. package/dist/resources/models/embedHook.js +10 -2
  178. package/dist/resources/models/embedHook.js.map +1 -1
  179. package/dist/resources/models/openaiStream.d.ts +56 -0
  180. package/dist/resources/models/openaiStream.js +94 -0
  181. package/dist/resources/models/openaiStream.js.map +1 -0
  182. package/dist/resources/replayLogs.js +4 -3
  183. package/dist/resources/replayLogs.js.map +1 -1
  184. package/dist/resources/search.d.ts +1 -1
  185. package/dist/resources/search.js +104 -11
  186. package/dist/resources/search.js.map +1 -1
  187. package/dist/resources/secretDecryptor.d.ts +54 -0
  188. package/dist/resources/secretDecryptor.js +131 -0
  189. package/dist/resources/secretDecryptor.js.map +1 -0
  190. package/dist/resources/tracked.js +15 -5
  191. package/dist/resources/tracked.js.map +1 -1
  192. package/dist/security/auth.js +71 -8
  193. package/dist/security/auth.js.map +1 -1
  194. package/dist/security/jsLoader.js +11 -0
  195. package/dist/security/jsLoader.js.map +1 -1
  196. package/dist/security/tokenAuthentication.d.ts +9 -1
  197. package/dist/security/tokenAuthentication.js +31 -1
  198. package/dist/security/tokenAuthentication.js.map +1 -1
  199. package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
  200. package/dist/server/DurableSubscriptionsSession.js +8 -4
  201. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  202. package/dist/server/REST.js +28 -9
  203. package/dist/server/REST.js.map +1 -1
  204. package/dist/server/fastifyRoutes.js +11 -1
  205. package/dist/server/fastifyRoutes.js.map +1 -1
  206. package/dist/server/graphqlQuerying.js +4 -3
  207. package/dist/server/graphqlQuerying.js.map +1 -1
  208. package/dist/server/http.d.ts +40 -2
  209. package/dist/server/http.js +560 -77
  210. package/dist/server/http.js.map +1 -1
  211. package/dist/server/itc/serverHandlers.js +64 -0
  212. package/dist/server/itc/serverHandlers.js.map +1 -1
  213. package/dist/server/liveSubscriptionAuth.d.ts +12 -0
  214. package/dist/server/liveSubscriptionAuth.js +140 -0
  215. package/dist/server/liveSubscriptionAuth.js.map +1 -0
  216. package/dist/server/loadRootComponents.js +2 -1
  217. package/dist/server/loadRootComponents.js.map +1 -1
  218. package/dist/server/middlewareChain.d.ts +44 -1
  219. package/dist/server/middlewareChain.js +86 -10
  220. package/dist/server/middlewareChain.js.map +1 -1
  221. package/dist/server/mqtt.js +2 -1
  222. package/dist/server/mqtt.js.map +1 -1
  223. package/dist/server/operationsServer.js +1 -1
  224. package/dist/server/operationsServer.js.map +1 -1
  225. package/dist/server/serverHelpers/Headers.d.ts +9 -0
  226. package/dist/server/serverHelpers/Headers.js +22 -0
  227. package/dist/server/serverHelpers/Headers.js.map +1 -1
  228. package/dist/server/serverHelpers/JSONStream.js +3 -3
  229. package/dist/server/serverHelpers/JSONStream.js.map +1 -1
  230. package/dist/server/serverHelpers/Request.d.ts +52 -1
  231. package/dist/server/serverHelpers/Request.js +122 -1
  232. package/dist/server/serverHelpers/Request.js.map +1 -1
  233. package/dist/server/serverHelpers/contentTypes.js +3 -8
  234. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  235. package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
  236. package/dist/server/serverHelpers/progressEmitter.js +52 -4
  237. package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
  238. package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
  239. package/dist/server/serverHelpers/registeredOperations.js +286 -0
  240. package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
  241. package/dist/server/serverHelpers/serverHandlers.js +11 -5
  242. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  243. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
  244. package/dist/server/serverHelpers/serverUtilities.js +67 -5
  245. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  246. package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
  247. package/dist/server/serverHelpers/uwsServer.js +433 -0
  248. package/dist/server/serverHelpers/uwsServer.js.map +1 -0
  249. package/dist/server/static.d.ts +0 -15
  250. package/dist/server/static.js +161 -2
  251. package/dist/server/static.js.map +1 -1
  252. package/dist/server/status/index.d.ts +4 -1
  253. package/dist/server/status/index.js +64 -4
  254. package/dist/server/status/index.js.map +1 -1
  255. package/dist/server/threads/manageThreads.d.ts +10 -0
  256. package/dist/server/threads/manageThreads.js +107 -4
  257. package/dist/server/threads/manageThreads.js.map +1 -1
  258. package/dist/server/threads/socketRouter.js +5 -1
  259. package/dist/server/threads/socketRouter.js.map +1 -1
  260. package/dist/server/threads/threadServer.js +92 -14
  261. package/dist/server/threads/threadServer.js.map +1 -1
  262. package/dist/upgrade/directives/5-2-0.d.ts +7 -0
  263. package/dist/upgrade/directives/5-2-0.js +108 -0
  264. package/dist/upgrade/directives/5-2-0.js.map +1 -0
  265. package/dist/upgrade/directives/directivesController.js +2 -1
  266. package/dist/upgrade/directives/directivesController.js.map +1 -1
  267. package/dist/utility/common_utils.js +5 -0
  268. package/dist/utility/common_utils.js.map +1 -1
  269. package/dist/utility/envFile.d.ts +41 -0
  270. package/dist/utility/envFile.js +221 -0
  271. package/dist/utility/envFile.js.map +1 -0
  272. package/dist/utility/globalSchema.d.ts +9 -0
  273. package/dist/utility/hdbTerms.d.ts +25 -0
  274. package/dist/utility/hdbTerms.js +31 -0
  275. package/dist/utility/hdbTerms.js.map +1 -1
  276. package/dist/utility/logging/harper_logger.d.ts +21 -0
  277. package/dist/utility/logging/harper_logger.js +160 -8
  278. package/dist/utility/logging/harper_logger.js.map +1 -1
  279. package/dist/utility/logging/logRotator.js +29 -17
  280. package/dist/utility/logging/logRotator.js.map +1 -1
  281. package/dist/utility/logging/readLog.d.ts +1 -1
  282. package/dist/utility/logging/readLog.js +305 -2
  283. package/dist/utility/logging/readLog.js.map +1 -1
  284. package/dist/utility/operationPermissions.d.ts +18 -0
  285. package/dist/utility/operationPermissions.js +35 -1
  286. package/dist/utility/operationPermissions.js.map +1 -1
  287. package/dist/utility/operation_authorization.d.ts +17 -0
  288. package/dist/utility/operation_authorization.js +38 -0
  289. package/dist/utility/operation_authorization.js.map +1 -1
  290. package/dist/utility/secretEnvelope.d.ts +30 -0
  291. package/dist/utility/secretEnvelope.js +94 -0
  292. package/dist/utility/secretEnvelope.js.map +1 -0
  293. package/dist/utility/signalling.d.ts +7 -0
  294. package/dist/utility/signalling.js +16 -0
  295. package/dist/utility/signalling.js.map +1 -1
  296. package/dist/validation/configValidator.js +70 -24
  297. package/dist/validation/configValidator.js.map +1 -1
  298. package/index.ts +4 -0
  299. package/json/systemSchema.json +38 -0
  300. package/npm-shrinkwrap.json +15066 -9347
  301. package/package.json +13 -8
  302. package/resources/DESIGN.md +15 -12
  303. package/resources/DatabaseTransaction.ts +206 -37
  304. package/resources/LMDBTransaction.ts +43 -9
  305. package/resources/PrimaryRocksDatabase.ts +201 -0
  306. package/resources/RecordEncoder.ts +81 -69
  307. package/resources/RequestTarget.ts +21 -0
  308. package/resources/Resource.ts +109 -25
  309. package/resources/ResourceInterface.ts +6 -2
  310. package/resources/Resources.ts +1 -3
  311. package/resources/Table.ts +238 -29
  312. package/resources/analytics/read.ts +25 -30
  313. package/resources/blob.ts +159 -23
  314. package/resources/crdt.ts +37 -31
  315. package/resources/databases.ts +143 -6
  316. package/resources/graphql.ts +8 -0
  317. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
  318. package/resources/jsResource.ts +6 -0
  319. package/resources/loadEnv.ts +34 -3
  320. package/resources/login.ts +2 -2
  321. package/resources/models/Models.ts +19 -3
  322. package/resources/models/embedHook.ts +13 -2
  323. package/resources/models/openaiStream.ts +133 -0
  324. package/resources/replayLogs.ts +4 -3
  325. package/resources/search.ts +113 -11
  326. package/resources/secretDecryptor.ts +159 -0
  327. package/resources/tracked.ts +24 -17
  328. package/security/auth.ts +70 -10
  329. package/security/jsLoader.ts +12 -0
  330. package/security/tokenAuthentication.ts +42 -1
  331. package/server/DESIGN.md +10 -0
  332. package/server/DurableSubscriptionsSession.ts +8 -4
  333. package/server/REST.ts +26 -9
  334. package/server/fastifyRoutes.ts +10 -1
  335. package/server/graphqlQuerying.ts +4 -3
  336. package/server/http.ts +563 -77
  337. package/server/itc/serverHandlers.js +70 -0
  338. package/server/liveSubscriptionAuth.ts +152 -0
  339. package/server/loadRootComponents.js +2 -1
  340. package/server/middlewareChain.ts +107 -17
  341. package/server/mqtt.ts +2 -1
  342. package/server/operationsServer.ts +2 -2
  343. package/server/serverHelpers/Headers.ts +24 -0
  344. package/server/serverHelpers/JSONStream.ts +3 -3
  345. package/server/serverHelpers/Request.ts +127 -0
  346. package/server/serverHelpers/contentTypes.ts +3 -8
  347. package/server/serverHelpers/progressEmitter.ts +55 -5
  348. package/server/serverHelpers/registeredOperations.ts +259 -0
  349. package/server/serverHelpers/serverHandlers.js +11 -5
  350. package/server/serverHelpers/serverUtilities.ts +88 -5
  351. package/server/serverHelpers/uwsServer.ts +488 -0
  352. package/server/static.ts +180 -2
  353. package/server/status/index.ts +72 -4
  354. package/server/threads/manageThreads.js +102 -4
  355. package/server/threads/socketRouter.ts +5 -1
  356. package/server/threads/threadServer.js +93 -10
  357. package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
  358. package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
  359. package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
  360. package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
  361. package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
  362. package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
  363. package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
  364. package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
  365. package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
  366. package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
  367. package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
  368. package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
  369. package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
  370. package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
  371. package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
  372. package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
  373. package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
  374. package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
  375. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
  376. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
  377. package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
  378. package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
  379. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
  380. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
  381. package/studio/web/index.html +1 -1
  382. package/upgrade/directives/5-2-0.ts +82 -0
  383. package/upgrade/directives/directivesController.ts +2 -1
  384. package/utility/common_utils.ts +5 -0
  385. package/utility/envFile.ts +218 -0
  386. package/utility/hdbTerms.ts +31 -0
  387. package/utility/logging/harper_logger.ts +156 -8
  388. package/utility/logging/logRotator.ts +24 -14
  389. package/utility/logging/readLog.ts +323 -2
  390. package/utility/operationPermissions.ts +35 -1
  391. package/utility/operation_authorization.ts +74 -1
  392. package/utility/secretEnvelope.ts +113 -0
  393. package/utility/signalling.ts +15 -0
  394. package/validation/configValidator.ts +78 -25
  395. package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
@@ -0,0 +1,201 @@
1
+ import { RocksDatabase, type RocksDatabaseOptions, constants, type Store } from '@harperfast/rocksdb-js';
2
+
3
+ const FRESH_VERSION_FLAG = constants.FRESH_VERSION_FLAG;
4
+ import { WeakLRUCache } from 'weak-lru-cache';
5
+ import { when } from '../utility/when.ts';
6
+ import { entryMap, METADATA, type Entry } from './RecordEncoder.ts';
7
+
8
+ /**
9
+ * RocksDatabase subclass that owns all primary-store behaviour for Harper tables:
10
+ * - RecordEncoder metadata extraction (version, metadataFlags, etc.)
11
+ * - Optional WeakLRUCache keyed on entry version, verified via the process-wide VerificationTable
12
+ *
13
+ * Replaces both the old CachingRocksDatabase and the RocksDB-specific patches applied by
14
+ * handleLocalTimeForGets. Call initStore(rootStore) after open() — or let
15
+ * handleLocalTimeForGets delegate to it automatically via the isPrimaryRocksDatabase marker.
16
+ *
17
+ * Caching is enabled by default; pass { cache: false } to disable (useful for benchmarking
18
+ * or stores where version tracking is not desired).
19
+ *
20
+ * Cache freshness pattern (using the rocksdb-js VT API):
21
+ * 1. verifyVersion(key, cached.version) → true → return cached entry (no disk I/O)
22
+ * 2. verifyVersion(key, cached.version) → false → read from DB, populateVersion, update cache
23
+ */
24
+ export class PrimaryRocksDatabase extends RocksDatabase {
25
+ readonly isPrimaryRocksDatabase = true;
26
+ #cache?: WeakLRUCache;
27
+ readCount = 0;
28
+ cachePuts = false;
29
+ declare rootStore: any;
30
+ declare decoder: any;
31
+
32
+ get #enc(): any {
33
+ return (this as any).encoder;
34
+ }
35
+
36
+ constructor(pathOrStore: string | Store, options?: RocksDatabaseOptions & { cache?: boolean }) {
37
+ const enableCache = (options as any)?.cache === true;
38
+ super(pathOrStore, enableCache ? { ...options, verificationTable: true } : options);
39
+ if (enableCache) {
40
+ this.#cache = new WeakLRUCache();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Initialises encoder/decoder state. Must be called once after open() with the root
46
+ * RocksDatabase. Equivalent to the RocksDB branch of handleLocalTimeForGets, but as
47
+ * a real method rather than instance-level monkey-patching.
48
+ */
49
+ initStore(rootStore: RocksDatabase) {
50
+ this.readCount = 0;
51
+ this.cachePuts = false;
52
+ this.rootStore = rootStore;
53
+ this.#enc.rootStore = rootStore;
54
+ this.#enc.isRocksDB = true;
55
+ this.decoder = this.#enc;
56
+ }
57
+
58
+ #withEntry(entry: Entry, id: any): Entry {
59
+ if (entry.value) {
60
+ if (entry.value.constructor === Object && this.#enc.structPrototype) {
61
+ const originalValue = entry.value;
62
+ entry.value = new this.#enc.structPrototype.constructor();
63
+ Object.assign(entry.value, originalValue);
64
+ }
65
+ if (typeof entry.value === 'object' && entry.value !== null) {
66
+ entryMap.set(entry.value, entry);
67
+ }
68
+ }
69
+ entry.key = id;
70
+ return entry;
71
+ }
72
+
73
+ #processEntry(raw: any, id: any): Entry | undefined {
74
+ if (raw == null) return undefined;
75
+ if (raw[METADATA]) {
76
+ raw.metadataFlags = raw[METADATA];
77
+ return this.#withEntry(raw, id);
78
+ }
79
+ const entry = { value: raw, key: id } as Entry;
80
+ // map metadata-less values too, so getEntry guarantees the value→Entry
81
+ // mapping for every object value it returns (getSync/get rely on this)
82
+ if (typeof raw === 'object') entryMap.set(raw, entry);
83
+ return entry;
84
+ }
85
+
86
+ /**
87
+ * Core read method. Returns a full Entry (with version, metadataFlags, value, …) or
88
+ * undefined. When caching is enabled, passes `expectedVersion` to the native layer so
89
+ * a single call handles both verification (returns FRESH_VERSION_FLAG on hit) and VT
90
+ * population (auto-seeded on DB read). Only cold reads (no cached version) need a
91
+ * separate populateVersion call.
92
+ */
93
+ getEntry(id: any, options?: any): any {
94
+ this.readCount++;
95
+ const cache = this.#cache;
96
+ // The cache stores the record *value* (weakly, via setValue) rather than
97
+ // the Entry: a WeakRef-wrapped value lets the LRFU expirer release it once
98
+ // it cycles out of the LRU stages (a raw Entry stored via set() has neither
99
+ // a .deref nor a .cache hook, so it would never be reclaimed — an unbounded
100
+ // leak). The value→Entry WeakMap (entryMap) recovers the Entry metadata
101
+ // (version, flags) needed for the fast path while the value is live.
102
+ const cachedValue = cache?.getValue(id);
103
+ const cached =
104
+ cachedValue != null && typeof cachedValue === 'object'
105
+ ? (entryMap.get(cachedValue) as Entry | undefined)
106
+ : undefined;
107
+ const expectedVersion = cached?.version;
108
+
109
+ // Build get options, always merging with caller options to preserve
110
+ // transaction snapshot. Pass expectedVersion when cached:
111
+ // VT hit → native returns FRESH_VERSION_FLAG, no DB read
112
+ // VT miss → native reads DB and auto-populates VT slot
113
+ // For cold reads (no cached version), use populateVersion flag so the
114
+ // native layer seeds the VT slot in the same call.
115
+ let getOptions: any;
116
+ if (expectedVersion != null) {
117
+ getOptions = options ? { ...options, expectedVersion } : { expectedVersion };
118
+ } else if (cache) {
119
+ getOptions = options ? { ...options, populateVersion: true } : { populateVersion: true };
120
+ } else {
121
+ getOptions = options;
122
+ }
123
+ const raw = options?.async ? super.get(id, getOptions) : super.getSync(id, getOptions);
124
+
125
+ return when(raw, (result) => {
126
+ if (result === FRESH_VERSION_FLAG) return cached;
127
+ const entry = this.#processEntry(result, id);
128
+ if (entry == null) {
129
+ if (cache && cachedValue !== undefined) cache.delete(id);
130
+ return undefined;
131
+ }
132
+ // Only object values can be weakly cached and mapped back to their Entry;
133
+ // primitive/empty values fall through uncached (no fast path, still correct).
134
+ if (entry.version != null && cache && entry.value != null && typeof entry.value === 'object') {
135
+ entryMap.set(entry.value, entry);
136
+ cache.setValue(id, entry.value, (entry.size ?? 0) >> 10);
137
+ }
138
+ return entry;
139
+ });
140
+ }
141
+
142
+ getSync(id: any, options?: any): any {
143
+ const entry = this.getEntry(id, options) as Entry;
144
+ return entry?.value;
145
+ }
146
+
147
+ get(id: any, options?: any): any {
148
+ return when(this.getEntry(id, { ...options, async: true }), (entry: Entry) => entry?.value);
149
+ }
150
+
151
+ getRange(options?: any): any {
152
+ const iterable = super.getRange(options);
153
+ if (options?.valuesForKey) return iterable.map((v: any) => v?.value);
154
+ if (options?.values === false || options?.onlyCount) return iterable;
155
+ if (!this.#enc.isRocksDB) return iterable;
156
+ const enc = this.#enc;
157
+ return iterable.map((entry: any) => {
158
+ if (entry.value?.[METADATA]) {
159
+ entry.metadataFlags = entry.value[METADATA];
160
+ Object.assign(entry, entry.value);
161
+ }
162
+ if (entry.value?.constructor === Object && enc.structPrototype) {
163
+ const originalValue = entry.value;
164
+ entry.value = new enc.structPrototype.constructor();
165
+ for (const key in originalValue) entry.value[key] = originalValue[key];
166
+ }
167
+ return entry;
168
+ });
169
+ }
170
+
171
+ putSync(id: any, value: any, options?: any): any {
172
+ this.#cache?.delete(id);
173
+ return super.putSync(id, value, options);
174
+ }
175
+
176
+ removeSync(id: any, options?: any): any {
177
+ this.#cache?.delete(id);
178
+ return super.removeSync(id, options);
179
+ }
180
+
181
+ clearSync(): void {
182
+ // clearSync wipes every record; the per-instance cache must be dropped
183
+ // too, otherwise a cached entry would survive the clear and be served as
184
+ // a stale "still exists" hit on the next read.
185
+ this.#cache?.clear();
186
+ return super.clearSync();
187
+ }
188
+
189
+ clear(): Promise<void> {
190
+ this.#cache?.clear();
191
+ return super.clear();
192
+ }
193
+
194
+ open(): PrimaryRocksDatabase {
195
+ return super.open() as PrimaryRocksDatabase;
196
+ }
197
+
198
+ static open(pathOrStore: string | Store, options?: RocksDatabaseOptions): PrimaryRocksDatabase {
199
+ return new PrimaryRocksDatabase(pathOrStore, options).open();
200
+ }
201
+ }
@@ -18,6 +18,7 @@ import {
18
18
  LOCAL_ONLY,
19
19
  } from './auditStore.ts';
20
20
  import * as harperLogger from '../utility/logging/harper_logger.ts';
21
+ import { getNextMonotonicTime } from '../utility/lmdb/commonUtility.ts';
21
22
  import './blob.ts';
22
23
  import {
23
24
  blobsWereEncoded,
@@ -118,6 +119,12 @@ export class RecordEncoder extends StructonEncoder {
118
119
  isRocksDB: boolean;
119
120
  name: string;
120
121
  useVersions: boolean;
122
+ // Self-versioning mode for stores whose writes never stage a timestamp (HNSW/custom-index
123
+ // object stores). When set, each encode stamps a fresh monotonic version into the 8-byte
124
+ // metadata prefix so the value carries a version the RocksDB Verification Table can extract —
125
+ // enabling race-safe cache verification of graph nodes during traversal. Unlike primary-record
126
+ // writes, this never reads or clears the shared timestampNextEncoding global (harper#1307).
127
+ autoVersion = false;
121
128
  constructor(options) {
122
129
  options.useBigIntExtension = true;
123
130
  // Bound the per-encoder typed-structure dictionary. It is append-only and pinned on the
@@ -169,6 +176,33 @@ export class RecordEncoder extends StructonEncoder {
169
176
  lastValueEncoding = superEncode.call(this, record, options);
170
177
  return lastValueEncoding;
171
178
  }
179
+ if (this.autoVersion && this.isRocksDB) {
180
+ // Self-versioned index store (RocksDB only — the Verification Table is RocksDB-specific):
181
+ // prefix the record-encoder metadata header so each node carries a version the VT can
182
+ // extract and the decode path recognises. The header is [8-byte BE float64 version]
183
+ // [4-byte BE metadata word], exactly the format decode() expects: the version's first
184
+ // byte (0x42 for current-era ms timestamps) triggers metadata detection, and the
185
+ // ACTION_32_BIT-tagged flags word (flags = 0 — no expiration/residency/etc.) is required
186
+ // so decode consumes a metadata word rather than mis-reading the struct's first byte as
187
+ // flags — the flags word being mandatory was the actual bug behind earlier failures,
188
+ // NOT any struct incompatibility. Use the same reserve-start mechanism the primary
189
+ // metadata path below uses (the 2048|valueStart option reserves valueStart bytes at the
190
+ // front of the encode buffer; we write the header in place), which works fine with the
191
+ // forced randomAccessStructure/typed-struct mode of custom-object indexes — verified
192
+ // against the cold/frozen-read (#1161) and concurrent multi-worker (#386) suites. This
193
+ // avoids a per-write extra allocation + full-payload copy. Never touch the shared
194
+ // timestampNextEncoding/metadataInNextEncoding globals — those belong to the enclosing
195
+ // primary-record write whose commit nests this index encode (harper#1307).
196
+ const valueStart = 12; // 8-byte version + 4-byte metadata word
197
+ const encoded = superEncode.call(this, record, options | 2048 | valueStart);
198
+ const position = encoded.start || 0;
199
+ const dataView =
200
+ encoded.dataView || (encoded.dataView = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength));
201
+ dataView.setFloat64(position, getNextMonotonicTime()); // version (big-endian, rocksdb stores it directly)
202
+ dataView.setUint32(position + 8, ACTION_32_BIT << 24); // metadata word: ACTION_32_BIT tag, flags = 0
203
+ lastValueEncoding = encoded.subarray(position + valueStart, encoded.end);
204
+ return encoded;
205
+ }
172
206
  // this handles our custom metadata encoding, prefixing the record with metadata, including the local
173
207
  // timestamp into the audit record, invalidation status and residency information
174
208
  if (timestampNextEncoding || metadataInNextEncoding >= 0) {
@@ -457,52 +491,37 @@ function getTimestamp() {
457
491
  }
458
492
 
459
493
  export function handleLocalTimeForGets(store, rootStore) {
460
- const isRocksDB = store instanceof RocksDatabase;
494
+ if ((store as any).isPrimaryRocksDatabase) {
495
+ (store as any).initStore(rootStore);
496
+ return store;
497
+ }
461
498
  store.readCount = 0;
462
499
  store.cachePuts = false;
463
500
  store.rootStore = rootStore;
464
501
  store.encoder.rootStore = rootStore;
465
- store.encoder.isRocksDB = isRocksDB;
466
502
  store.decoder = store.encoder;
467
503
  const storeGetEntry = store.getEntry;
468
- const storeGetSync = store.getSync;
469
- const storeGet = store.get;
470
504
 
471
505
  store.getEntry = function (id, options) {
472
506
  store.readCount++;
473
507
  lastMetadata = null;
474
- if (isRocksDB) {
475
- return when(
476
- options?.async ? storeGet.call(store, id, options) : storeGetSync.call(store, id, options),
477
- (entry) => {
478
- if (entry) {
479
- if (entry[METADATA]) {
480
- entry.metadataFlags = entry[METADATA];
481
- return withEntry(entry);
482
- } else return { value: entry };
483
- } else return entry;
484
- }
485
- );
486
- } else {
487
- let entry: Entry = storeGetEntry.call(this, id, options);
488
- if (lastMetadata) {
489
- entry.metadataFlags = lastMetadata[METADATA];
490
- entry.localTime = lastMetadata.localTime;
491
- entry.residencyId = lastMetadata.residencyId;
492
- entry.nodeId = lastMetadata.nodeId;
493
- entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
494
- entry.size = lastMetadata.size;
495
- if (lastMetadata.expiresAt >= 0) {
496
- entry.expiresAt = lastMetadata.expiresAt;
497
- }
498
- if (isRocksDB) entry.version = lastMetadata.localTime;
499
- if (entry.value) {
500
- entryMap.set(entry.value, entry); // allow the record to access the entry
501
- }
502
- entry.key = id;
508
+ let entry: Entry = storeGetEntry.call(this, id, options);
509
+ if (lastMetadata) {
510
+ entry.metadataFlags = lastMetadata[METADATA];
511
+ entry.localTime = lastMetadata.localTime;
512
+ entry.residencyId = lastMetadata.residencyId;
513
+ entry.nodeId = lastMetadata.nodeId;
514
+ entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
515
+ entry.size = lastMetadata.size;
516
+ if (lastMetadata.expiresAt >= 0) {
517
+ entry.expiresAt = lastMetadata.expiresAt;
518
+ }
519
+ if (entry.value) {
520
+ entryMap.set(entry.value, entry); // allow the record to access the entry
503
521
  }
504
- return entry && withEntry(entry);
522
+ entry.key = id;
505
523
  }
524
+ return entry && withEntry(entry);
506
525
  // if we have decoded with metadata, we want to pull it out and assign to this entry
507
526
  function withEntry(entry) {
508
527
  if (entry.value) {
@@ -547,15 +566,9 @@ export function handleLocalTimeForGets(store, rootStore) {
547
566
  if (options.values === false || options.onlyCount) return iterable;
548
567
  return iterable.map((entry) => {
549
568
  // if we have metadata, move the metadata to the entry
550
- if (isRocksDB) {
551
- if (entry.value?.[METADATA]) {
552
- entry.metadataFlags = entry.value[METADATA];
553
- Object.assign(entry, entry.value);
554
- }
555
- } else if (lastMetadata) {
569
+ if (lastMetadata) {
556
570
  entry.metadataFlags = lastMetadata[METADATA];
557
571
  entry.localTime = lastMetadata.localTime;
558
- if (isRocksDB) entry.version = lastMetadata.localTime;
559
572
  entry.residencyId = lastMetadata.residencyId;
560
573
  entry.nodeId = lastMetadata.nodeId;
561
574
  entry.additionalAuditRefs = lastMetadata.additionalAuditRefs;
@@ -575,36 +588,35 @@ export function handleLocalTimeForGets(store, rootStore) {
575
588
  });
576
589
  };
577
590
 
578
- if (!isRocksDB) {
579
- // add read transaction tracking
580
- const txn = store.useReadTransaction();
581
- txn.done();
582
- if (!txn.done.isTracked) {
583
- const Txn = txn.constructor;
584
- const use = txn.use;
585
- const done = txn.done;
586
- Txn.prototype.use = function () {
587
- if (!this.timerTracked) {
588
- this.timerTracked = true;
589
- trackedTxns.push(new WeakRef(this));
590
- }
591
- use.call(this);
592
- };
593
- Txn.prototype.done = function () {
594
- if (this.isDone) return;
595
- done.call(this);
596
- this.openTimer = 0; // reset so idle pool time doesn't accumulate toward the stale-open threshold
597
- if (this.isDone) {
598
- for (let i = 0; i < trackedTxns.length; i++) {
599
- const txn = trackedTxns[i].deref();
600
- if (!txn || txn.isDone || txn.isCommitted) {
601
- trackedTxns.splice(i--, 1);
602
- }
591
+ // add read transaction tracking (LMDB only — RocksDB primary stores are PrimaryRocksDatabase and
592
+ // return early above; only LMDB stores reach here)
593
+ const txn = store.useReadTransaction();
594
+ txn.done();
595
+ if (!txn.done.isTracked) {
596
+ const Txn = txn.constructor;
597
+ const use = txn.use;
598
+ const done = txn.done;
599
+ Txn.prototype.use = function () {
600
+ if (!this.timerTracked) {
601
+ this.timerTracked = true;
602
+ trackedTxns.push(new WeakRef(this));
603
+ }
604
+ use.call(this);
605
+ };
606
+ Txn.prototype.done = function () {
607
+ if (this.isDone) return;
608
+ done.call(this);
609
+ this.openTimer = 0; // reset so idle pool time doesn't accumulate toward the stale-open threshold
610
+ if (this.isDone) {
611
+ for (let i = 0; i < trackedTxns.length; i++) {
612
+ const txn = trackedTxns[i].deref();
613
+ if (!txn || txn.isDone || txn.isCommitted) {
614
+ trackedTxns.splice(i--, 1);
603
615
  }
604
616
  }
605
- };
606
- Txn.prototype.done.isTracked = true;
607
- }
617
+ }
618
+ };
619
+ Txn.prototype.done.isTracked = true;
608
620
  }
609
621
 
610
622
  return store;
@@ -56,6 +56,8 @@ export class RequestTarget extends URLSearchParams {
56
56
  declare previousResidency?: string[];
57
57
 
58
58
  // Action tracking
59
+ /** Cache disposition of this get on a caching table: true if loaded from the source, false if
60
+ * served from cache. Set per-get; read it on the RequestTarget you passed to the get. */
59
61
  declare loadedFromSource?: boolean;
60
62
  declare createdNewId?: string;
61
63
 
@@ -65,6 +67,25 @@ export class RequestTarget extends URLSearchParams {
65
67
  declare allowFullScan?: boolean;
66
68
  declare allowConditionsOnDynamicAttributes?: boolean;
67
69
 
70
+ /**
71
+ * Predicate-aware vector search (#1241). A `(record) => boolean` filter evaluated during HNSW
72
+ * traversal (and as a post-filter for other paths), so a vector sort keeps exploring until it has
73
+ * enough MATCHING nearest neighbors instead of post-filtering an under-filled candidate set. The
74
+ * function must be synchronous and side-effect free; the record it receives is frozen. JS-API only —
75
+ * never parsed from a REST query string (no eval of user-supplied code over HTTP).
76
+ */
77
+ declare vectorFilter?: (record: any) => boolean;
78
+
79
+ /**
80
+ * When `false`, the query reads against the latest committed data without holding a consistent
81
+ * read snapshot open for the duration of the iteration. This trades read consistency (rows
82
+ * written after the scan starts may be observed) for not pinning a snapshot that blocks
83
+ * compaction and ties up resources — useful for long-running scans such as analytics queries.
84
+ * Defaults to `true` (a stable snapshot is held). Currently only honored by RocksDB-backed
85
+ * tables; see `DatabaseTransaction.getReadTxn`.
86
+ */
87
+ declare snapshot?: boolean;
88
+
68
89
  constructor(target?: string) {
69
90
  let searchIndex: number | undefined;
70
91
  let path: string | undefined;
@@ -10,7 +10,7 @@ import {
10
10
  RequestTargetOrId,
11
11
  } from './ResourceInterface.ts';
12
12
  import { randomUUID } from 'crypto';
13
- import { DatabaseTransaction, type Transaction } from './DatabaseTransaction.ts';
13
+ import { DatabaseTransaction, TRANSACTION_STATE, type Transaction } from './DatabaseTransaction.ts';
14
14
  import { IterableEventQueue } from './IterableEventQueue.ts';
15
15
  import { _assignPackageExport } from '../globals.js';
16
16
  import { ClientError, AccessViolation } from '../utility/errors/hdbError.ts';
@@ -18,6 +18,7 @@ import { transaction, contextStorage } from './transaction.ts';
18
18
  import { parseQuery } from './search.ts';
19
19
  import { RequestTarget } from './RequestTarget.ts';
20
20
  import { when, promiseNormalize } from '../utility/when.ts';
21
+ import { registerLiveSubscription } from '../server/liveSubscriptionAuth.ts';
21
22
  import type { JsonSchemaFragment } from './jsonSchemaTypes.ts';
22
23
 
23
24
  const EXTENSION_TYPES = {
@@ -68,10 +69,6 @@ export class Resource<Record extends object = any> implements ResourceInterface<
68
69
  return true; // Subclasses should override if needed
69
70
  }
70
71
 
71
- wasLoadedFromSource(): boolean | void {
72
- // Subclasses should override if needed
73
- }
74
-
75
72
  addTo(_property: keyof Record, _value: Record[keyof Record]): void {
76
73
  throw new Error('Not implemented');
77
74
  }
@@ -661,8 +658,34 @@ function transactional(
661
658
  if (isCollection) resourceOptions.isCollection = true;
662
659
  } else resourceOptions = options;
663
660
  const loadAsInstance = this.loadAsInstance;
664
- if (context?.transaction) {
665
- // we are already in a transaction, proceed
661
+ // Only join an existing transaction if it is still genuinely OPEN (mirrors the reuse check
662
+ // resources/transaction.ts's transaction() helper already applies to itself). A `context`
663
+ // object can carry a *stale* `.transaction` left over from an earlier, unrelated call that
664
+ // already ran to completion: ambient contexts obtained via contextStorage.getStore() are
665
+ // no longer guaranteed to be fresh, one-shot objects now that processLocalTransaction (#1591/
666
+ // #1592) installs one shared, long-lived context for the lifetime of an entire operation
667
+ // handler. Without this check, a second, logically-independent static Resource API call made
668
+ // later in the same handler (no explicit context of its own) would see that leftover
669
+ // `.transaction` reference and wrongly treat it as "still open," silently folding its write
670
+ // into a transaction whose commit lifecycle was already driven to completion by the first
671
+ // call — coalescing two unrelated writes and risking the first one being dropped. See
672
+ // harper-pro's replication/subscriptionManager.ts ensureNode(), which is called twice in
673
+ // sequence (once for this node, once for a peer) from a single add_node/add_node_back
674
+ // operation handler and reproduced this exact loss in a live cluster-formation test.
675
+ //
676
+ // A transaction that is no longer OPEN reached that state one of two ways, and they need
677
+ // opposite treatment:
678
+ // - committed: the prior call ran to completion normally. Safe to silently start a fresh
679
+ // transaction for this independent call (the case above).
680
+ // - aborted due to exceeding storage.maxTransactionOpenTime (#1411, DatabaseTransaction.ts
681
+ // abortDueToTimeout(), marked via the `timedOut` poison flag): the whole logical operation
682
+ // must fail atomically, not have this write quietly land on a brand-new transaction while
683
+ // the earlier write(s) were rolled back. Joining the poisoned transaction here (instead of
684
+ // starting fresh) makes the write throw transactionOpenTooLongError via addWrite()/commit()'s
685
+ // poison check, correctly propagating the abort to the caller. See
686
+ // integrationTests/resources/txn-overtime-atomicity.test.ts.
687
+ if (context?.transaction?.open === TRANSACTION_STATE.OPEN || context?.transaction?.timedOut) {
688
+ // we are already in a transaction (or it was poisoned by a timeout abort and must fail), proceed
666
689
  const resource = this.getResource(query, context, resourceOptions);
667
690
  return resource.then
668
691
  ? resource.then(authorizeActionOnResource)
@@ -683,6 +706,20 @@ function transactional(
683
706
  );
684
707
  }
685
708
  function authorizeActionOnResource(resource: ResourceInterface) {
709
+ // For subscribe, register the resulting subscription for continuous re-authorization so it is
710
+ // terminated if the principal later loses access or its token expires (#1414). Non-subscribe
711
+ // actions run unchanged.
712
+ // 'subscribe' is the direct MQTT path; 'connect' is the SSE/WebSocket path (REST CONNECT) —
713
+ // both resolve to the same subscription iterable.
714
+ const isSubscribeAction = options.method === 'subscribe' || options.method === 'connect';
715
+ const runAction = (data: any) => {
716
+ const result = action(resource, query, context, data);
717
+ if (!isSubscribeAction) return result;
718
+ return when(result, (subscription: any) => {
719
+ registerLiveSubscriptionForContext(subscription, resource, query, context);
720
+ return subscription;
721
+ });
722
+ };
686
723
  let checkPermission = false;
687
724
  if (query.checkPermission) {
688
725
  checkPermission = true;
@@ -697,26 +734,38 @@ function transactional(
697
734
  if (checkPermission) {
698
735
  if (loadAsInstance !== false) {
699
736
  // do permission checks, with allow methods
700
- const allowed =
701
- options.type === 'read'
702
- ? resource.allowRead(context.user, query, context)
703
- : options.type === 'update'
704
- ? resource.doesExist?.() === false
705
- ? resource.allowCreate(context.user, data, context)
706
- : resource.allowUpdate(context.user, data, context)
707
- : options.type === 'create'
708
- ? resource.allowCreate(context.user, data, context)
709
- : resource.allowDelete(context.user, query, context);
737
+ let allowed;
738
+ try {
739
+ allowed =
740
+ options.type === 'read'
741
+ ? resource.allowRead(context.user, query, context)
742
+ : options.type === 'update'
743
+ ? resource.doesExist?.() === false
744
+ ? resource.allowCreate(context.user, data, context)
745
+ : resource.allowUpdate(context.user, data, context)
746
+ : options.type === 'create'
747
+ ? resource.allowCreate(context.user, data, context)
748
+ : resource.allowDelete(context.user, query, context);
749
+ } catch {
750
+ // allow* threw — fail closed rather than letting the request proceed
751
+ throw new AccessViolation(context.user);
752
+ }
710
753
  if ((allowed as any)?.then) {
711
- return (allowed as any).then((allowed) => {
712
- query.checkPermission = false;
713
- if (!allowed) {
754
+ return (allowed as any).then(
755
+ (allowed) => {
756
+ query.checkPermission = false;
757
+ if (!allowed) {
758
+ throw new AccessViolation(context.user);
759
+ }
760
+ return when(data, (data) => {
761
+ return runAction(data);
762
+ });
763
+ },
764
+ () => {
765
+ // async allow* rejected — fail closed
714
766
  throw new AccessViolation(context.user);
715
767
  }
716
- return when(data, (data) => {
717
- return action(resource, query, context, data);
718
- });
719
- });
768
+ );
720
769
  }
721
770
  query.checkPermission = false;
722
771
  if (!allowed) {
@@ -725,11 +774,46 @@ function transactional(
725
774
  }
726
775
  }
727
776
  return when(data, (data) => {
728
- return action(resource, query, context, data);
777
+ return runAction(data);
729
778
  });
730
779
  }
731
780
  }
732
781
  }
782
+ function registerLiveSubscriptionForContext(subscription: any, resource: any, query: any, context: Context) {
783
+ const user: any = context?.user;
784
+ const username = user?.username;
785
+ // Internal watchers, replication and local-bypass have no user principal — nothing to re-authorize.
786
+ if (!username) return;
787
+ const capturedId = query?.id;
788
+ const capturedIsCollection = query?.isCollection;
789
+ const capturedSelect = query?.select;
790
+ registerLiveSubscription({
791
+ subscription,
792
+ username,
793
+ // JWT exp of the bearer credential (set by the auth layer); undefined for password/mTLS/session.
794
+ authExpiresAt: user.authExpiresAt,
795
+ recheck: async () => {
796
+ // Re-fetch current user state — the user/role cache is rebuilt on mutations — so a dropped or
797
+ // role-stripped user no longer authorizes.
798
+ const { findAndValidateUser } = require('../security/user');
799
+ const fresh: any = await findAndValidateUser(username, undefined, false);
800
+ if (!fresh?.role) return false;
801
+ // Advance the subscription's context to the fresh user so downstream checks — context.user
802
+ // and getCurrentUser() (which reads the resource's context) — evaluate against current state,
803
+ // not the stale user captured at subscribe time.
804
+ if (context) (context as any).user = fresh;
805
+ // Re-run the same table/RBAC-level allowRead the subscription was granted with, against the
806
+ // fresh user. No per-record evaluation — this matches how access was originally granted.
807
+ const reTarget: any = new RequestTarget();
808
+ reTarget.id = capturedId;
809
+ reTarget.isCollection = capturedIsCollection;
810
+ reTarget.select = capturedSelect;
811
+ reTarget.checkPermission = fresh.role?.permission;
812
+ return !!(await resource.allowRead(fresh, reTarget, context));
813
+ },
814
+ });
815
+ }
816
+
733
817
  const KNOWN_METHODS = ['get', 'head', 'put', 'post', 'delete', 'patch', 'query', 'move', 'copy'];
734
818
  type ClientErrorWithMethods = ClientError & { allow: string[]; method: string };
735
819
  export function missingMethod(resource: any, method: string) {
@@ -49,7 +49,6 @@ export interface ResourceInterface<Record extends object = any>
49
49
  subscribe?(request: SubscriptionRequest): AsyncIterable<Record> | Promise<AsyncIterable<Record>>;
50
50
 
51
51
  doesExist(): boolean;
52
- wasLoadedFromSource(): boolean | void;
53
52
 
54
53
  getCurrentUser(): User | undefined;
55
54
  }
@@ -95,7 +94,6 @@ export interface Context {
95
94
  sourceApply?: boolean;
96
95
  originatingOperation?: OperationFunctionName;
97
96
  previousResidency?: string[];
98
- loadedFromSource?: boolean;
99
97
  nodeName?: string;
100
98
  resourceCache?: Map<Id, any>;
101
99
  _freezeRecords?: boolean; // until v5, we conditionally freeze records for back-compat
@@ -177,6 +175,12 @@ interface TypedDirectCondition<Record extends object, Property extends keyof Rec
177
175
  * full scan unless paired with another indexed condition.
178
176
  */
179
177
  negated?: boolean;
178
+ /**
179
+ * Internal (#1241): a `(primaryKey) => boolean` predicate composed by the query executor from
180
+ * companion conditions, a caller `vectorFilter`, and record-level RBAC, pushed into a filterable
181
+ * custom index (HNSW) so filtering happens during traversal. Not part of the public query surface.
182
+ */
183
+ recordFilter?: (primaryKey: Id) => boolean;
180
184
  }
181
185
 
182
186
  interface ConditionGroup<Record extends object = any> {
@@ -17,9 +17,7 @@ export interface ResourceEntry {
17
17
  }
18
18
 
19
19
  export type RouteSegment =
20
- | { type: 'static'; value: string }
21
- | { type: 'param'; value: string }
22
- | { type: 'wildcard'; value: string };
20
+ { type: 'static'; value: string } | { type: 'param'; value: string } | { type: 'wildcard'; value: string };
23
21
 
24
22
  export interface CompiledRoute {
25
23
  /** Normalized pattern (leading/trailing slashes stripped) — used as the identity key for the route. */