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
@@ -140,6 +140,18 @@ function isFrozenRecordObject(value: any): boolean {
140
140
  Object.isFrozen(value)
141
141
  );
142
142
  }
143
+ // Freeze a decoded record value for cache integrity, guarding the bare-TypedArray-root case:
144
+ // V8 throws "Cannot freeze array buffer views with elements" on Object.freeze of a non-empty
145
+ // TypedArray/DataView (#1298). _writeUpdate now rejects such roots on write, but this read-side
146
+ // guard still backstops records that bypass validation (source/cache population, replication of
147
+ // legacy data) and lets an already-poisoned table be read again after upgrade. The freeze is
148
+ // shallow anyway, so a typed-array root needs none. Only freeze plain objects: skip ArrayBuffer
149
+ // views/ArrayBuffers, and short-circuit primitives/null/undefined to avoid a needless native
150
+ // Object.freeze call on the hot read/scan path.
151
+ export function freezeRecord(value: any): void {
152
+ if (value !== null && typeof value === 'object' && !ArrayBuffer.isView(value) && !(value instanceof ArrayBuffer))
153
+ Object.freeze(value);
154
+ }
143
155
  export const INVALIDATED = 1;
144
156
  export const EVICTED = 8; // note that 2 is reserved for timestamps
145
157
  const TEST_WRITE_KEY_BUFFER = Buffer.allocUnsafeSlow(8192);
@@ -203,6 +215,7 @@ export function makeTable(options) {
203
215
  replicate,
204
216
  description,
205
217
  hidden,
218
+ cacheControl,
206
219
  } = options;
207
220
  let { expirationMS: expirationMs, evictionMS: evictionMs, audit, trackDeletes } = options;
208
221
  evictionMs ??= 0;
@@ -306,6 +319,8 @@ export function makeTable(options) {
306
319
  static description = description;
307
320
  static properties = properties;
308
321
  static hidden = hidden;
322
+ // default `Cache-Control` for anonymous REST reads (from `@table(cacheControl: "...")`), see REST.ts
323
+ static cacheControl = cacheControl;
309
324
  static outputSchemas: { [verb: string]: JsonSchemaFragment } | undefined;
310
325
  static mcp: { annotations?: { [verb: string]: any } } | undefined;
311
326
  static replicate = replicate;
@@ -730,7 +745,7 @@ export function makeTable(options) {
730
745
  // return 504 (rather than 404) if there is no content and the cache-control header
731
746
  // dictates not to go to source
732
747
  if (!this.doesExist()) throw new ServerError('Entry is not cached', 504);
733
- if (hasSourceGet && target) target.loadedFromSource = false; // mark it as cached
748
+ if (hasSourceGet) setLoadedFromSource(target, false); // mark it as cached
734
749
  } else if (resourceOptions?.ensureLoaded) {
735
750
  const loadingFromSource = ensureLoadedFromSource(
736
751
  (this.constructor as any).source,
@@ -746,7 +761,7 @@ export function makeTable(options) {
746
761
  TableResource._updateResource(this, entry);
747
762
  return this;
748
763
  });
749
- } else if (hasSourceGet) target.loadedFromSource = false; // mark it as cached
764
+ } else if (hasSourceGet) setLoadedFromSource(target, false); // mark it as cached
750
765
  }
751
766
  return this;
752
767
  }
@@ -1212,7 +1227,12 @@ export function makeTable(options) {
1212
1227
  let allowed = true;
1213
1228
  if ((target as any)?.checkPermission) {
1214
1229
  // requesting authorization verification
1215
- allowed = this.allowRead(context.user, target, context);
1230
+ try {
1231
+ allowed = this.allowRead(context.user, target, context);
1232
+ } catch {
1233
+ // allow* threw — fail closed rather than letting the request proceed
1234
+ throw new AccessViolation(context.user);
1235
+ }
1216
1236
  }
1217
1237
  return promiseNormalize(
1218
1238
  when(
@@ -1227,6 +1247,7 @@ export function makeTable(options) {
1227
1247
  // return 504 (rather than 404) if there is no content and the cache-control header
1228
1248
  // dictates not to go to source
1229
1249
  if (!entry?.value) throw new ServerError('Entry is not cached', 504);
1250
+ if (hasSourceGet) setLoadedFromSource(target, false); // mark it as cached
1230
1251
  } else if (ensureLoaded) {
1231
1252
  const loadingFromSource = ensureLoadedFromSource(
1232
1253
  constructor.source,
@@ -1239,7 +1260,7 @@ export function makeTable(options) {
1239
1260
  if (loadingFromSource) {
1240
1261
  txn?.disregardReadTxn(); // this could take some time, so don't keep the transaction open if possible
1241
1262
  return loadingFromSource.then((entry) => entry?.value);
1242
- }
1263
+ } else if (hasSourceGet) setLoadedFromSource(target, false); // mark it as cached
1243
1264
  }
1244
1265
  return entry?.value;
1245
1266
  });
@@ -1303,7 +1324,10 @@ export function makeTable(options) {
1303
1324
  if (!property.name) property = { name: property };
1304
1325
  if (!property.checkPermission && (target as any).checkPermission)
1305
1326
  property.checkPermission = (target as any).checkPermission;
1306
- if (!relatedTable.prototype.allowRead.call(null, user, property, context)) return false;
1327
+ // Invoke the related table's allowRead on a properly-bound instance rather than
1328
+ // `.call(null, ...)` so `this` is a valid resource of the related type.
1329
+ const relatedResource = new relatedTable(undefined, context);
1330
+ if (!relatedResource.allowRead(user, property, context)) return false;
1307
1331
  if (!property.select) return property.name; // no select was applied, just return the name
1308
1332
  }
1309
1333
  return property;
@@ -1895,6 +1919,33 @@ export function makeTable(options) {
1895
1919
  if (fullUpdate || (recordUpdate && hasChanges(this.#changes === recordUpdate ? this : recordUpdate))) {
1896
1920
  if (!(context as any)?.source) {
1897
1921
  transaction.checkOverloaded();
1922
+ // A record must be a plain object. Reject primitive, string/number, bare-binary,
1923
+ // and bare-array roots — e.g. a raw Buffer from an application/octet-stream PUT, a
1924
+ // JSON string/number body, or a top-level JSON array. Such roots carry no primary
1925
+ // key or attributes, are meaningless to SQL/get-attributes, and a bare TypedArray
1926
+ // root additionally throws on Object.freeze during scans (#1298). Binary data belongs
1927
+ // in a Bytes/Blob attribute. (Messages go through _writePublish, not here, so raw
1928
+ // publish payloads are unaffected.)
1929
+ const isBinaryRoot = ArrayBuffer.isView(recordUpdate) || recordUpdate instanceof ArrayBuffer;
1930
+ if (
1931
+ recordUpdate === null ||
1932
+ typeof recordUpdate !== 'object' ||
1933
+ isBinaryRoot ||
1934
+ Array.isArray(recordUpdate)
1935
+ ) {
1936
+ // Avoid dumping every byte of a large binary body or huge payload into the error.
1937
+ let received: string;
1938
+ if (isBinaryRoot) {
1939
+ received = `${recordUpdate.constructor?.name ?? 'binary'} of ${recordUpdate.byteLength} bytes`;
1940
+ } else {
1941
+ const full = stringify(recordUpdate) ?? typeof recordUpdate;
1942
+ received = full.length > 200 ? full.slice(0, 200) + '...' : full;
1943
+ }
1944
+ throw new ClientError(
1945
+ `A record must be an object, but received ${received}. To store binary data, put it ` +
1946
+ `in a Bytes or Blob attribute (e.g. a record like { "${primaryKey ?? 'id'}": …, "data": <bytes> }).`
1947
+ );
1948
+ }
1898
1949
  // Records are intentionally immutable: decoded records are frozen (and 5.2 record
1899
1950
  // caching relies on it), so mutating in place would corrupt cached/shared state.
1900
1951
  // validate() coerces values and we stamp created/updated times + the primary key
@@ -1946,6 +1997,16 @@ export function makeTable(options) {
1946
1997
  },
1947
1998
  before: writeToSource(),
1948
1999
  commit: (txnTime: number, existingEntry: Entry, retry: boolean, transaction: any) => {
2000
+ // Whether a prior attempt of THIS write appended its own audit entry (sticky, set in
2001
+ // save(); log entries are not part of the aborted rocks transaction, so they survive).
2002
+ // Only such a write can find its own orphaned entry in the dedup lookups below and must
2003
+ // not treat it as "already applied". Per-write on purpose: the transaction-wide retry
2004
+ // flag would also suppress dedup for a genuine re-delivered duplicate co-batched with
2005
+ // the conflicting write (double-applying it) and for fresh writes staged through a
2006
+ // reused transaction whose retries counter is stale. Sticky on purpose: a proxy read
2007
+ // from the last attempt's skipped state launders when a recommit round self-skips
2008
+ // (walk identity tie against its own staged record) before a fresh-transaction replay.
2009
+ const stagedOwnAuditEntry = retry && write.appendedAuditEntry === true;
1949
2010
  write.skipped = false; // reset on each retry; cleanup happens after commit if still true
1950
2011
  if (retry) {
1951
2012
  if (context && existingEntry?.version > (context.lastModified || 0))
@@ -2066,8 +2127,12 @@ export function makeTable(options) {
2066
2127
  // local audit time, not version, so this version-keyed lookup doesn't apply there (LMDB keeps the
2067
2128
  // exact unbounded walk). A miss (the keyed lookup can lag a back-to-back re-delivery — #1137)
2068
2129
  // simply falls through to the walk, so this never changes correctness; the additionalAuditRefs
2069
- // check above remains the read-your-writes guard.
2070
- if (isRocksDB && dedupVersionCouldBeRetained(txnTime)) {
2130
+ // check above remains the read-your-writes guard. Never when this write staged in a prior
2131
+ // failed attempt: that attempt already appended this write's own audit entry, so the lookup
2132
+ // would find it and skip the write as "already applied" when the record was never committed.
2133
+ // A recommit of the same transaction survived that skip only because the old write batch
2134
+ // still carried the put; a fresh-transaction replay (ERR_TRY_AGAIN) would drop the write.
2135
+ if (isRocksDB && !stagedOwnAuditEntry && dedupVersionCouldBeRetained(txnTime)) {
2071
2136
  const priorAudit = auditStore.get(txnTime, tableId, id, options?.nodeId);
2072
2137
  if (
2073
2138
  priorAudit &&
@@ -2126,7 +2191,11 @@ export function makeTable(options) {
2126
2191
  // A re-delivered write whose exact (version, nodeId) is already in the audit log was already
2127
2192
  // applied; drop it rather than re-applying it (double-applying commutative ops) or writing a
2128
2193
  // duplicate audit-only record. Used by the early-out and the depth-cap block below.
2194
+ // Never a duplicate for a write that staged in a prior failed attempt: that attempt already
2195
+ // appended this write's own audit entry, so the lookup would match it while the record was
2196
+ // never committed (see the up-front keyed dedup above).
2129
2197
  const isReDeliveredDuplicate = () => {
2198
+ if (stagedOwnAuditEntry) return false;
2130
2199
  if (!dedupVersionCouldBeRetained(txnTime)) return false; // pre-retention version — skip the end-of-log scan (best-effort; see above)
2131
2200
  const duplicate = auditStore.get(txnTime, tableId, id, options?.nodeId);
2132
2201
  return (
@@ -2573,7 +2642,13 @@ export function makeTable(options) {
2573
2642
  if (target.parseError) throw target.parseError; // if there was a parse error, we can throw it now
2574
2643
  if (target.checkPermission) {
2575
2644
  // requesting authorization verification
2576
- const allowed = this.allowRead((context as any).user, target, context);
2645
+ let allowed;
2646
+ try {
2647
+ allowed = this.allowRead((context as any).user, target, context);
2648
+ } catch {
2649
+ // allow* threw — fail closed rather than letting the request proceed
2650
+ throw new AccessViolation((context as any).user);
2651
+ }
2577
2652
  if (!allowed) {
2578
2653
  throw new AccessViolation((context as any).user);
2579
2654
  }
@@ -2775,7 +2850,21 @@ export function makeTable(options) {
2775
2850
  // transaction, and we really don't care if the
2776
2851
  // counts are done in the same read transaction because they are just estimates) until the search
2777
2852
  // results have been iterated and finished.
2778
- const readTxn = txn.useReadTxn();
2853
+ // When the query opts out of a snapshot (`snapshot: false`, e.g. long-running analytics
2854
+ // scans), the read transaction reads against the latest committed data without pinning a
2855
+ // consistent snapshot, so the scan doesn't hold a snapshot that blocks compaction.
2856
+ const readTxn = txn.useReadTxn(target.snapshot === false);
2857
+ // Record-level read guard (#1241): a resource may define a static allowReadRecord(user, record)
2858
+ // that participates in query execution (pushed into HNSW traversal for vector sorts, applied as a
2859
+ // post-filter otherwise). Resolve off the actual (possibly subclassed) constructor so overrides win.
2860
+ // SCOPE: this is a QUERY-result filter, not a general read-authorization boundary — direct
2861
+ // single-record get(id) does not consult it (use allowRead/instance hooks for that). It must be
2862
+ // synchronous, side-effect free, and fast; it can run once per candidate record during traversal.
2863
+ const allowReadRecord = (this.constructor as any).allowReadRecord;
2864
+ const recordAccess =
2865
+ typeof allowReadRecord === 'function' || typeof target.vectorFilter === 'function'
2866
+ ? { allowReadRecord, user: (context as any)?.user, vectorFilter: target.vectorFilter }
2867
+ : undefined;
2779
2868
  const entries = executeConditions(
2780
2869
  conditions,
2781
2870
  operator,
@@ -2784,16 +2873,28 @@ export function makeTable(options) {
2784
2873
  target,
2785
2874
  context,
2786
2875
  (results: any[], filters: Function[]) => transformToEntries(results, select, context, readTxn, filters),
2787
- filtered
2876
+ filtered,
2877
+ recordAccess
2788
2878
  );
2789
2879
  const ensure_loaded = (target as any).ensureLoaded !== false;
2880
+ // Authoritative RBAC enforcement (#1241): the guards inside executeConditions evaluate the LOCAL
2881
+ // record, but on a caching table transformEntryForSelect may then revalidate an expired/invalidated
2882
+ // row from source and return a DIFFERENT record. An authorization check must hold on the record
2883
+ // actually returned, so allowReadRecord is re-checked there, after materialization (the earlier
2884
+ // evaluation stays as a prune that also bounds HNSW traversal). vectorFilter and condition filters
2885
+ // intentionally keep the local-record semantics all query filters have on caching tables.
2886
+ const recordGuard =
2887
+ typeof allowReadRecord === 'function'
2888
+ ? (record: any) => allowReadRecord((context as any)?.user, record)
2889
+ : undefined;
2790
2890
  const transformToRecord = TableResource.transformEntryForSelect(
2791
2891
  select,
2792
2892
  context,
2793
2893
  readTxn,
2794
2894
  filtered,
2795
2895
  ensure_loaded,
2796
- true
2896
+ true,
2897
+ recordGuard
2797
2898
  );
2798
2899
  let results = TableResource.transformToOrderedSelect(
2799
2900
  entries,
@@ -3004,9 +3105,12 @@ export function makeTable(options) {
3004
3105
  * @param filtered
3005
3106
  * @param ensure_loaded
3006
3107
  * @param canSkip
3108
+ * @param recordGuard record-level read check (#1241) applied to the record actually being
3109
+ * returned — i.e. AFTER any caching-source revalidation replaces a stale local copy — so an
3110
+ * authorization verdict can't be made on bytes that differ from what the caller receives.
3007
3111
  * @returns
3008
3112
  */
3009
- static transformEntryForSelect(select, context, readTxn, filtered, ensure_loaded?, canSkip?) {
3113
+ static transformEntryForSelect(select, context, readTxn, filtered, ensure_loaded?, canSkip?, recordGuard?) {
3010
3114
  let checkLoaded;
3011
3115
  if (
3012
3116
  ensure_loaded &&
@@ -3025,6 +3129,7 @@ export function makeTable(options) {
3025
3129
  }
3026
3130
  let transformCache;
3027
3131
  const source = this.source;
3132
+ const resourceClass = this;
3028
3133
  // Transform an entry to a record. Note that *this* instance is intended to be the iterator.
3029
3134
  const transform = function (entry: Entry) {
3030
3135
  let record;
@@ -3062,13 +3167,33 @@ export function makeTable(options) {
3062
3167
  message: 'This entry has expired',
3063
3168
  };
3064
3169
  }
3065
- const loadingFromSource = ensureLoadedFromSource(source, entry.key ?? entry, entry, context);
3170
+ // Stale-while-revalidate is an instance method, but a query has no per-row resource
3171
+ // instance to consult (the single-record `get` path passes `this`). Construct one for this
3172
+ // row — via the same `new constructor(id, context)` the framework's getResource uses — so
3173
+ // the hook sees the current row's identity (this.getId()) and record state, matching the
3174
+ // single-record path. It must be a real instance, not the bare class prototype: every
3175
+ // resource prototype chain ends in a tracked-property Proxy, so reading an absent property
3176
+ // (probing for an undefined `allowStaleWhileRevalidate`, or a hook touching `this.x`) on a
3177
+ // non-instance invokes getChanges() with no backing state and throws (harper#1578). We also
3178
+ // load the stale entry into it (as the single-record path does via _updateResource) so a
3179
+ // hook consulting this.getRecord()/this.<field> sees the stale row, not undefined. `entry`
3180
+ // here may be lazy (its `.value` is a GC-able deref, undefined once collected), so we set the
3181
+ // already-dereferenced `record` explicitly rather than relying on `entry.value`. This runs
3182
+ // only for the expired/invalidated rows already headed to source, so the cost is negligible.
3183
+ const swrResource = new resourceClass(entry.key ?? entry, context);
3184
+ resourceClass._updateResource(swrResource, entry);
3185
+ swrResource.setRecord(record);
3186
+ const loadingFromSource = ensureLoadedFromSource(source, entry.key ?? entry, entry, context, swrResource);
3066
3187
  if (loadingFromSource?.then) {
3067
3188
  return loadingFromSource.then(transform);
3068
3189
  }
3069
3190
  }
3070
3191
  }
3071
3192
  if (record == null) return canSkip ? SKIP : record;
3193
+ // Record-level RBAC (#1241): enforced here because `record` is now the final, materialized
3194
+ // record — a caching table's source revalidation (above) may have replaced the local copy the
3195
+ // query filters evaluated. Fail closed on the record actually being returned.
3196
+ if (recordGuard && !recordGuard(record)) return canSkip ? SKIP : undefined;
3072
3197
  if (select && !(select[0] === '*' && select.length === 1)) {
3073
3198
  let promises: Promise<any>[];
3074
3199
  const selectAttribute = (attribute, callback) => {
@@ -3233,6 +3358,9 @@ export function makeTable(options) {
3233
3358
  // snapshot:false, which catches any commits that land during yield points; dropping in the
3234
3359
  // listener avoids duplicate delivery.
3235
3360
  let dropDuringReplay = false;
3361
+ // Coalescing guards for the reload re-snapshot (harper-pro#495), driven from the listener below.
3362
+ let reloadResnapshotRunning = false;
3363
+ let reloadResnapshotPending = false;
3236
3364
  const thisId = requestTargetToId(request) ?? null; // treat undefined and null as the root
3237
3365
  const subscription = addSubscription(
3238
3366
  TableResource,
@@ -3246,9 +3374,15 @@ export function makeTable(options) {
3246
3374
  // we only send the full message, this are individual messages that can be sent out of order
3247
3375
  // TODO: Do we want to have a limit to how far out-of-order we are willing to send?
3248
3376
  value = auditRecord.getValue?.(primaryStore, getFullRecord);
3249
- } else if (type !== 'end_txn' && type !== 'reload') {
3250
- // 'reload' is a whole-table marker with a null id and no record (harper-pro#489); pass it
3251
- // through verbatim so subscribers re-read the table, skipping the per-record lookup below.
3377
+ } else if (type === 'reload') {
3378
+ // Whole-table marker with a null id and no record (harper-pro#489): a copyApply base copy
3379
+ // back-filled rows with no per-row audit events. For a user table, re-deliver the current
3380
+ // scope as 'put's so MQTT/SSE/WS subscribers recover the snapshotted records, and do NOT
3381
+ // forward the bare marker (harper-pro#495). System-DB subscribers (knownNodes etc.) instead
3382
+ // get the raw marker and run their own bespoke whole-table rescan.
3383
+ if (databaseName !== 'system') return scheduleReloadResnapshot();
3384
+ // system DB: fall through to forward the bare 'reload' event verbatim.
3385
+ } else if (type !== 'end_txn') {
3252
3386
  // these are events that indicate that the primary record has changed. I believe we always want to simply
3253
3387
  // send the latest value. Note that it is fine to synchronously access these records, they should have just
3254
3388
  // been written, so are fresh in memory.
@@ -3369,7 +3503,7 @@ export function makeTable(options) {
3369
3503
  logger.error?.('Error getting history entry', auditRecord.localTime, error);
3370
3504
  }
3371
3505
  }
3372
- for (let i = history.length; i > 0; ) {
3506
+ for (let i = history.length; i > 0;) {
3373
3507
  send(history[--i]);
3374
3508
  }
3375
3509
  // Use the latest record cursor saw (history[0] = most recent due to reverse
@@ -3478,7 +3612,7 @@ export function makeTable(options) {
3478
3612
  } else break;
3479
3613
  if (count) count--;
3480
3614
  } while (nextTime > startTime && count !== 0);
3481
- for (let i = history.length; i > 0; ) {
3615
+ for (let i = history.length; i > 0;) {
3482
3616
  send(history[--i]);
3483
3617
  }
3484
3618
  }
@@ -3509,6 +3643,78 @@ export function makeTable(options) {
3509
3643
  }
3510
3644
  subscription.send(event);
3511
3645
  }
3646
+ // #region reload re-snapshot (harper-pro#495)
3647
+ // A copyApply base copy back-fills rows as snapshots with NO per-row audit entries, so the live
3648
+ // listener above never fires for them and an already-connected subscriber would miss them until
3649
+ // the next direct write. After the copy, a whole-table 'reload' marker is delivered here (id=null).
3650
+ // For a user table we react by re-delivering the subscription's current scope as ordinary 'put'
3651
+ // events — so EVERY consumer that funnels through subscribe() (MQTT, SSE, WS) recovers the records
3652
+ // uniformly, with no per-protocol handling. System-DB reloads are NOT re-snapshotted: their
3653
+ // subscribers (knownNodes peer-discovery, hdb_certificate CA install) run a bespoke whole-table
3654
+ // rescan off the raw marker, which a per-row re-emit cannot express (it can't drop stale rows).
3655
+ // Yields the latest committed value (snapshot:false) and skips tombstones, mirroring the
3656
+ // omitCurrent initial-snapshot scan.
3657
+ async function* currentScopeRecords() {
3658
+ const isCollection = request.isCollection ?? thisId == null;
3659
+ if (isCollection) {
3660
+ let sinceYield = 0;
3661
+ for (const { key: id, value, version, localTime, size } of primaryStore.getRange({
3662
+ start: thisId ?? false,
3663
+ end: thisId == null ? undefined : [thisId, MAXIMUM_KEY],
3664
+ versions: true,
3665
+ snapshot: false, // no need for a snapshot, just want the latest data
3666
+ })) {
3667
+ if (++sinceYield >= REPLAY_YIELD_INTERVAL) {
3668
+ sinceYield = 0;
3669
+ await rest();
3670
+ }
3671
+ if (!value) continue; // skip tombstones
3672
+ yield { id, localTime, value, version, type: 'put', size };
3673
+ }
3674
+ } else {
3675
+ const entry = primaryStore.getEntry(thisId);
3676
+ if (entry?.value) yield { id: thisId, ...entry, type: 'put' };
3677
+ }
3678
+ }
3679
+ // Drain the current scope into the subscription with the same back-pressure as the live path.
3680
+ // Coalesced: a marker that arrives while a re-snapshot is running just re-arms it once more (so
3681
+ // markers for several tables, or a marker landing mid-scan, are not lost), and we never run two
3682
+ // scans concurrently. The first thing it does is `await rest()` (a setImmediate macrotask), so the
3683
+ // scan never executes inside the synchronous broadcast listener — which on the same-thread
3684
+ // aftercommit path holds an inter-thread lock that must not span event-loop turns.
3685
+ async function runReloadResnapshot() {
3686
+ reloadResnapshotRunning = true;
3687
+ try {
3688
+ await rest(); // defer off the broadcast listener's stack before scanning
3689
+ while (reloadResnapshotPending) {
3690
+ reloadResnapshotPending = false;
3691
+ // Subscription.end() nulls `subscriptions`; bail the moment it closes — before, between,
3692
+ // or mid-scan — so we never scan + send into a dead queue. pending is already cleared, so
3693
+ // the finally re-arm won't re-loop.
3694
+ if (!subscription.subscriptions) return;
3695
+ for await (const record of currentScopeRecords()) {
3696
+ if (!subscription.subscriptions) return;
3697
+ send(record);
3698
+ if (subscription.queue?.length > EVENT_HIGH_WATER_MARK) {
3699
+ if ((await subscription.waitForDrain()) === false) return;
3700
+ }
3701
+ }
3702
+ }
3703
+ } catch (error) {
3704
+ harperLogger.error?.('Error in reload re-snapshot:', error);
3705
+ } finally {
3706
+ reloadResnapshotRunning = false;
3707
+ // A marker that landed after the last pending-check but before we cleared the flag would
3708
+ // otherwise be dropped — re-arm if so (unless the subscription has since closed).
3709
+ if (subscription.subscriptions && reloadResnapshotPending) scheduleReloadResnapshot();
3710
+ }
3711
+ }
3712
+ function scheduleReloadResnapshot() {
3713
+ if (!subscription.subscriptions) return;
3714
+ reloadResnapshotPending = true;
3715
+ if (!reloadResnapshotRunning) runReloadResnapshot();
3716
+ }
3717
+ // #endregion
3512
3718
  return subscription;
3513
3719
  }
3514
3720
 
@@ -3704,12 +3910,10 @@ export function makeTable(options) {
3704
3910
  );
3705
3911
  break;
3706
3912
  case 'ID':
3707
- if (
3708
- !(
3709
- typeof value === 'string' ||
3710
- (value?.length > 0 && value.every?.((value) => typeof value === 'string'))
3711
- )
3712
- )
3913
+ if (!(
3914
+ typeof value === 'string' ||
3915
+ (value?.length > 0 && value.every?.((value) => typeof value === 'string'))
3916
+ ))
3713
3917
  (validationErrors || (validationErrors = [])).push(
3714
3918
  `Value ${stringify(value)} in property ${name} must be a string, or an array of strings`
3715
3919
  );
@@ -4054,7 +4258,7 @@ export function makeTable(options) {
4054
4258
  });
4055
4259
  if (value?.then) hasPromises = true;
4056
4260
  // for now, we shouldn't be getting promises until rocksdb
4057
- if (TableResource.loadAsInstance === false) Object.freeze(returnEntry ? value?.value : value);
4261
+ if (TableResource.loadAsInstance === false) freezeRecord(returnEntry ? value?.value : value);
4058
4262
  return value;
4059
4263
  });
4060
4264
  return relationship.filterMissing
@@ -4069,7 +4273,7 @@ export function makeTable(options) {
4069
4273
  transaction: txnForContext(context).getReadTxn(),
4070
4274
  });
4071
4275
  // for now, we shouldn't be getting promises until rocksdb
4072
- if (TableResource.loadAsInstance === false) Object.freeze(returnEntry ? value?.value : value);
4276
+ if (TableResource.loadAsInstance === false) freezeRecord(returnEntry ? value?.value : value);
4073
4277
  return value;
4074
4278
  };
4075
4279
  attribute.set = (object, related) => {
@@ -4440,7 +4644,7 @@ export function makeTable(options) {
4440
4644
  // we need to freeze entry records to ensure the integrity of the cache;
4441
4645
  // but we only do this when users have opted into loadAsInstance/freezeRecords to avoid back-compat
4442
4646
  // issues
4443
- Object.freeze(entry?.value);
4647
+ freezeRecord(entry?.value);
4444
4648
  if (
4445
4649
  entry?.residencyId &&
4446
4650
  entry.metadataFlags & INVALIDATED &&
@@ -4557,6 +4761,11 @@ export function makeTable(options) {
4557
4761
  }
4558
4762
  }
4559
4763
 
4764
+ function setLoadedFromSource(target: RequestTarget | undefined, loadedFromSource: boolean) {
4765
+ // cache disposition is a per-get result, recorded on the RequestTarget of the get (#1576)
4766
+ // target may be a primitive id on instance-API calls, which can't hold the flag
4767
+ if (target && typeof target === 'object') target.loadedFromSource = loadedFromSource;
4768
+ }
4560
4769
  function ensureLoadedFromSource(source: typeof TableResource, id, entry, context, resource?, target?) {
4561
4770
  if (context?.onlyIfCached) {
4562
4771
  if (!entry?.value) throw new ServerError('Entry is not cached', 504);
@@ -4789,7 +4998,7 @@ export function makeTable(options) {
4789
4998
  whenResolved(getFromSource(source, id, primaryStore.getEntry(id), context, target));
4790
4999
  else {
4791
5000
  // served from cache after waiting for another request to resolve
4792
- if (target) target.loadedFromSource = false;
5001
+ setLoadedFromSource(target, false);
4793
5002
  whenResolved(entry);
4794
5003
  }
4795
5004
  };
@@ -4804,7 +5013,7 @@ export function makeTable(options) {
4804
5013
  });
4805
5014
  }
4806
5015
  // lock acquired — this request will actually load from source
4807
- if (target) target.loadedFromSource = true;
5016
+ setLoadedFromSource(target, true);
4808
5017
 
4809
5018
  const existingRecord = existingEntry?.value;
4810
5019
  // it is important to remember that this is _NOT_ part of the current transaction; nothing is changing
@@ -192,52 +192,47 @@ interface GetAnalyticsOpts {
192
192
 
193
193
  export async function get(metric: string, opts?: GetAnalyticsOpts): Promise<Metric[]> {
194
194
  const { getAttributes, startTime, endTime, additionalConditions, log: logName } = opts ?? {};
195
-
196
- // A two-sided time window bounds the primary-key (`id` = [time, nodeId]) range on both ends. When we
197
- // have one, lead with it and pin execution order (below) so it drives the scan. `Table.search`
198
- // otherwise reorders conditions by estimated cost, but the planner estimates a primary-key `between`
199
- // as a flat 10% of the table (search.ts BETWEEN_ESTIMATE) rather than from the actual range width —
200
- // so on a large `hdb_analytics` table a narrow window looks costlier than `metric equals`, iteration
201
- // is driven off the metric index, and the metric's entire history is decoded instead of the window
202
- // (#1796). The proper long-term fix is a range-width-aware cost estimate in search.ts.
203
- const boundedWindow = startTime != null && endTime != null;
204
- const conditions: Conditions = [];
205
- if (boundedWindow) {
206
- conditions.push({ attribute: 'id', comparator: 'between', value: [startTime, endTime] });
207
- }
208
- conditions.push({ attribute: 'metric', comparator: 'equals', value: metric });
195
+ const conditions: Conditions = [{ attribute: 'metric', comparator: 'equals', value: metric }];
209
196
  if (logName !== undefined) {
210
197
  conditions.push({ attribute: 'log', comparator: 'equals', value: logName });
211
198
  }
212
199
  if (additionalConditions) {
213
200
  conditions.push(...additionalConditions.map(conformCondition));
214
201
  }
215
- // An open-ended range (only start or only end) can span the whole table, so we append it and leave
216
- // ordering to the planner rather than forcing an unbounded range to drive — which could scan the
217
- // full table for a selective metric.
218
- if (!boundedWindow) {
219
- if (startTime != null) {
220
- conditions.push({ attribute: 'id', comparator: 'greater_than_equal', value: startTime });
221
- }
222
- if (endTime != null) {
223
- conditions.push({ attribute: 'id', comparator: 'less_than', value: endTime });
224
- }
225
- }
226
-
227
202
  const select = getAttributes ?? [];
228
203
 
229
- // ensure we're always selecting id (an empty select array already selects everything, including id)
204
+ // ensure we're always selecting id
230
205
  if (!isSelected(select, 'id')) {
231
206
  select.push('id');
232
207
  }
233
208
 
209
+ if (startTime && endTime) {
210
+ conditions.push({
211
+ attribute: 'id',
212
+ comparator: 'between',
213
+ value: [startTime, endTime],
214
+ });
215
+ } else {
216
+ if (startTime) {
217
+ conditions.push({
218
+ attribute: 'id',
219
+ comparator: 'greater_than_equal',
220
+ value: startTime,
221
+ });
222
+ }
223
+ if (endTime) {
224
+ conditions.push({
225
+ attribute: 'id',
226
+ comparator: 'less_than',
227
+ value: endTime,
228
+ });
229
+ }
230
+ }
231
+
234
232
  // `snapshot: false` lets these (potentially long-running) analytics scans read against the
235
233
  // latest committed data without holding a consistent read snapshot open, so they stay easier
236
234
  // on the rest of the system (a pinned snapshot would block compaction for the scan's duration).
237
235
  const request: any = { conditions, allowConditionsOnDynamicAttributes: true, snapshot: false };
238
- if (boundedWindow) {
239
- request.enforceExecutionOrder = true;
240
- }
241
236
  if (select.length > 0) {
242
237
  request['select'] = select;
243
238
  }