@harperfast/harper 5.3.0-beta.4 → 5.3.0

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 (314) hide show
  1. package/bin/backup.ts +6 -1
  2. package/components/Application.ts +486 -115
  3. package/components/DESIGN.md +109 -21
  4. package/components/deploymentProvenance.ts +28 -0
  5. package/components/operations.js +14 -7
  6. package/components/packageComponent.ts +5 -1
  7. package/config-root.schema.json +1 -1
  8. package/dataLayer/DESIGN.md +35 -7
  9. package/dataLayer/blobBackup.ts +71 -30
  10. package/dataLayer/harperBridge/ResourceBridge.ts +16 -2
  11. package/dataLayer/rocksdbBackup.ts +88 -34
  12. package/dataLayer/schemaDescribe.ts +45 -1
  13. package/dist/bin/backup.js +1 -1
  14. package/dist/bin/backup.js.map +1 -1
  15. package/dist/components/Application.d.ts +11 -21
  16. package/dist/components/Application.js +436 -105
  17. package/dist/components/Application.js.map +1 -1
  18. package/dist/components/deploymentProvenance.d.ts +4 -0
  19. package/dist/components/deploymentProvenance.js +32 -0
  20. package/dist/components/deploymentProvenance.js.map +1 -0
  21. package/dist/components/operations.js +14 -7
  22. package/dist/components/operations.js.map +1 -1
  23. package/dist/components/packageComponent.js +6 -1
  24. package/dist/components/packageComponent.js.map +1 -1
  25. package/dist/dataLayer/blobBackup.d.ts +30 -3
  26. package/dist/dataLayer/blobBackup.js +73 -36
  27. package/dist/dataLayer/blobBackup.js.map +1 -1
  28. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +1 -0
  29. package/dist/dataLayer/harperBridge/ResourceBridge.js +15 -3
  30. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  31. package/dist/dataLayer/rocksdbBackup.d.ts +4 -1
  32. package/dist/dataLayer/rocksdbBackup.js +74 -36
  33. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  34. package/dist/dataLayer/schemaDescribe.js +45 -1
  35. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  36. package/dist/resources/DatabaseTransaction.d.ts +1 -0
  37. package/dist/resources/DatabaseTransaction.js +33 -8
  38. package/dist/resources/DatabaseTransaction.js.map +1 -1
  39. package/dist/resources/ResourceInterface.d.ts +7 -3
  40. package/dist/resources/ResourceInterface.js +2 -0
  41. package/dist/resources/ResourceInterface.js.map +1 -1
  42. package/dist/resources/Table.d.ts +151 -445
  43. package/dist/resources/Table.js +209 -8
  44. package/dist/resources/Table.js.map +1 -1
  45. package/dist/resources/analytics/write.d.ts +11 -0
  46. package/dist/resources/analytics/write.js +3 -0
  47. package/dist/resources/analytics/write.js.map +1 -1
  48. package/dist/resources/blob.d.ts +30 -0
  49. package/dist/resources/blob.js +189 -1
  50. package/dist/resources/blob.js.map +1 -1
  51. package/dist/resources/databases.d.ts +1 -0
  52. package/dist/resources/databases.js +80 -15
  53. package/dist/resources/databases.js.map +1 -1
  54. package/dist/resources/derivedIndexRuntime.d.ts +13 -0
  55. package/dist/resources/derivedIndexRuntime.js +57 -5
  56. package/dist/resources/derivedIndexRuntime.js.map +1 -1
  57. package/dist/resources/derivedIndexes.d.ts +1 -0
  58. package/dist/resources/derivedIndexes.js +116 -15
  59. package/dist/resources/derivedIndexes.js.map +1 -1
  60. package/dist/resources/fullTextSchema.d.ts +5 -3
  61. package/dist/resources/fullTextSchema.js +48 -5
  62. package/dist/resources/fullTextSchema.js.map +1 -1
  63. package/dist/resources/fullTextSchemaLifecycle.d.ts +2 -0
  64. package/dist/resources/fullTextSchemaLifecycle.js +33 -0
  65. package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
  66. package/dist/resources/graphql.js +30 -12
  67. package/dist/resources/graphql.js.map +1 -1
  68. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +21 -1
  69. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +114 -5
  70. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  71. package/dist/resources/indexes/fullTextDerivedIndex.d.ts +11 -2
  72. package/dist/resources/indexes/fullTextDerivedIndex.js +187 -24
  73. package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -1
  74. package/dist/resources/indexes/fullTextNativeBinding.d.ts +99 -1
  75. package/dist/resources/indexes/fullTextNativeBinding.js +35 -1
  76. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
  77. package/dist/resources/indexes/fullTextQueryIndex.d.ts +58 -0
  78. package/dist/resources/indexes/fullTextQueryIndex.js +943 -0
  79. package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -0
  80. package/dist/resources/indexes/fullTextQueryProtocol.d.ts +6 -0
  81. package/dist/resources/indexes/fullTextQueryProtocol.js +32 -0
  82. package/dist/resources/indexes/fullTextQueryProtocol.js.map +1 -0
  83. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +4 -1
  84. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +30 -7
  85. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
  86. package/dist/resources/search.d.ts +41 -0
  87. package/dist/resources/search.js +468 -26
  88. package/dist/resources/search.js.map +1 -1
  89. package/dist/resources/transactionBroadcast.js +101 -50
  90. package/dist/resources/transactionBroadcast.js.map +1 -1
  91. package/dist/resources/typeParity.d.ts +9 -0
  92. package/dist/resources/typeParity.js +3 -0
  93. package/dist/resources/typeParity.js.map +1 -0
  94. package/dist/server/REST.js.map +1 -1
  95. package/dist/server/itc/serverHandlers.js +48 -8
  96. package/dist/server/itc/serverHandlers.js.map +1 -1
  97. package/dist/server/serverHelpers/contentTypes.d.ts +7 -0
  98. package/dist/server/serverHelpers/contentTypes.js +36 -23
  99. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  100. package/dist/server/serverHelpers/uwsServer.js +28 -5
  101. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  102. package/dist/server/threads/itc.js +19 -0
  103. package/dist/server/threads/itc.js.map +1 -1
  104. package/dist/server/threads/manageThreads.d.ts +3 -0
  105. package/dist/server/threads/manageThreads.js +90 -23
  106. package/dist/server/threads/manageThreads.js.map +1 -1
  107. package/dist/utility/operation_authorization.js +25 -7
  108. package/dist/utility/operation_authorization.js.map +1 -1
  109. package/dist/utility/signalling.js +3 -0
  110. package/dist/utility/signalling.js.map +1 -1
  111. package/dist/validation/searchValidator.js +22 -2
  112. package/dist/validation/searchValidator.js.map +1 -1
  113. package/npm-shrinkwrap.json +22 -22
  114. package/package.json +2 -4
  115. package/resources/DESIGN.md +19 -2
  116. package/resources/DatabaseTransaction.ts +40 -10
  117. package/resources/FULL_TEXT.md +97 -0
  118. package/resources/ResourceInterface.ts +8 -2
  119. package/resources/Table.ts +767 -17
  120. package/resources/analytics/write.ts +3 -3
  121. package/resources/blob.ts +183 -1
  122. package/resources/databases.ts +126 -14
  123. package/resources/derivedIndexRuntime.ts +79 -6
  124. package/resources/derivedIndexes.ts +142 -18
  125. package/resources/fullTextSchema.ts +55 -9
  126. package/resources/fullTextSchemaLifecycle.ts +45 -0
  127. package/resources/graphql.ts +33 -12
  128. package/resources/indexes/DESIGN.md +167 -15
  129. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +125 -5
  130. package/resources/indexes/fullTextDerivedIndex.ts +216 -32
  131. package/resources/indexes/fullTextNativeBinding.ts +133 -3
  132. package/resources/indexes/fullTextQueryIndex.ts +1120 -0
  133. package/resources/indexes/fullTextQueryProtocol.ts +35 -0
  134. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +28 -8
  135. package/resources/search.ts +513 -26
  136. package/resources/transactionBroadcast.ts +86 -47
  137. package/resources/typeParity.ts +20 -0
  138. package/schema.graphql +18 -5
  139. package/server/DESIGN.md +18 -0
  140. package/server/REST.ts +1 -1
  141. package/server/itc/serverHandlers.js +61 -8
  142. package/server/serverHelpers/contentTypes.ts +31 -26
  143. package/server/serverHelpers/uwsServer.ts +35 -7
  144. package/server/threads/itc.js +21 -0
  145. package/server/threads/manageThreads.js +95 -21
  146. package/studio/web/assets/{Chat-Bq60AO-g.js → Chat-CNEKMllJ.js} +13 -13
  147. package/studio/web/assets/FloatingChat-CEnWhHeR.js +23 -0
  148. package/studio/web/assets/SearchDialog-CwhJdEdY.js +1 -0
  149. package/studio/web/assets/{abnfDiagram-O67JEVCF-DpPZWMOf.js → abnfDiagram-O67JEVCF-raXE027J.js} +1 -1
  150. package/studio/web/assets/alertDialog-BbWD9Xxa.js +1 -0
  151. package/studio/web/assets/apiToken-DC5vsaaO.js +1 -0
  152. package/studio/web/assets/applications-DQZxDy0x.js +296 -0
  153. package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +1 -0
  154. package/studio/web/assets/{architectureDiagram-NJMV4G6O-OmgLraID.js → architectureDiagram-NJMV4G6O-C8_Eet7w.js} +1 -1
  155. package/studio/web/assets/arrow-up-D2g_57vT.js +1 -0
  156. package/studio/web/assets/authStore-CJCaADw9.js +3 -0
  157. package/studio/web/assets/{blockDiagram-BEXU5L5S-Cv7CD3Ai.js → blockDiagram-BEXU5L5S-DR6S5-1D.js} +1 -1
  158. package/studio/web/assets/{button-MFP9n8Ck.js → button-DWwfgGWI.js} +2 -2
  159. package/studio/web/assets/{c4Diagram-YGBWAQC7-Ds1vKYnN.js → c4Diagram-YGBWAQC7-BAfZ1G01.js} +1 -1
  160. package/studio/web/assets/channel-D0AkPjJ8.js +1 -0
  161. package/studio/web/assets/{chevron-up-CFe8x5Bp.js → chevron-up-CFxbMGv2.js} +1 -1
  162. package/studio/web/assets/{chunk-3FUC2YCW-DpnBPH8B.js → chunk-3FUC2YCW-DuRp-j19.js} +1 -1
  163. package/studio/web/assets/{chunk-5DYCD2WN-LLLCQSzg.js → chunk-5DYCD2WN-BceBfIHT.js} +1 -1
  164. package/studio/web/assets/{chunk-742MDFTN-BL8J_pIO.js → chunk-742MDFTN-CIkt8ijt.js} +1 -1
  165. package/studio/web/assets/{chunk-7INBJB4K-C-hJHiiK.js → chunk-7INBJB4K-BpeAXyOi.js} +1 -1
  166. package/studio/web/assets/{chunk-7M6MHVWA-C6ZqkFbA.js → chunk-7M6MHVWA-h6WlaTzO.js} +1 -1
  167. package/studio/web/assets/{chunk-7PRAP22T-CUXM8BG6.js → chunk-7PRAP22T-KyqX6x3f.js} +1 -1
  168. package/studio/web/assets/{chunk-GTNCS2PH-DJ2BFvS1.js → chunk-GTNCS2PH-DGHLXyow.js} +1 -1
  169. package/studio/web/assets/{chunk-GWA4HPMP-C42bSqLL.js → chunk-GWA4HPMP-DsejB6gA.js} +1 -1
  170. package/studio/web/assets/{chunk-MBY4JIJT-C-5jz6Lo.js → chunk-MBY4JIJT-BwSGweQP.js} +1 -1
  171. package/studio/web/assets/{chunk-NETBCI7D-D-bzlN4r.js → chunk-NETBCI7D-C8UnOw_f.js} +1 -1
  172. package/studio/web/assets/{chunk-O7XYJQB3-ep14zA23.js → chunk-O7XYJQB3-CBS3nZYV.js} +1 -1
  173. package/studio/web/assets/{chunk-UA2S7LBM-BMRWRHED.js → chunk-UA2S7LBM-BHpEdH8D.js} +1 -1
  174. package/studio/web/assets/{chunk-WEXAMYUT-DdLYVdX1.js → chunk-WEXAMYUT-BAIhTwyh.js} +1 -1
  175. package/studio/web/assets/{chunk-Z7XXMR3K-BmKEDjcP.js → chunk-Z7XXMR3K-DSHbImdf.js} +1 -1
  176. package/studio/web/assets/{chunk-ZIGJFQKS-DLZgdNxA.js → chunk-ZIGJFQKS-Cs8fUn7y.js} +1 -1
  177. package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DOI5vgKT.js → classDiagram-v2-NBCMYWYE-DmDPkt2u.js} +1 -1
  178. package/studio/web/assets/cn-6T8VranJ.js +1 -0
  179. package/studio/web/assets/{codicon-Brq4_Ui5.ttf → codicon-DF1abBS2.ttf} +0 -0
  180. package/studio/web/assets/{createLucideIcon-bziYxJli.js → createLucideIcon-fO5dL7D5.js} +1 -1
  181. package/studio/web/assets/{css.worker-l1psR_wG.js → css.worker-DoT-OOzx.js} +36 -36
  182. package/studio/web/assets/{cssMode-DJyc9mHj.js → cssMode-Dz2DxF4e.js} +1 -1
  183. package/studio/web/assets/{cynefin-OW5HDTMX-BSW-DB7S.js → cynefin-OW5HDTMX-DIvbXLSj.js} +1 -1
  184. package/studio/web/assets/{cynefinDiagram-VND7K2PF-CbAHEwxt.js → cynefinDiagram-VND7K2PF-BdtaXiCE.js} +1 -1
  185. package/studio/web/assets/{dagre-6A5THRUB-Csxewgs4.js → dagre-6A5THRUB-C7RUWHyz.js} +1 -1
  186. package/studio/web/assets/{diagram-22UHCM2B-a0rt8tY4.js → diagram-22UHCM2B-DJCvcaWP.js} +1 -1
  187. package/studio/web/assets/{diagram-3UASUU5V-lEj2SVNh.js → diagram-3UASUU5V-Du6VKWln.js} +1 -1
  188. package/studio/web/assets/{diagram-ATOU4E4O-HcrAeB-q.js → diagram-ATOU4E4O-dxRfeG4B.js} +1 -1
  189. package/studio/web/assets/{diagram-CDSNMT55-CYxiuyEx.js → diagram-CDSNMT55-l5Ga0u-W.js} +1 -1
  190. package/studio/web/assets/{diagram-MLGK6HIB-ChbCJTgJ.js → diagram-MLGK6HIB-CNQY47LA.js} +1 -1
  191. package/studio/web/assets/{diagram-MPIPVDR6-DaX9Y-N3.js → diagram-MPIPVDR6-7prveD1B.js} +1 -1
  192. package/studio/web/assets/{dist-Cz2Y6-6G.js → dist-BrRuMUzt.js} +1 -1
  193. package/studio/web/assets/{download-CtostjYn.js → download-Cn6tPG4L.js} +1 -1
  194. package/studio/web/assets/{ebnfDiagram-ZINNZB2B-CwAsENB1.js → ebnfDiagram-ZINNZB2B-5OnkTpfi.js} +1 -1
  195. package/studio/web/assets/{editor-DDIsQfj6.js → editor-B3mjd3tz.js} +1 -1
  196. package/studio/web/assets/editor.worker-Yd1NR0QT.js +33 -0
  197. package/studio/web/assets/{elk-276RUBZZ-Hexg9E4D.js → elk-276RUBZZ-DxDQPjQT.js} +1 -1
  198. package/studio/web/assets/{erDiagram-OPXOYQCR-CeC-wwO1.js → erDiagram-OPXOYQCR-CQlCX84D.js} +1 -1
  199. package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +1 -0
  200. package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +1 -0
  201. package/studio/web/assets/{ganttDiagram-FUAMR5RP-C2_Hhl1-.js → ganttDiagram-FUAMR5RP-bloX3Voy.js} +1 -1
  202. package/studio/web/assets/{getAnalytics-CjthMQqF.js → getAnalytics-C0FHjtdQ.js} +1 -1
  203. package/studio/web/assets/{gitGraph-4MIJSDKK-xIk5Vuew.js → gitGraph-4MIJSDKK-BrXWmJt2.js} +1 -1
  204. package/studio/web/assets/{gitGraphDiagram-X574FWY7-CMxJ34up.js → gitGraphDiagram-X574FWY7-C2KnnZeY.js} +1 -1
  205. package/studio/web/assets/{html-g1BJHxLy.js → html-BsRGM7tp.js} +1 -1
  206. package/studio/web/assets/{html.worker-DdwM4IrA.js → html.worker-B1dwY0Ay.js} +27 -27
  207. package/studio/web/assets/{htmlMode-Vh5IglCQ.js → htmlMode-tUKJaA3o.js} +1 -1
  208. package/studio/web/assets/index-72cEH474.css +1 -0
  209. package/studio/web/assets/index-BuqFjN5K.js +828 -0
  210. package/studio/web/assets/index.lazy-CcpfQFY_.js +2 -0
  211. package/studio/web/assets/{info-A6RAGUB7-o49TEbj-.js → info-A6RAGUB7-BhzBHe4U.js} +1 -1
  212. package/studio/web/assets/{infoDiagram-VRGFBTTK-Cjhh7yzZ.js → infoDiagram-VRGFBTTK-B0J1132p.js} +1 -1
  213. package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-C63aXRmh.js → ishikawaDiagram-OU5B5YK6-BNk0tuQx.js} +1 -1
  214. package/studio/web/assets/{javascript-5VIolqf7.js → javascript-BZ_uRGHl.js} +1 -1
  215. package/studio/web/assets/{journeyDiagram-ZHPQQLJL-D_o0P5Mu.js → journeyDiagram-ZHPQQLJL-Be79RMQM.js} +1 -1
  216. package/studio/web/assets/json.worker-BQqXGI3X.js +65 -0
  217. package/studio/web/assets/{jsonMode-DG4zQraI.js → jsonMode-DgB-aFMj.js} +1 -1
  218. package/studio/web/assets/{kanban-definition-PNTS6WVX-CTI9SSxY.js → kanban-definition-PNTS6WVX-4uuoQP9W.js} +1 -1
  219. package/studio/web/assets/{languageServices-t5FBpX-i.js → languageServices-BleuBw17.js} +1 -1
  220. package/studio/web/assets/localStorageKeys-etzFY_xV.js +1 -0
  221. package/studio/web/assets/{lspLanguageFeatures-DgVnNPh-.js → lspLanguageFeatures-ByVYLSsQ.js} +1 -1
  222. package/studio/web/assets/{mermaid-parser.core-C2lcH_jP.js → mermaid-parser.core-V1UYMeXV.js} +3 -3
  223. package/studio/web/assets/{mermaid.core-BG5bjfIG.js → mermaid.core-1PX6R49U.js} +4 -4
  224. package/studio/web/assets/{mindmap-definition-NLK3R4M7-BK7ho6JD.js → mindmap-definition-NLK3R4M7-C70Ctm0J.js} +1 -1
  225. package/studio/web/assets/notifications-D1Nf-Edn.js +1 -0
  226. package/studio/web/assets/notifications-TuzN0IMi.js +1 -0
  227. package/studio/web/assets/{packet-AYTQ26CC-BHPy6KBV.js → packet-AYTQ26CC-DWbXHx5g.js} +1 -1
  228. package/studio/web/assets/{pegDiagram-GJSIUBJH-DzjwG3jO.js → pegDiagram-GJSIUBJH-d6z1cc7W.js} +1 -1
  229. package/studio/web/assets/{pie-WAS4IAKB-D0cP-DTU.js → pie-WAS4IAKB-DjoTm3yr.js} +1 -1
  230. package/studio/web/assets/{pieDiagram-5QR66LMP-C_hOP9Lv.js → pieDiagram-5QR66LMP-Dz3RHpDA.js} +1 -1
  231. package/studio/web/assets/profile-D7C7Lupz.js +1 -0
  232. package/studio/web/assets/{quadrantDiagram-O4NWA36T-CZW8PbV1.js → quadrantDiagram-O4NWA36T-CIF5Qkou.js} +1 -1
  233. package/studio/web/assets/{radar-RG4KPBEZ-C8fk0U81.js → radar-RG4KPBEZ-CVBgjCOj.js} +1 -1
  234. package/studio/web/assets/{railroad-74A4TZTK-B60FWm17.js → railroad-74A4TZTK-BdhXw10Q.js} +1 -1
  235. package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +1 -0
  236. package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +1 -0
  237. package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +1 -0
  238. package/studio/web/assets/{railroadDiagram-XR7U4H2S-DyyNmgbU.js → railroadDiagram-XR7U4H2S-y3Jv2U6N.js} +1 -1
  239. package/studio/web/assets/regions-nGQCm-v8.js +1 -0
  240. package/studio/web/assets/register-C3x9Ze6P.js +909 -0
  241. package/studio/web/assets/register-DjpsE8to.css +1 -0
  242. package/studio/web/assets/{requirementDiagram-PLB6GJNP-C4H-mLKu.js → requirementDiagram-PLB6GJNP-ChINZEmu.js} +1 -1
  243. package/studio/web/assets/restartGate-nHAFLDh-.js +1 -0
  244. package/studio/web/assets/{sankeyDiagram-IPEJSGJF-DHDyga4c.js → sankeyDiagram-IPEJSGJF-D9AyLce2.js} +1 -1
  245. package/studio/web/assets/{sequenceDiagram-PO4LG4MO-CdNcHXRI.js → sequenceDiagram-PO4LG4MO-hfk4aIEm.js} +1 -1
  246. package/studio/web/assets/{setComponentFile-z4-qJCbA.js → setComponentFile-DpjrRqeG.js} +1 -1
  247. package/studio/web/assets/{setup-CS50uJ4x.js → setup-CGiKLZ9e.js} +2 -2
  248. package/studio/web/assets/{stateDiagram-v2-GCMORJYK-CpKuXq15.js → stateDiagram-v2-GCMORJYK-Bs8RIb9K.js} +1 -1
  249. package/studio/web/assets/status-CGJwrNPH.js +61 -0
  250. package/studio/web/assets/{swimlanes-2SLR337P-NRHa2D8I.js → swimlanes-2SLR337P-B52xrf6t.js} +1 -1
  251. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +8 -0
  252. package/studio/web/assets/tabs-BCTQzoQK.js +1 -0
  253. package/studio/web/assets/{timeline-definition-EJHVYXUP-D39Y-xpq.js → timeline-definition-EJHVYXUP-DJSITpQX.js} +1 -1
  254. package/studio/web/assets/toggleHighContrast-C-Ne6P7T.js +51 -0
  255. package/studio/web/assets/toggleHighContrast-n7clI0si.css +1 -0
  256. package/studio/web/assets/{treeView-Q6P3EWNA-CRWR9OqD.js → treeView-Q6P3EWNA-C-epdxRo.js} +1 -1
  257. package/studio/web/assets/{treemap-WGGIJYW6-ZfXyJgLO.js → treemap-WGGIJYW6-DOVyp_zw.js} +1 -1
  258. package/studio/web/assets/{ts.worker-DDMUrpqh.js → ts.worker-Clm12uY6.js} +131 -131
  259. package/studio/web/assets/{tsMode-D83Gg3cq.js → tsMode-CSreYTys.js} +1 -1
  260. package/studio/web/assets/{typescript-Dfp-GrNq.js → typescript-MnxBk89W.js} +1 -1
  261. package/studio/web/assets/{useEntityRestURL-BKetlINf.js → useEntityRestURL-TNrh77-s.js} +1 -1
  262. package/studio/web/assets/{usecaseDiagram-POWQR4AR-jf-zIDZ_.js → usecaseDiagram-POWQR4AR-B6252cbq.js} +1 -1
  263. package/studio/web/assets/{vendor-core-BdkdbUek.js → vendor-core-6LhfJlRC.js} +1 -1
  264. package/studio/web/assets/vendor-datadog-ozKf-pts.js +6 -0
  265. package/studio/web/assets/{vendor-react-izWchTRL.js → vendor-react-BOliS0BL.js} +1 -1
  266. package/studio/web/assets/vendor-tanstack-Bntfka-M.js +2 -0
  267. package/studio/web/assets/vendor-ui-9EYYDgoN.js +41 -0
  268. package/studio/web/assets/{vennDiagram-UO4OBE2U-BKYAZUh_.js → vennDiagram-UO4OBE2U-DZTsDA4k.js} +1 -1
  269. package/studio/web/assets/{wardley-WFR3VGLG-BO7f36ci.js → wardley-WFR3VGLG-Cfqoe6z4.js} +1 -1
  270. package/studio/web/assets/{wardleyDiagram-VNRHLVJA-Djb0JDQc.js → wardleyDiagram-VNRHLVJA-qu_f7Tvl.js} +1 -1
  271. package/studio/web/assets/{workers-CCvmDLH3.js → workers-BfZlUNVI.js} +3 -3
  272. package/studio/web/assets/{xml-06pZp00U.js → xml-BHd6qGH7.js} +1 -1
  273. package/studio/web/assets/{xychartDiagram-PMCCYNJV-BQh9ubvb.js → xychartDiagram-PMCCYNJV-UrOKRsmR.js} +1 -1
  274. package/studio/web/assets/{yaml-iSnWMVlV.js → yaml-C53R8OQq.js} +1 -1
  275. package/studio/web/assets/{yaml.worker-DAK2o_H1.js → yaml.worker-D9JekGdp.js} +183 -183
  276. package/studio/web/index.html +15 -15
  277. package/utility/operation_authorization.ts +27 -7
  278. package/utility/signalling.ts +3 -0
  279. package/validation/searchValidator.ts +22 -13
  280. package/studio/web/assets/FloatingChat-D4560T0p.js +0 -23
  281. package/studio/web/assets/alertDialog-UrbiMgQt.js +0 -1
  282. package/studio/web/assets/apiToken-BEvbFPMc.js +0 -1
  283. package/studio/web/assets/applications-CSUVTH4E.js +0 -296
  284. package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +0 -1
  285. package/studio/web/assets/authStore-5jSS5pTW.js +0 -3
  286. package/studio/web/assets/channel-BDtouPd6.js +0 -1
  287. package/studio/web/assets/dialog-DTepachC.js +0 -1
  288. package/studio/web/assets/editor.worker-CkkvdXvO.js +0 -33
  289. package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +0 -1
  290. package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +0 -1
  291. package/studio/web/assets/index-CPoDzQTe.css +0 -1
  292. package/studio/web/assets/index-DGRAzvO4.js +0 -824
  293. package/studio/web/assets/index.lazy-vgSic7et.js +0 -2
  294. package/studio/web/assets/json.worker-CSi2hF-a.js +0 -65
  295. package/studio/web/assets/notifications-BeWcX04m.js +0 -1
  296. package/studio/web/assets/notifications-DUGWrWoO.js +0 -1
  297. package/studio/web/assets/profile-BvUEDBzW.js +0 -1
  298. package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +0 -1
  299. package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +0 -1
  300. package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +0 -1
  301. package/studio/web/assets/regions-CB-_2m7M.js +0 -1
  302. package/studio/web/assets/register-1w1Zr3b2.js +0 -848
  303. package/studio/web/assets/register-jjEx9u7D.css +0 -1
  304. package/studio/web/assets/setLocalStorage-D_kflv4U.js +0 -1
  305. package/studio/web/assets/status-CI0wOdOE.js +0 -61
  306. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +0 -8
  307. package/studio/web/assets/tabs-CnQ_zenf.js +0 -1
  308. package/studio/web/assets/toggleHighContrast--ixms01u.css +0 -1
  309. package/studio/web/assets/toggleHighContrast-BsAE4jPe.js +0 -49
  310. package/studio/web/assets/useLocalStorage-BYoZF60G.js +0 -1
  311. package/studio/web/assets/vendor-datadog-fCQhBHGk.js +0 -6
  312. package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +0 -2
  313. package/studio/web/assets/vendor-ui-aE1tHcW7.js +0 -41
  314. package/studio/web/assets/x-vYntCxm4.js +0 -1
@@ -106,19 +106,25 @@ did not need it:
106
106
  activation cannot re-derive. An _optional_ record could not distinguish a payload build from a package
107
107
  build whose record was lost, so a missing, malformed or wrong-version one is refused rather than defaulted.
108
108
  - **Claiming an id is exclusive.** `buildCandidateApplication` used to tolerate an existing deployment
109
- directory because a fresh UUID could not collide; a public id can be repeated by an operator or by a
110
- redelivered replication, so a claim now rejects another component's directory and any directory carrying
111
- `.complete`, and rebuilds only over an uncertified partial of its _own_ component. Ownership is published
112
- as part of the claim — the `.component` sidecar is written right after the exclusive `mkdir`, not at
113
- certification — because `buildCandidateApplication` can spend minutes resolving and packing before any
114
- tree exists to infer an owner from. For that whole window the directory answered to nobody, and an empty
115
- `readdir` is indistinguishable from an abandoned claim, so a second component could delete a build that
116
- was still running. **Emptiness is not a verdict:** an unattributed directory is refused, never reclaimed,
117
- which is the same reading recovery already gives it. The id this request names is also pinned
118
- through the preparation preamble, so retention cannot evict the artifact the request is about to use —
119
- which it otherwise would, immediately, at `deployment_stagingRetention_maxCount: 0`. The contract is
120
- bounded: an id names one artifact _while that artifact exists_. Activation consumes it (the swap is a
121
- rename) and retention can prune it, after which the id is free again.
109
+ directory because a fresh UUID could not collide; a public id can be repeated by an operator, so a claim
110
+ rejects another component's directory and any directory carrying `.complete`, and rebuilds only over an
111
+ uncertified partial of its _own_ component — in a fresh claim, never inside the partial tree. Ownership is
112
+ published as part of the claim, not at certification, because `buildCandidateApplication` can spend minutes
113
+ resolving and packing before any tree exists to infer an owner from. The claim is built at
114
+ `.deploy-staging/.claiming-<uuid>-<digest>`, named there, and renamed onto the id, so the directory only
115
+ ever appears at its id already attributed. It used to be an exclusive `mkdir` followed by the sidecar write,
116
+ and a death between the two left an unattributed directory — indistinguishable from a claim in flight, so
117
+ refused forever. Now no live claim is ever visible unattributed: an EMPTY directory at an id is that
118
+ wreckage from an older build and is taken over (`rmdir`, which removes nothing else); a non-empty
119
+ unattributed one is still refused. A claim that died before its rename names no id, is skipped by every scan
120
+ of the staging root (all of them skip dot-prefixed entries), and is removed under the component's lock by its
121
+ next deploy, or by `drop_component`; a digest of the component in its name, rather than the name itself, makes
122
+ that attributable without a read and cannot push a long name past a filename limit.
123
+ On Windows a rename onto any existing directory fails with the `EPERM` a transient holder raises, so the
124
+ destination is classified before renaming rather than after a spent retry budget. The id this request names
125
+ is also pinned through the preparation preamble, so retention cannot evict the artifact the request is about
126
+ to use — which it otherwise would, immediately, at `deployment_stagingRetention_maxCount: 0`. What an id
127
+ names after its activation is the next section's subject.
122
128
  - **Staging owns its bytes.** A `file:<directory>` source is refused, and so is any symlink in the built
123
129
  tree resolving outside it (bar the `node_modules/harper`/`harperdb` links the loader owns and repairs).
124
130
  Certification fsyncs the tree but follows no links, and the post-swap relocation repair leaves external
@@ -221,8 +227,9 @@ never depends on it. Roll-forward requires the journal, the candidate and `.comp
221
227
  observable, which means a lost directory update degrades to a roll back rather than to a wrong decision.
222
228
 
223
229
  Retiring the rollback record only marks the displaced tree disposable; both the activation path and
224
- recovery then sweep it, or the components root would grow by a whole component version per deploy. The
225
- retire is **correctness, not hygiene** — that marker is what stops the legacy pass treating the record as
230
+ recovery then put it back into its deployment's record, or sweep it when nothing can keep it (see "A deployment
231
+ id keeps naming its release"), since otherwise the components root would grow by a whole component version per
232
+ deploy. The retire is **correctness, not hygiene** — that marker is what stops the legacy pass treating the record as
226
233
  authoritative once the journal is gone — so a failure to retire propagates and the component fails closed
227
234
  with its journal intact. Only the sweep itself is best-effort, because it costs disk rather than a wrong
228
235
  decision. For the same reason, a swap whose rename cannot be confirmed on storage skips both the retire
@@ -278,9 +285,10 @@ publication-lock timeout is rethrown as a 503 `ServerError` rather than the prep
278
285
  class, which recovery reads as "a live deploy holds this component's lock" — a deferral, not a verdict.
279
286
 
280
287
  The throw also means `deploy_component` never reaches replication: the release is live on this node only, and
281
- the error says so. A fresh deploy converges; a retry of a `deployment_id` activation does not, because its
282
- preamble settles the kept journal and the artifact is gone — the unconvergeable-retry gap #2315 step 5 owns.
283
- So a condition that would fail every publish is refused BEFORE anything moves: `assertRootConfigEffectPublishable`
288
+ the error says so. A fresh deploy converges. So does a retry of a `deployment_id` activation: its preamble settles
289
+ the kept journal, publishing the entry, and then answers that the id is already live here, so the operation goes
290
+ on to replicate. Peers of an immediate deploy hold no artifact, so its id answers 404 there. Even so, a condition
291
+ that would fail every publish is refused BEFORE anything moves: `assertRootConfigEffectPublishable`
284
292
  runs ahead of the journal and refuses an effect that would have to change a document that does not parse, or
285
293
  whose directory this process cannot write. Only a static condition is caught; a publish can still fail.
286
294
 
@@ -347,9 +355,9 @@ and validated, activated by nobody. Recovery used to remove every owned journal-
347
355
  dormant builds and bounds them per component to `deployment_stagingRetention_maxCount` (default 5, 0 keeps
348
356
  none), newest by `.complete` mtime, ties broken by deployment id so concurrent passes pick the same victims.
349
357
  Everything else journal-less — a partial tree, a directory whose tree already moved live, a stale
350
- `.unsettled` — is still residue and still removed. Nothing here produces a dormant build yet beyond the crash
351
- window between `.complete` and the journal; #2315 step 6 (deploy from an existing aside) is the producer this
352
- bound exists for.
358
+ `.unsettled` — is still residue and still removed. Two things produce them: a stage (`activate: false`), and every
359
+ activation that displaces a release it can put back under its own id (next section). A deployment's record — its
360
+ directory without a tree — is not a build and is not counted.
353
361
 
354
362
  Removal is decided **only under the owner's preparation lock**: activation writes `.complete` moments
355
363
  before its journal while holding that lock, so an unlocked read of "complete, no journal" is a candidate, not
@@ -370,6 +378,86 @@ hygiene: it never fails a component closed and never replaces a deploy's own err
370
378
  best-effort under filesystem failure and is not a storage quota — journaled, unsettled and unowned
371
379
  directories are preserved by design and can still fill a volume.
372
380
 
381
+ ### A deployment id keeps naming its release after the swap
382
+
383
+ On a node, a deployment id names its release for as long as the node holds that release: while it is dormant,
384
+ `deployment_id` activates it; while it is live, activating it answers that it already is, without a swap; when a
385
+ later activation displaces it, it becomes dormant again under the same id, within
386
+ `deployment_stagingRetention_maxCount`. A node holding neither answers 404 (#2315 step 5). Four pieces:
387
+
388
+ - **Provenance rides the tree.** `buildCandidateApplication` writes `.harper-deployment.json` — `{ v, component,
389
+ deploymentId }` — at the top of every candidate it extracts, after the install and before `.complete`, replacing
390
+ any marker the payload carried. It moves with the tree through the commit rename, so the live tree always names
391
+ the build that made it live, and anything else that replaces the directory replaces the marker too: it is
392
+ absent, never stale. A `file:` link gets none, since its target is not the deploy's to write, and a live link
393
+ has no provenance whatever its target holds. The tree is the component's own to write, which is why the marker
394
+ carries only an id: nothing published on activation comes from it. A component rewriting its own marker can
395
+ mislabel only its own releases. `package_component` and `get_components` leave the top-level marker out; a
396
+ deeper file of that name is the component's.
397
+ - **The id's record outlives the swap.** A build whose caller declared what it publishes (`describeArtifact` — every
398
+ `deploy_component`) writes `.artifact.json` before `.complete`, as a stage does, and a committed activation
399
+ keeps `.deploy-staging/<id>` minus its tree and journal: `.component`, `.complete`, `.artifact.json`. That
400
+ record is where the release goes back to, so its directory entry is flushed before the swap, as a stage's is. A
401
+ boot install, `add_component` and a link build declare nothing and keep none, as before. Keeping the descriptor
402
+ here rather than snapshotting the entry at displacement is
403
+ deliberate: for a boot install the entry in force is the new build's, so after an out-of-band edit the displaced
404
+ tree would be described with the wrong package. Re-activating an id publishes what that deployment declared,
405
+ which is the contract a staged artifact already had; `set_configuration` edits made while it was live are not
406
+ carried back, just as a package redeploy replaces the entry whole. One side effect, stated: an immediate deploy
407
+ that crashes before its first rename is now DESCRIBED, so recovery returns its candidate to dormant rather than
408
+ discarding it.
409
+ - **The displaced tree goes back into its record.** `retainDisplacedRelease` runs where a committed activation
410
+ disposes of the tree it displaced — after the retire, in `activateCandidateApplication`'s post-commit block, in
411
+ `sweepAsideRecords` on a roll forward, and in the legacy pass for a retired tree still in the aside — and
412
+ renames it into `<record>/<component>` when its marker names this component's record. **Only an explicit
413
+ retention decision deletes a tree**, so its verdict is three-way: _kept_; _ineligible_ (no marker, no usable
414
+ record, or `maxCount: 0`, when the record goes too) — swept as before; or _failed_ (a read error other than
415
+ ENOENT, or a failed rename) — left retired in the aside with its record for the legacy pass to retry at the next
416
+ preparation, since on Windows the rename and the delete both fail while something holds a handle in the tree.
417
+ A failure after the rename (syncing its parents) propagates, as the aside syncs do. At the activation it keeps
418
+ the journal, so recovery repeats the aside barrier before letting it go — not the kept tree's own entry, whose
419
+ loss costs only that kept copy. In recovery it fails the component closed with its journal, the retire's own
420
+ contract. The kept tree's
421
+ `.complete` mtime is refreshed (retried briefly) so retention orders it by when it stopped being live; left at
422
+ its build time, stages nobody activated would outrank it. **Only the activation's own site prunes** after a
423
+ keep, pinning the kept id: settlement runs inside a request's preamble, whose pin it cannot see, and a prune
424
+ there evicted the very artifact that request was activating. The pass a settlement runs inside counts what it
425
+ kept and pins it beside its own pin, so a refresh that failed cannot let that pass evict the release it just put
426
+ back. A kept release is not re-certified: it is kept as
427
+ it was when displaced, including what it wrote into its own directory while live, and activation re-runs the
428
+ link rule and the load validation. An absolute link `repairRelocatedDependencyLinks` wrote after its own swap (a
429
+ Windows `file:`/workspace junction) names the live path, so such a release's re-activation is refused 409.
430
+ - **Activating the live id answers without a swap.** After the preamble, `prepareApplication` in `activate` mode
431
+ compares the live marker with the id: equal means the release is already serving here — a retry whose earlier
432
+ attempt swapped on this node and failed later or elsewhere — so it sets `alreadyActive`, requests a restart
433
+ (nothing can tell whether the running generation ever loaded it, the reasoning a late startup preparation
434
+ follows), and returns; `deploy_component` emits the `prepare` phase end itself, since nothing ran
435
+ `validateCandidate`, and replicates. A restart it performs replaces every worker, dedicated ones included: which
436
+ ones loaded the previous release is unknowable, since the preamble may just have published an isolation change.
437
+ Per node, with no cross-node inference: a node that swapped answers success, one still holding the artifact
438
+ activates it, one with neither answers 404. Only `activate`: a deploy or stage claims an id the origin minted
439
+ fresh, and harper-pro never resends a replicated operation (`sendOperationToNode` is one socket per call), so a
440
+ replicated deploy of an id this node already holds — only a hand-crafted `_deploymentId` produces one — is refused
441
+ by the claim. A record whose release is neither live nor kept answers 404, where a missing tree used to answer 409.
442
+
443
+ **A record is stale only when nothing refers to it.** "Not live" is not enough: a crash after the commit and
444
+ before the re-home leaves the displaced release's record and its tree in `.deploy-aside`, and a boot scan can
445
+ reach the record before the journal that will re-home into it — the record would be deleted and its only tree
446
+ swept. So a record is residue only when neither the live marker nor the marker of any tree in the owner's aside,
447
+ retired or not, names it. Boot recovery checks that unlocked and leaves a referenced record alone without the
448
+ lock, as it does a dormant build (#2531: a per-directory lock on every pass makes healthy components lose the
449
+ 250 ms probe to sibling threads), then re-derives it under the owner's lock before removing a stale one.
450
+ `drop_component` removes the dropped component's records and abandoned claims along with its dormant builds.
451
+
452
+ Compatibility: a tree made live before this change has no marker, so the first deploy after the upgrade sweeps
453
+ it. An older build reads a record as residue and removes it, and a kept release as an ordinary dormant build it
454
+ can activate; the marker is an ignored file to it. The journal format is unchanged. A peer on an older build
455
+ answers 404 to a consumed id, as before. `deployment_stagingRetention_maxCount` (default 5) now bounds kept
456
+ releases with staged builds, and each is a whole installed tree, so at the default a component holds up to five
457
+ extra copies of itself; `0` keeps no previous release, and the rest of this section still applies. Until #2315
458
+ step 7, a package component reinstalls from its source on the first start after each package deploy; that
459
+ reinstall keeps no record, but the certified tree it displaces is kept, which is the one worth keeping.
460
+
373
461
  ## Component preparation is serialized across worker threads
374
462
 
375
463
  `prepareApplication()` performs one transaction per component: build the replacement, validate it, then swap it in (see "A deploy builds off to the side" below). Deploy operations can execute on worker threads as well as main, so a module-local promise queue is insufficient—each worker has its own module registry. `withComponentPreparationLock()` (`components/componentPreparationLock.ts`) instead acquires an atomic filesystem lock keyed by the absolute component path. The deprecated `install_node_modules` operation uses the same lock, so it cannot run npm concurrently with a deploy.
@@ -0,0 +1,28 @@
1
+ import { basename } from 'node:path';
2
+
3
+ /** Rides the rename that makes a tree live, so it is never stale. Only an id: the tree is the component's to write. */
4
+ export const DEPLOYMENT_PROVENANCE_FILE = '.harper-deployment.json';
5
+
6
+ const DEPLOYMENT_PROVENANCE_VERSION = 1;
7
+
8
+ export function formatDeploymentProvenance(component: string, deploymentId: string): string {
9
+ return JSON.stringify({ v: DEPLOYMENT_PROVENANCE_VERSION, component, deploymentId });
10
+ }
11
+
12
+ export function parseDeploymentProvenance(raw: string, component: string): string | undefined {
13
+ let parsed: any;
14
+ try {
15
+ parsed = JSON.parse(raw);
16
+ } catch {
17
+ return undefined;
18
+ }
19
+ if (parsed?.v !== DEPLOYMENT_PROVENANCE_VERSION || parsed.component !== component) return undefined;
20
+ const deploymentId = parsed.deploymentId;
21
+ // A single path segment that cannot name a claim in progress or any other dot-prefixed staging entry.
22
+ return typeof deploymentId === 'string' &&
23
+ deploymentId.length > 0 &&
24
+ deploymentId === basename(deploymentId) &&
25
+ !deploymentId.startsWith('.')
26
+ ? deploymentId
27
+ : undefined;
28
+ }
@@ -39,6 +39,7 @@ const {
39
39
  retireComponentDirectory,
40
40
  } = require('./Application.ts');
41
41
  const { COMPONENT_PREPARATION_LOCK_DIR, withComponentPreparationLock } = require('./componentPreparationLock.ts');
42
+ const { DEPLOYMENT_PROVENANCE_FILE } = require('./deploymentProvenance.ts');
42
43
  const {
43
44
  applyRootConfigEffect,
44
45
  assertRootConfigEffectPublishable,
@@ -888,6 +889,8 @@ async function deployComponent(req) {
888
889
  await validateComponentLoads(candidateDirPath, emit);
889
890
  },
890
891
  });
892
+ // Nothing ran `validateCandidate`, which ends this phase.
893
+ if (application.alreadyActive) emit('phase', { phase: 'prepare', status: 'done' });
891
894
  // The build is certified on disk from here on, so every later failure — a peer result, or a rejection
892
895
  // thrown by the replication layer itself — still leaves an artifact this id can activate.
893
896
  if (mode === 'stage') stagedOnOrigin = true;
@@ -938,8 +941,9 @@ async function deployComponent(req) {
938
941
  }
939
942
  // A still-isolated application restarts only its own dedicated worker. Everything else restarts the
940
943
  // pool: a shared application, and either direction of an isolation flip, where the reconcile in
941
- // restartWorkers starts or stops the moving application's own worker.
942
- const restartScope = wasIsolated && nowIsolated ? application.name : undefined;
944
+ // restartWorkers starts or stops the moving application's own worker. An already-live retry cannot know
945
+ // which workers loaded the previous release, so it restarts them all.
946
+ const restartScope = application.alreadyActive ? '*' : wasIsolated && nowIsolated ? application.name : undefined;
943
947
  if (mode === 'stage') {
944
948
  // No restart and no restart-required flag: nothing about the running component changed. The
945
949
  // marker is what tells the origin which peers understood the request — see the confirmation
@@ -967,7 +971,8 @@ async function deployComponent(req) {
967
971
  operation: 'restart_service',
968
972
  service: 'http',
969
973
  scope: manageThreads.encodeRestartScope(restartScope),
970
- scopeFallback: restartScope === undefined ? undefined : manageThreads.encodeRestartScope(undefined),
974
+ scopeFallback:
975
+ restartScope === undefined || restartScope === '*' ? undefined : manageThreads.encodeRestartScope(undefined),
971
976
  replicated: true,
972
977
  });
973
978
  emit('phase', { phase: 'restart', status: 'done' });
@@ -1204,6 +1209,7 @@ async function getComponents() {
1204
1209
  // Recursive function that will traverse the components dir and build json
1205
1210
  // directory tree as it goes.
1206
1211
  const rootConfig = configUtils.getConfiguration();
1212
+ const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
1207
1213
  const walkDir = async (dir, result) => {
1208
1214
  try {
1209
1215
  const list = await fs.readdir(dir, { withFileTypes: true });
@@ -1211,12 +1217,13 @@ async function getComponents() {
1211
1217
  const itemName = item.name;
1212
1218
  // Deny-list, not a dot-prefix skip: component CONTENTS legitimately include dot-files
1213
1219
  // (`.aiignore`, `.env.example`) that callers expect to see, so only Harper's own
1214
- // bookkeeping directories are excluded by name.
1220
+ // bookkeeping is excluded by name: its directories, and each component's provenance marker.
1215
1221
  if (
1216
1222
  itemName === 'node_modules' ||
1217
1223
  itemName === ASIDE_STAGING_DIR ||
1218
1224
  itemName === DEPLOY_STAGING_DIR ||
1219
- itemName === COMPONENT_PREPARATION_LOCK_DIR
1225
+ itemName === COMPONENT_PREPARATION_LOCK_DIR ||
1226
+ (itemName === DEPLOYMENT_PROVENANCE_FILE && path.dirname(dir) === componentsRoot)
1220
1227
  )
1221
1228
  continue;
1222
1229
  const itemPath = path.join(dir, itemName);
@@ -1248,8 +1255,8 @@ async function getComponents() {
1248
1255
  }
1249
1256
  };
1250
1257
 
1251
- const results = await walkDir(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), {
1252
- name: configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT).split(path.sep).slice(-1).pop(),
1258
+ const results = await walkDir(componentsRoot, {
1259
+ name: componentsRoot.split(path.sep).slice(-1).pop(),
1253
1260
  entries: [],
1254
1261
  });
1255
1262
  const { getUnsatisfiedEnv } = require('./componentSecrets.ts');
@@ -3,6 +3,7 @@ import { stat, readdir } from 'node:fs/promises';
3
3
  import { Readable, pipeline } from 'node:stream';
4
4
  import tar from 'tar-fs';
5
5
  import { createGzip } from 'node:zlib';
6
+ import { DEPLOYMENT_PROVENANCE_FILE } from './deploymentProvenance.ts';
6
7
 
7
8
  interface PackageOptions {
8
9
  skip_node_modules?: boolean;
@@ -42,9 +43,12 @@ const INSTALL_OWNED_PATH = join('node_modules', 'harper');
42
43
  * excluded the whole tree. Shared by the stream packer and the directory walk so they cannot diverge.
43
44
  */
44
45
  function isExcluded(directory: string, fullPath: string, options: PackageOptions): boolean {
46
+ const name = basename(fullPath);
45
47
  // Unconditional, including under `skip_symlinks`: packed literally, the link ships an absolute path to
46
48
  // the packaging host's install root, which arrives dangling.
47
- if (basename(fullPath) === 'harper' && relative(directory, fullPath) === INSTALL_OWNED_PATH) return true;
49
+ if (name === 'harper' && relative(directory, fullPath) === INSTALL_OWNED_PATH) return true;
50
+ // A package made from a live tree would otherwise carry that release's deployment id into the next one.
51
+ if (name === DEPLOYMENT_PROVENANCE_FILE && relative(directory, fullPath) === DEPLOYMENT_PROVENANCE_FILE) return true;
48
52
  if (!options.skip_node_modules) return false;
49
53
  const rel = relative(directory, fullPath);
50
54
  return rel.split(sep).includes('node_modules') || rel.includes(WEBPACK_CACHE_SEGMENT);
@@ -25,7 +25,7 @@
25
25
  "compressionThreshold": {
26
26
  "type": "integer",
27
27
  "minimum": 0,
28
- "description": "Responses larger than this threshold (bytes) will be compressed for clients that accept compression. Default: 1200"
28
+ "description": "Responses larger than this threshold (bytes) will be compressed for clients that accept brotli. Default: 0 (compression disabled)"
29
29
  },
30
30
  "cors": { "type": "boolean", "description": "Enable Cross Origin Resource Sharing. Default: true" },
31
31
  "corsAccessList": {
@@ -89,13 +89,41 @@ Three non-obvious mechanics keep that safe:
89
89
  it and broadcasting a reload would surface the earlier attempt's partial/corrupt directory as
90
90
  healthy. Only a _fresh_ marker on a _previously healthy_ database that failed before destruction is
91
91
  safe to clear.
92
- - **The ITC close broadcast is best-effort, so closure is verified before the purge.** The SCHEMA
93
- broadcast (`signalSchemaChange`) resolves after remote handlers complete but times out at 30s
94
- "best-effort", swallows errors, and never reaches job-worker threads at all (their ports are
95
- excluded from broadcasts to avoid re-entrant deadlocks). A destructive purge cannot trust it:
96
- `restoreBackup` polls rocksdb-js `registryStatus()` (process-global across worker threads) until
97
- the database path has no open instance, and aborts with a 409 — _cleaning up the marker, since
98
- nothing was destroyed_ — if handles remain.
92
+ - **The ITC close broadcast is normally best-effort, so closure is verified before the purge.** A
93
+ SCHEMA broadcast (`signalSchemaChange`) usually resolves after remote handlers complete but times
94
+ out at 30s "best-effort" and swallows errors. The restore `close` phase is stricter: it waits until
95
+ every eligible recipient acknowledges or its port closes, and aborts the restore with a retryable
96
+ 409 if that does not happen within 30s, because proceeding past an unconfirmed blob-save barrier
97
+ would re-open the race the barrier exists to close. A destructive purge still
98
+ verifies closure independently: `restoreBackup` polls rocksdb-js `registryStatus()` (process-global
99
+ across worker threads) until the database path has no open instance, and aborts with a 409 —
100
+ _cleaning up the marker, since nothing was destroyed_ — if handles remain.
101
+ - **The close acknowledgement fences blob saves, deferred reclamation and orphan cleanup, not just
102
+ database handles.** A store
103
+ handle can close while a `saveBlob` file pipeline it started is still pending, because blob roots
104
+ live outside RocksDB and streamed saves settle independently of the record write; a queued
105
+ reclamation is worse still, being a timer that never consults the database at all. The restore
106
+ signal therefore has explicit `close` and `reload` phases: every worker refuses new saves for that
107
+ database, stops draining reclamations for it, stops any `cleanup_orphan_blobs` walk in progress
108
+ (its "nothing references this path" verdict was reached against the generation being replaced),
109
+ awaits the saves and unlinks already dispatched -- including a save-failure cleanup's -- then
110
+ closes the store and acknowledges. Restore signals are the one schema broadcast that includes job
111
+ workers; ordinary gossip still excludes them to avoid re-entrant broadcast deadlocks.
112
+
113
+ The fence records the token of the restore that currently owns it, not a flag and not a set. Two
114
+ restores of the same database can overlap — a restore releases its lock before awaiting its `reload`
115
+ broadcast — so a database-wide boolean lets the first restore's late reload lift the second one's
116
+ fence, admitting saves during the second one's post-close check. A later close takes ownership
117
+ without the database ever unfencing in between, and a release whose token is not the current owner
118
+ does nothing. A _set_ of tokens fails the opposite way: a restore that dies after destruction never
119
+ reloads, so its token would keep the database fenced for the life of the process, defeating the
120
+ rerun the failure tells the operator to perform.
121
+
122
+ Releasing the fence discards that database's queued reclamations rather than resuming them: they
123
+ condemn file paths belonging to the generation the restore just replaced, so draining them would
124
+ unlink the bytes the restore wrote at those same paths. A restore that aborted before destroying
125
+ anything says so, and its queue is kept and rewoken instead.
126
+
99
127
  - **Online restore is impossible for a database a component holds open — and that failure is
100
128
  correct.** rocksdb-js's registry is process-global but records only a per-path refCount, with no
101
129
  attribution to a thread or component; Harper keeps no component→database ownership map. So when a
@@ -101,6 +101,25 @@ const BACKUP_MARKER_REASONS: CaptureMarkerReasons = {
101
101
  pending: 'blob was not yet complete when this backup was taken',
102
102
  };
103
103
 
104
+ export async function* walkBlobFiles(root: string): AsyncGenerator<string> {
105
+ const stack: string[] = [root];
106
+ while (stack.length > 0) {
107
+ const dir = stack.pop() as string;
108
+ let entries;
109
+ try {
110
+ entries = await readdir(dir, { withFileTypes: true });
111
+ } catch (error: any) {
112
+ if (error.code === 'ENOENT') continue;
113
+ throw error;
114
+ }
115
+ for (const entry of entries) {
116
+ const path = join(dir, entry.name);
117
+ if (entry.isDirectory()) stack.push(path);
118
+ else if (entry.isFile()) yield path;
119
+ }
120
+ }
121
+ }
122
+
104
123
  /** Put one blob file into the destination, returning how the entry was captured. */
105
124
  async function captureBlobFile(
106
125
  srcPath: string,
@@ -151,34 +170,16 @@ export async function copyTree(
151
170
  onProgress?: () => void
152
171
  ): Promise<{ substituted: number; captured: number; copied: number }> {
153
172
  const counts = { substituted: 0, captured: 0, copied: 0 };
154
- if (!existsSync(srcRoot)) return counts;
155
- const stack: string[] = [srcRoot];
156
- while (stack.length > 0) {
157
- const dir = stack.pop() as string;
158
- let entries;
159
- try {
160
- entries = await readdir(dir, { withFileTypes: true });
161
- } catch (error: any) {
162
- if (error.code === 'ENOENT') continue; // directory removed mid-walk
163
- throw error;
164
- }
165
- for (const entry of entries) {
166
- const srcPath = join(dir, entry.name);
167
- if (entry.isDirectory()) {
168
- stack.push(srcPath);
169
- } else if (entry.isFile()) {
170
- const destPath = join(destRoot, relative(srcRoot, srcPath));
171
- if (classify) {
172
- const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
173
- if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
174
- else if (disposition === 'capture') counts.captured++;
175
- } else {
176
- await linkOrCopy(srcPath, destPath, counts);
177
- }
178
- onProgress?.();
179
- }
180
- // symlinks/other node types in a blob root are not expected and are intentionally skipped
173
+ for await (const srcPath of walkBlobFiles(srcRoot)) {
174
+ const destPath = join(destRoot, relative(srcRoot, srcPath));
175
+ if (classify) {
176
+ const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
177
+ if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
178
+ else if (disposition === 'capture') counts.captured++;
179
+ } else {
180
+ await linkOrCopy(srcPath, destPath, counts);
181
181
  }
182
+ onProgress?.();
182
183
  }
183
184
  return counts;
184
185
  }
@@ -335,6 +336,44 @@ export async function assertBlobSnapshotRestorable(
335
336
  }
336
337
  }
337
338
 
339
+ /**
340
+ * Refuse an engine-only restore unless the operator has accepted the mixed result.
341
+ *
342
+ * Restoring engine files without the matching blobs produces a mixed generation: records rolled back
343
+ * to the backup point, addressing whichever blobs the roots happen to hold. There are two ways that
344
+ * goes wrong, and the destination being empty rules out only the first:
345
+ *
346
+ * 1. Blobs are present now, so a restored record can resolve onto a blob that no longer belongs to it,
347
+ * or onto nothing at all if the blob it referenced was deleted since the backup point.
348
+ * 2. The roots are empty, so nothing collides today -- but blob ids are a per-database counter that
349
+ * `getNextFileId` (`resources/blob.ts`) re-seeds by scanning the roots. Empty roots re-seed at 1,
350
+ * which is exactly the id space the restored records already reference, so the *next* blob written
351
+ * is placed at a path a restored record points at. A brand-new target database is not exempt: its
352
+ * roots are empty for the same reason.
353
+ *
354
+ * Purging the roots instead is not the answer -- that strips blobs the restored records still
355
+ * reference -- so the operator has to choose, and the choice is recorded. Case 2 would stop being a
356
+ * hazard if the blob-id high-water mark were persisted with the engine and restored alongside it, so
357
+ * the counter could not fall behind the references it has already handed out.
358
+ */
359
+ export function assertEngineOnlyRestoreAllowed(
360
+ databaseName: string,
361
+ { backupHasBlobs, allowEngineOnly }: { backupHasBlobs: boolean; allowEngineOnly: boolean }
362
+ ): void {
363
+ // Strictly true, not merely truthy: a manifest is on-disk data, and a corrupt `blobs` value must not
364
+ // be read as "this backup has blobs" and waved past the guard.
365
+ if (backupHasBlobs === true || allowEngineOnly === true) return;
366
+ // Decided from the manifest alone. Walking the destination to tailor the message would turn a
367
+ // deterministic 400 into a traversal that can be slow or fail outright (EACCES on an unreadable
368
+ // root), for wording -- and both hazards are worth stating anyway, since the operator who passes
369
+ // the opt-in is accepting whichever one applies.
370
+ throw new ClientError(
371
+ `Cannot restore an engine-only backup over database '${databaseName}': the backup captured no blobs, so the restored records would address whichever blobs are on disk now. ` +
372
+ `If the database still has blob files, a record can resolve to a blob that no longer belongs to it; if it has none, blob ids restart at 1 and the next blob written lands on a path a restored record already references — so a new target database is not an escape. ` +
373
+ `Restore a backup that includes blobs, or pass 'allow_engine_only' to accept the mixed result.`
374
+ );
375
+ }
376
+
338
377
  /**
339
378
  * Restore a backup's blob snapshot back into the database's blob roots. Each root is purged and
340
379
  * rewritten from `blobs/<backupId>/<rootIndex>/` so the restored blob set matches the backup exactly
@@ -342,9 +381,11 @@ export async function assertBlobSnapshotRestorable(
342
381
  *
343
382
  * A backup created with blobs excluded (or an older backup that predates blob snapshots) has no
344
383
  * snapshot directory: in that case the live blob roots are left untouched and a warning is logged,
345
- * since purging them would strip blobs the restored records may still reference. Roots are restored
346
- * by index into the *same* configured root; an incompatible root count is rejected up front (see
347
- * `assertBlobSnapshotRestorable`) rather than collapsed, so blobs are never mis-addressed.
384
+ * since purging them would strip blobs the restored records may still reference. A restore only
385
+ * reaches that state when the operator accepted it with `allow_engine_only`
386
+ * (`assertEngineOnlyRestoreAllowed` refuses otherwise, whatever the destination roots hold). Roots are restored by index into the *same* configured root;
387
+ * an incompatible root count is rejected up front (see `assertBlobSnapshotRestorable`) rather than
388
+ * collapsed, so blobs are never mis-addressed.
348
389
  */
349
390
  export async function restoreBlobSnapshot(
350
391
  backupDir: string,
@@ -56,6 +56,7 @@ export type SearchByConditionsRequest = Query &
56
56
  database?: string;
57
57
  table: string;
58
58
  get_attributes: Select;
59
+ hdb_user?: Context['user'];
59
60
  reverse?: boolean;
60
61
  operator?: Operator;
61
62
  };
@@ -85,6 +86,12 @@ export class ResourceBridge extends BridgeMethods {
85
86
  attribute: c.attribute ?? c.search_attribute,
86
87
  comparator: c.comparator ?? c.search_type,
87
88
  value: c.value !== undefined ? c.value : c.search_value, // null is valid value
89
+ ...(c.fields === undefined ? null : { fields: c.fields }),
90
+ ...(c.includeHighlights === undefined ? null : { includeHighlights: c.includeHighlights }),
91
+ ...(c.maxIndexLagMilliseconds === undefined ? null : { maxIndexLagMilliseconds: c.maxIndexLagMilliseconds }),
92
+ ...(c.waitForIndexMilliseconds === undefined
93
+ ? null
94
+ : { waitForIndexMilliseconds: c.waitForIndexMilliseconds }),
88
95
  };
89
96
  }
90
97
  }
@@ -107,6 +114,7 @@ export class ResourceBridge extends BridgeMethods {
107
114
  allowFullScan: true, // operations API can do full scans by default, but REST is more cautious about what it allows
108
115
  } as any,
109
116
  {
117
+ user: searchObject.hdb_user,
110
118
  onlyIfCached: searchObject.onlyIfCached,
111
119
  noCacheStore: searchObject.noCacheStore,
112
120
  noCache: searchObject.noCache,
@@ -727,8 +735,14 @@ export class ResourceBridge extends BridgeMethods {
727
735
  function getSelect({ get_attributes }: any, table: any) {
728
736
  if (get_attributes) {
729
737
  if (get_attributes[0] === '*') {
730
- if (table.schemaDefined) return;
731
- else get_attributes = table.attributes.map((attribute) => attribute.name);
738
+ const metadata = get_attributes.slice(1).filter((name) => name === '$score' || name === '$highlights');
739
+ if (table.schemaDefined && metadata.length === 0) return;
740
+ get_attributes = [
741
+ ...table.attributes
742
+ .filter((attribute) => !attribute.computed && !attribute.relationship)
743
+ .map((attribute) => attribute.name),
744
+ ...metadata,
745
+ ];
732
746
  }
733
747
  get_attributes.forceNulls = true;
734
748
  return get_attributes;