@noy-db/hub 0.2.0-pre.23 → 0.2.0-pre.24

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 (285) hide show
  1. package/dist/aggregate/index.cjs.map +1 -1
  2. package/dist/aggregate/index.d.cts +3 -3
  3. package/dist/aggregate/index.d.ts +3 -3
  4. package/dist/aggregate/index.js +5 -5
  5. package/dist/attestation/index.cjs.map +1 -1
  6. package/dist/attestation/index.d.cts +4 -4
  7. package/dist/attestation/index.d.ts +4 -4
  8. package/dist/attestation/index.js +6 -6
  9. package/dist/blobs/index.cjs.map +1 -1
  10. package/dist/blobs/index.d.cts +6 -6
  11. package/dist/blobs/index.d.ts +6 -6
  12. package/dist/blobs/index.js +6 -6
  13. package/dist/bundle/index.cjs +617 -1202
  14. package/dist/bundle/index.cjs.map +1 -1
  15. package/dist/bundle/index.d.cts +15 -6
  16. package/dist/bundle/index.d.ts +15 -6
  17. package/dist/bundle/index.js +58 -193
  18. package/dist/bundle/index.js.map +1 -1
  19. package/dist/{chunk-CQYEDODS.js → chunk-35U5YNRR.js} +3 -3
  20. package/dist/{chunk-NV4IHBZS.js → chunk-3XJU3OHE.js} +5 -5
  21. package/dist/{chunk-OTWT6BAJ.js → chunk-4BB4T3O7.js} +12 -2
  22. package/dist/chunk-4BB4T3O7.js.map +1 -0
  23. package/dist/{chunk-IVZWHIEK.js → chunk-4HEGG5NJ.js} +5 -5
  24. package/dist/{chunk-WE2BUQD2.js → chunk-4TCMCCC3.js} +5 -3
  25. package/dist/{chunk-5YTXYPES.js → chunk-5A2FVGHT.js} +5 -5
  26. package/dist/{chunk-NSXNXLYM.js → chunk-5GZC2ZM3.js} +2 -2
  27. package/dist/{chunk-JYNH4FIM.js → chunk-77WF53XY.js} +4 -4
  28. package/dist/{chunk-O5XKZCUD.js → chunk-7X4EF35A.js} +5 -5
  29. package/dist/{chunk-SQKAECUL.js → chunk-7ZCTUI26.js} +2 -2
  30. package/dist/{chunk-J6RGRZOY.js → chunk-AO3QSMCU.js} +2 -2
  31. package/dist/{chunk-JDCPRJVS.js → chunk-AONK5GCC.js} +4 -4
  32. package/dist/{chunk-FRRJIUSI.js → chunk-B5CSNGSE.js} +17 -9
  33. package/dist/chunk-B5CSNGSE.js.map +1 -0
  34. package/dist/{chunk-IY24WS2P.js → chunk-BCMHJYVT.js} +4 -4
  35. package/dist/{chunk-IY24WS2P.js.map → chunk-BCMHJYVT.js.map} +1 -1
  36. package/dist/{chunk-TYMDCIQM.js → chunk-C472BRJ4.js} +4 -4
  37. package/dist/{chunk-MBXKRHSS.js → chunk-CCNRFAL3.js} +2 -2
  38. package/dist/{chunk-BZW5IL43.js → chunk-DCA2BDHA.js} +4 -4
  39. package/dist/{chunk-JBBWALNI.js → chunk-DCICHSRS.js} +2 -2
  40. package/dist/{chunk-2XA2ZML4.js → chunk-FG6IQ3ZL.js} +3 -3
  41. package/dist/{chunk-C2RJVZZL.js → chunk-G4GW5VOS.js} +2 -2
  42. package/dist/{chunk-U2XSUCDF.js → chunk-GEWIFM4J.js} +2 -2
  43. package/dist/{chunk-TNH5SLCD.js → chunk-HD4QCT2O.js} +2 -2
  44. package/dist/{chunk-I3IYTUUI.js → chunk-HHJ5DZCZ.js} +3 -3
  45. package/dist/{chunk-6QAZ5O6X.js → chunk-IEIADIPM.js} +2 -2
  46. package/dist/{chunk-YPIOFSN3.js → chunk-IHAISFXP.js} +2 -2
  47. package/dist/{chunk-GJTKMME7.js → chunk-JKM2AVVH.js} +2 -2
  48. package/dist/{chunk-EYK72OTL.js → chunk-JRMOSIH4.js} +5 -5
  49. package/dist/chunk-JRMOSIH4.js.map +1 -0
  50. package/dist/{chunk-S45MDEEF.js → chunk-LMWVNF6X.js} +2 -2
  51. package/dist/{chunk-TA6HPKWQ.js → chunk-LR7CODVN.js} +1 -1
  52. package/dist/chunk-LR7CODVN.js.map +1 -0
  53. package/dist/{chunk-TAMRU7A2.js → chunk-OKV7S356.js} +4 -4
  54. package/dist/{chunk-HYJMAV53.js → chunk-OWAMTSAI.js} +93 -93
  55. package/dist/chunk-OWAMTSAI.js.map +1 -0
  56. package/dist/{chunk-IW4L4X65.js → chunk-P5A4E53B.js} +2 -2
  57. package/dist/{chunk-JOK73NDT.js → chunk-P7OL22JP.js} +3 -3
  58. package/dist/{chunk-P65YMN5V.js → chunk-QOXZM3L2.js} +407 -162
  59. package/dist/chunk-QOXZM3L2.js.map +1 -0
  60. package/dist/chunk-R43KS34V.js +399 -0
  61. package/dist/chunk-R43KS34V.js.map +1 -0
  62. package/dist/{chunk-TGIJTNM3.js → chunk-R5ZECURV.js} +2 -2
  63. package/dist/{chunk-KOAJ3TZM.js → chunk-RFEXGW3L.js} +2 -2
  64. package/dist/{chunk-F5ILTHMU.js → chunk-RNQPDV75.js} +5 -5
  65. package/dist/{chunk-WWVJXBOT.js → chunk-SGM7CK7R.js} +5 -5
  66. package/dist/{chunk-7MRT7EPB.js → chunk-SOQE5DUV.js} +3 -3
  67. package/dist/{chunk-F5GWNSE2.js → chunk-TOMSCJRV.js} +3 -3
  68. package/dist/{chunk-F5GWNSE2.js.map → chunk-TOMSCJRV.js.map} +1 -1
  69. package/dist/{chunk-ZONKSLF2.js → chunk-TQMQZOMX.js} +2 -2
  70. package/dist/{chunk-3HNKR65T.js → chunk-U6LTLN7O.js} +3 -3
  71. package/dist/{chunk-UU6M64HI.js → chunk-UAK2AMO2.js} +4 -4
  72. package/dist/{chunk-37VGJM3T.js → chunk-WQ3KAGOV.js} +2 -2
  73. package/dist/{chunk-C6W5KVDV.js → chunk-XC32SZPW.js} +35 -35
  74. package/dist/chunk-XC32SZPW.js.map +1 -0
  75. package/dist/{chunk-AI4USDRI.js → chunk-XQO4TAJS.js} +4 -4
  76. package/dist/{chunk-SQOK5UM6.js → chunk-ZBENTRFS.js} +2 -2
  77. package/dist/{chunk-6QE4DUYC.js → chunk-ZDITTESU.js} +2 -2
  78. package/dist/consent/index.cjs.map +1 -1
  79. package/dist/consent/index.d.cts +5 -5
  80. package/dist/consent/index.d.ts +5 -5
  81. package/dist/consent/index.js +3 -3
  82. package/dist/{crypto-456N7UVX.js → crypto-2LU6XUFF.js} +3 -3
  83. package/dist/{delegation-DP4COTXB.js → delegation-6ABSJGXV.js} +5 -5
  84. package/dist/derivations/index.cjs.map +1 -1
  85. package/dist/derivations/index.d.cts +6 -6
  86. package/dist/derivations/index.d.ts +6 -6
  87. package/dist/derivations/index.js +4 -4
  88. package/dist/{dev-unlock-DzDzLTdZ.d.ts → dev-unlock-BlhRHr6p.d.ts} +1 -1
  89. package/dist/{dev-unlock-Bw7iBD1D.d.cts → dev-unlock-DURe4IvF.d.cts} +1 -1
  90. package/dist/{errors-Dkc_fi-S.d.cts → errors-B2tUcRPg.d.cts} +19 -5
  91. package/dist/{errors-Dkc_fi-S.d.ts → errors-B2tUcRPg.d.ts} +19 -5
  92. package/dist/executor-JKMSEB34.js +8 -0
  93. package/dist/executor-UYXSQB4D.js +12 -0
  94. package/dist/executor-VJSCTBWY.js +8 -0
  95. package/dist/{fanout-sidecar-YXNAEZ33.js → fanout-sidecar-ZQT4Y7PF.js} +2 -2
  96. package/dist/forget/index.js +4 -4
  97. package/dist/guards/index.cjs.map +1 -1
  98. package/dist/guards/index.d.cts +6 -6
  99. package/dist/guards/index.d.ts +6 -6
  100. package/dist/guards/index.js +6 -6
  101. package/dist/{hash-C52X_-m5.d.cts → hash-CqRZfDZH.d.cts} +1 -1
  102. package/dist/{hash-DepR-xVc.d.ts → hash-cF4iWaBV.d.ts} +1 -1
  103. package/dist/history/index.cjs.map +1 -1
  104. package/dist/history/index.d.cts +6 -6
  105. package/dist/history/index.d.ts +6 -6
  106. package/dist/history/index.js +5 -5
  107. package/dist/i18n/index.cjs.map +1 -1
  108. package/dist/i18n/index.d.cts +5 -5
  109. package/dist/i18n/index.d.ts +5 -5
  110. package/dist/i18n/index.js +6 -6
  111. package/dist/index-B8MoIS7B.d.ts +70 -0
  112. package/dist/{index-Bm9hIY7t.d.ts → index-BLff_E35.d.ts} +2 -2
  113. package/dist/{index-tZqVB9g5.d.cts → index-BthnP2MA.d.cts} +2 -2
  114. package/dist/index-da0M3NnR.d.cts +70 -0
  115. package/dist/index.cjs +25907 -25557
  116. package/dist/index.cjs.map +1 -1
  117. package/dist/index.d.cts +135 -80
  118. package/dist/index.d.ts +135 -80
  119. package/dist/index.js +78 -51
  120. package/dist/index.js.map +1 -1
  121. package/dist/indexing/index.cjs.map +1 -1
  122. package/dist/indexing/index.js +4 -4
  123. package/dist/issue-KLRMW5DH.js +12 -0
  124. package/dist/kernel/index.cjs +657 -0
  125. package/dist/kernel/index.cjs.map +1 -0
  126. package/dist/kernel/index.d.cts +11 -0
  127. package/dist/kernel/index.d.ts +11 -0
  128. package/dist/kernel/index.js +40 -0
  129. package/dist/{ledger-I7JUYP4L.js → ledger-VOS2X3WJ.js} +5 -5
  130. package/dist/materialized-views/index.cjs.map +1 -1
  131. package/dist/materialized-views/index.d.cts +6 -6
  132. package/dist/materialized-views/index.d.ts +6 -6
  133. package/dist/materialized-views/index.js +8 -8
  134. package/dist/{mime-magic-Dejetix_.d.ts → mime-magic-BswIvWkR.d.ts} +1 -1
  135. package/dist/{mime-magic-Cxf9B_Dm.d.cts → mime-magic-CCrP-iXJ.d.cts} +1 -1
  136. package/dist/{ulid-Bg-IBJyA.d.cts → multi-bundle-6s5nKAZX.d.ts} +114 -58
  137. package/dist/{ulid-Dwt3JEcy.d.ts → multi-bundle-WhYiJEgV.d.cts} +114 -58
  138. package/dist/noydb-2PI2ZBX6.js +38 -0
  139. package/dist/overlay-views/index.cjs.map +1 -1
  140. package/dist/overlay-views/index.d.cts +6 -6
  141. package/dist/overlay-views/index.d.ts +6 -6
  142. package/dist/overlay-views/index.js +4 -4
  143. package/dist/periods/index.cjs.map +1 -1
  144. package/dist/periods/index.d.cts +5 -5
  145. package/dist/periods/index.d.ts +5 -5
  146. package/dist/periods/index.js +5 -5
  147. package/dist/{public-envelope-5XRTUNKF.js → public-envelope-IJJMWSTJ.js} +4 -4
  148. package/dist/query/index.cjs.map +1 -1
  149. package/dist/query/index.d.cts +3 -3
  150. package/dist/query/index.d.ts +3 -3
  151. package/dist/query/index.js +7 -7
  152. package/dist/registry-GAIFVWXF.js +8 -0
  153. package/dist/registry-J77ZUQ7G.js +8 -0
  154. package/dist/{registry-NWHOLD5M.js → registry-JGEVJ6YC.js} +3 -3
  155. package/dist/{revoke-5IEK22KT.js → revoke-WUY4AYRJ.js} +6 -6
  156. package/dist/sealed-record/index.cjs.map +1 -1
  157. package/dist/sealed-record/index.d.cts +1 -1
  158. package/dist/sealed-record/index.d.ts +1 -1
  159. package/dist/sealed-record/index.js +2 -2
  160. package/dist/session/index.cjs.map +1 -1
  161. package/dist/session/index.d.cts +6 -6
  162. package/dist/session/index.d.ts +6 -6
  163. package/dist/session/index.js +3 -3
  164. package/dist/shadow/index.cjs.map +1 -1
  165. package/dist/shadow/index.d.cts +5 -5
  166. package/dist/shadow/index.d.ts +5 -5
  167. package/dist/shadow/index.js +2 -2
  168. package/dist/{signer-I6YARZQA.js → signer-UJF3CFDC.js} +5 -5
  169. package/dist/snapshots/index.cjs.map +1 -1
  170. package/dist/snapshots/index.d.cts +5 -5
  171. package/dist/snapshots/index.d.ts +5 -5
  172. package/dist/snapshots/index.js +4 -4
  173. package/dist/{stale-CPESGAPL.js → stale-PW6VBGSP.js} +2 -2
  174. package/dist/store/index.cjs.map +1 -1
  175. package/dist/store/index.d.cts +5 -5
  176. package/dist/store/index.d.ts +5 -5
  177. package/dist/store/index.js +2 -2
  178. package/dist/{strategy-WtB-jXYv.d.cts → strategy-BWmgRPA2.d.cts} +1 -1
  179. package/dist/{strategy-54eIwox5.d.ts → strategy-D47TC5X6.d.ts} +1 -1
  180. package/dist/sync/index.cjs.map +1 -1
  181. package/dist/sync/index.d.cts +4 -4
  182. package/dist/sync/index.d.ts +4 -4
  183. package/dist/sync/index.js +4 -4
  184. package/dist/team/index.cjs +10 -3
  185. package/dist/team/index.cjs.map +1 -1
  186. package/dist/team/index.d.cts +5 -5
  187. package/dist/team/index.d.ts +5 -5
  188. package/dist/team/index.js +8 -8
  189. package/dist/{transition-guard-BcLyTGYq.d.cts → transition-guard-C3NxfVKk.d.cts} +1 -1
  190. package/dist/{transition-guard-Ctxapq1b.d.ts → transition-guard-CQH5263l.d.ts} +1 -1
  191. package/dist/tx/index.cjs +1 -1
  192. package/dist/tx/index.cjs.map +1 -1
  193. package/dist/tx/index.d.cts +5 -5
  194. package/dist/tx/index.d.ts +5 -5
  195. package/dist/tx/index.js +3 -3
  196. package/dist/{types-DONgts0n.d.ts → types-BGRX6sPT.d.ts} +288 -578
  197. package/dist/{types-Bhs2i_Ll.d.cts → types-COQ6qJZh.d.cts} +288 -578
  198. package/dist/ulid-DRH25k3y.d.cts +66 -0
  199. package/dist/ulid-DRH25k3y.d.ts +66 -0
  200. package/dist/util/index.cjs.map +1 -1
  201. package/dist/util/index.js +1 -1
  202. package/dist/{with-materialized-view-CyVLOr09.d.ts → with-materialized-view-Cj-6fuav.d.ts} +1 -1
  203. package/dist/{with-materialized-view-BYb3p9wT.d.cts → with-materialized-view-D4U-KrBH.d.cts} +1 -1
  204. package/dist/{with-overlayed-view-LGrQ984e.d.cts → with-overlayed-view-BKjdUPRx.d.cts} +1 -1
  205. package/dist/{with-overlayed-view-BhLRxqwI.d.ts → with-overlayed-view-COp_7EEy.d.ts} +1 -1
  206. package/dist/{with-rollup-CO8ibRcK.d.ts → with-rollup-B1_ZjG02.d.ts} +1 -1
  207. package/dist/{with-rollup-Bj8c7ttB.d.cts → with-rollup-C-Bok_o2.d.cts} +1 -1
  208. package/package.json +13 -3
  209. package/dist/chunk-C6W5KVDV.js.map +0 -1
  210. package/dist/chunk-EYK72OTL.js.map +0 -1
  211. package/dist/chunk-FRRJIUSI.js.map +0 -1
  212. package/dist/chunk-HYJMAV53.js.map +0 -1
  213. package/dist/chunk-JTI57WRT.js +0 -164
  214. package/dist/chunk-JTI57WRT.js.map +0 -1
  215. package/dist/chunk-OTWT6BAJ.js.map +0 -1
  216. package/dist/chunk-P65YMN5V.js.map +0 -1
  217. package/dist/chunk-TA6HPKWQ.js.map +0 -1
  218. package/dist/chunk-ZC7J6ZYV.js +0 -7
  219. package/dist/chunk-ZC7J6ZYV.js.map +0 -1
  220. package/dist/executor-4IEW4KG5.js +0 -8
  221. package/dist/executor-KYJCJCIN.js +0 -12
  222. package/dist/executor-W7VIBOBZ.js +0 -8
  223. package/dist/issue-JXC6T2QR.js +0 -12
  224. package/dist/noydb-VGR2HLDB.js +0 -39
  225. package/dist/registry-ATRHOG5B.js +0 -8
  226. package/dist/registry-LEHB26TY.js +0 -8
  227. package/dist/state-vault-JR3CFGNP.js +0 -14
  228. package/dist/vault-group-BB246VIM.js +0 -804
  229. package/dist/vault-group-BB246VIM.js.map +0 -1
  230. /package/dist/{chunk-CQYEDODS.js.map → chunk-35U5YNRR.js.map} +0 -0
  231. /package/dist/{chunk-NV4IHBZS.js.map → chunk-3XJU3OHE.js.map} +0 -0
  232. /package/dist/{chunk-IVZWHIEK.js.map → chunk-4HEGG5NJ.js.map} +0 -0
  233. /package/dist/{chunk-WE2BUQD2.js.map → chunk-4TCMCCC3.js.map} +0 -0
  234. /package/dist/{chunk-5YTXYPES.js.map → chunk-5A2FVGHT.js.map} +0 -0
  235. /package/dist/{chunk-NSXNXLYM.js.map → chunk-5GZC2ZM3.js.map} +0 -0
  236. /package/dist/{chunk-JYNH4FIM.js.map → chunk-77WF53XY.js.map} +0 -0
  237. /package/dist/{chunk-O5XKZCUD.js.map → chunk-7X4EF35A.js.map} +0 -0
  238. /package/dist/{chunk-SQKAECUL.js.map → chunk-7ZCTUI26.js.map} +0 -0
  239. /package/dist/{chunk-J6RGRZOY.js.map → chunk-AO3QSMCU.js.map} +0 -0
  240. /package/dist/{chunk-JDCPRJVS.js.map → chunk-AONK5GCC.js.map} +0 -0
  241. /package/dist/{chunk-TYMDCIQM.js.map → chunk-C472BRJ4.js.map} +0 -0
  242. /package/dist/{chunk-MBXKRHSS.js.map → chunk-CCNRFAL3.js.map} +0 -0
  243. /package/dist/{chunk-BZW5IL43.js.map → chunk-DCA2BDHA.js.map} +0 -0
  244. /package/dist/{chunk-JBBWALNI.js.map → chunk-DCICHSRS.js.map} +0 -0
  245. /package/dist/{chunk-2XA2ZML4.js.map → chunk-FG6IQ3ZL.js.map} +0 -0
  246. /package/dist/{chunk-C2RJVZZL.js.map → chunk-G4GW5VOS.js.map} +0 -0
  247. /package/dist/{chunk-U2XSUCDF.js.map → chunk-GEWIFM4J.js.map} +0 -0
  248. /package/dist/{chunk-TNH5SLCD.js.map → chunk-HD4QCT2O.js.map} +0 -0
  249. /package/dist/{chunk-I3IYTUUI.js.map → chunk-HHJ5DZCZ.js.map} +0 -0
  250. /package/dist/{chunk-6QAZ5O6X.js.map → chunk-IEIADIPM.js.map} +0 -0
  251. /package/dist/{chunk-YPIOFSN3.js.map → chunk-IHAISFXP.js.map} +0 -0
  252. /package/dist/{chunk-GJTKMME7.js.map → chunk-JKM2AVVH.js.map} +0 -0
  253. /package/dist/{chunk-S45MDEEF.js.map → chunk-LMWVNF6X.js.map} +0 -0
  254. /package/dist/{chunk-TAMRU7A2.js.map → chunk-OKV7S356.js.map} +0 -0
  255. /package/dist/{chunk-IW4L4X65.js.map → chunk-P5A4E53B.js.map} +0 -0
  256. /package/dist/{chunk-JOK73NDT.js.map → chunk-P7OL22JP.js.map} +0 -0
  257. /package/dist/{chunk-TGIJTNM3.js.map → chunk-R5ZECURV.js.map} +0 -0
  258. /package/dist/{chunk-KOAJ3TZM.js.map → chunk-RFEXGW3L.js.map} +0 -0
  259. /package/dist/{chunk-F5ILTHMU.js.map → chunk-RNQPDV75.js.map} +0 -0
  260. /package/dist/{chunk-WWVJXBOT.js.map → chunk-SGM7CK7R.js.map} +0 -0
  261. /package/dist/{chunk-7MRT7EPB.js.map → chunk-SOQE5DUV.js.map} +0 -0
  262. /package/dist/{chunk-ZONKSLF2.js.map → chunk-TQMQZOMX.js.map} +0 -0
  263. /package/dist/{chunk-3HNKR65T.js.map → chunk-U6LTLN7O.js.map} +0 -0
  264. /package/dist/{chunk-UU6M64HI.js.map → chunk-UAK2AMO2.js.map} +0 -0
  265. /package/dist/{chunk-37VGJM3T.js.map → chunk-WQ3KAGOV.js.map} +0 -0
  266. /package/dist/{chunk-AI4USDRI.js.map → chunk-XQO4TAJS.js.map} +0 -0
  267. /package/dist/{chunk-SQOK5UM6.js.map → chunk-ZBENTRFS.js.map} +0 -0
  268. /package/dist/{chunk-6QE4DUYC.js.map → chunk-ZDITTESU.js.map} +0 -0
  269. /package/dist/{crypto-456N7UVX.js.map → crypto-2LU6XUFF.js.map} +0 -0
  270. /package/dist/{delegation-DP4COTXB.js.map → delegation-6ABSJGXV.js.map} +0 -0
  271. /package/dist/{executor-4IEW4KG5.js.map → executor-JKMSEB34.js.map} +0 -0
  272. /package/dist/{executor-KYJCJCIN.js.map → executor-UYXSQB4D.js.map} +0 -0
  273. /package/dist/{executor-W7VIBOBZ.js.map → executor-VJSCTBWY.js.map} +0 -0
  274. /package/dist/{fanout-sidecar-YXNAEZ33.js.map → fanout-sidecar-ZQT4Y7PF.js.map} +0 -0
  275. /package/dist/{issue-JXC6T2QR.js.map → issue-KLRMW5DH.js.map} +0 -0
  276. /package/dist/{ledger-I7JUYP4L.js.map → kernel/index.js.map} +0 -0
  277. /package/dist/{noydb-VGR2HLDB.js.map → ledger-VOS2X3WJ.js.map} +0 -0
  278. /package/dist/{public-envelope-5XRTUNKF.js.map → noydb-2PI2ZBX6.js.map} +0 -0
  279. /package/dist/{registry-ATRHOG5B.js.map → public-envelope-IJJMWSTJ.js.map} +0 -0
  280. /package/dist/{registry-LEHB26TY.js.map → registry-GAIFVWXF.js.map} +0 -0
  281. /package/dist/{registry-NWHOLD5M.js.map → registry-J77ZUQ7G.js.map} +0 -0
  282. /package/dist/{revoke-5IEK22KT.js.map → registry-JGEVJ6YC.js.map} +0 -0
  283. /package/dist/{signer-I6YARZQA.js.map → revoke-WUY4AYRJ.js.map} +0 -0
  284. /package/dist/{stale-CPESGAPL.js.map → signer-UJF3CFDC.js.map} +0 -0
  285. /package/dist/{state-vault-JR3CFGNP.js.map → stale-PW6VBGSP.js.map} +0 -0
@@ -203,7 +203,7 @@ var init_format = __esm({
203
203
  });
204
204
 
205
205
  // src/errors.ts
206
- var NoydbError, DebugPlaintextError, DebugReservedFieldError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, FieldFrozenError, InvariantError, AmendmentForbiddenError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, ValidationError, SchemaValidationError, SchemaUpdateError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, LocaleNotSpecifiedError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, PartitionExtractionError, TransferSealError, AdoptionStateError, AttestationError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, DerivationCycleError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, UnknownShardError, ShardProvisioningError, DataResidencyError, CrossShardJoinError, VaultTemplateNotFoundError, ForgetStrategyNotConfiguredError, RecordCekNotFoundError;
206
+ var NoydbError, DebugPlaintextError, DebugReservedFieldError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, FieldFrozenError, InvariantError, AmendmentForbiddenError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, ValidationError, SchemaValidationError, SchemaUpdateError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, LocaleNotSpecifiedError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, PartitionExtractionError, TransferSealError, AdoptionStateError, AttestationError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, DerivationCycleError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, FederationMovedError, ForgetStrategyNotConfiguredError, RecordCekNotFoundError;
207
207
  var init_errors = __esm({
208
208
  "src/errors.ts"() {
209
209
  "use strict";
@@ -342,18 +342,6 @@ var init_errors = __esm({
342
342
  this.offendingCollection = offendingCollection;
343
343
  }
344
344
  };
345
- ReservedVaultNameError = class extends NoydbError {
346
- /** The rejected vault name. */
347
- vaultName;
348
- constructor(vaultName) {
349
- super(
350
- "RESERVED_VAULT_NAME",
351
- `"${vaultName}" is a reserved internal vault name and cannot be used as a group name or partition key`
352
- );
353
- this.name = "ReservedVaultNameError";
354
- this.vaultName = vaultName;
355
- }
356
- };
357
345
  FieldFrozenError = class extends NoydbError {
358
346
  collection;
359
347
  id;
@@ -941,58 +929,13 @@ Resolutions:
941
929
  this.expected = expected;
942
930
  }
943
931
  };
944
- UnknownShardError = class extends NoydbError {
945
- partitionKey;
946
- constructor(partitionKey, groupName) {
947
- super(
948
- "SHARD_UNKNOWN",
949
- `No shard for partition key "${partitionKey}" in vault group "${groupName}" and autoCreate is disabled. Call group.createShard(${JSON.stringify(partitionKey)}) first, or enable sharding.autoCreate.`
950
- );
951
- this.name = "UnknownShardError";
952
- this.partitionKey = partitionKey;
953
- }
954
- };
955
- ShardProvisioningError = class extends NoydbError {
956
- vaultId;
957
- constructor(vaultId, partitionKey) {
958
- super(
959
- "SHARD_PROVISIONING",
960
- `Registry has a row for partition "${partitionKey}" (vault "${vaultId}") but that vault is not provisioned in the store. Refusing to recreate it \u2014 the registry and store have diverged. Investigate before retrying.`
961
- );
962
- this.name = "ShardProvisioningError";
963
- this.vaultId = vaultId;
964
- }
965
- };
966
- DataResidencyError = class extends NoydbError {
967
- vaultId;
968
- requiredRegion;
969
- backendRegion;
970
- constructor(vaultId, requiredRegion, backendRegion) {
971
- super(
972
- "DATA_RESIDENCY",
973
- `Shard "${vaultId}" requires region "${requiredRegion}" but its placement backend declares region ${backendRegion === void 0 ? "(none)" : `"${backendRegion}"`}. Refusing to provision \u2014 route this shard to a region-correct backend via routeStore({ vaultRoutes }) (e.g. a region-encoded partition key) before retrying.`
974
- );
975
- this.name = "DataResidencyError";
976
- this.vaultId = vaultId;
977
- this.requiredRegion = requiredRegion;
978
- this.backendRegion = backendRegion;
979
- }
980
- };
981
- CrossShardJoinError = class extends NoydbError {
982
- constructor(message) {
983
- super("CROSS_SHARD_JOIN", message);
984
- this.name = "CrossShardJoinError";
985
- }
986
- };
987
- VaultTemplateNotFoundError = class extends NoydbError {
988
- templateName;
989
- constructor(templateName) {
932
+ FederationMovedError = class extends NoydbError {
933
+ constructor(api) {
990
934
  super(
991
- "VAULT_TEMPLATE_NOT_FOUND",
992
- `No vault template registered under "${templateName}". Register it with db.withVaultTemplate(${JSON.stringify(templateName)}, { version, configure }) before opening the vault group.`
935
+ "FEDERATION_MOVED",
936
+ `${api} has moved to @klum-db/lobby. Install @klum-db/lobby, then: import { createLobby } from '@klum-db/lobby'; const lobby = createLobby(db); await lobby.${api}(...)`
993
937
  );
994
- this.name = "VaultTemplateNotFoundError";
995
- this.templateName = templateName;
938
+ this.name = "FederationMovedError";
996
939
  }
997
940
  };
998
941
  ForgetStrategyNotConfiguredError = class extends NoydbError {
@@ -1730,6 +1673,17 @@ function parsePrefixAndHeader(bytes) {
1730
1673
  function readNoydbBundleHeader(bytes) {
1731
1674
  return parsePrefixAndHeader(bytes).header;
1732
1675
  }
1676
+ function readNoydbBundlePublicEnvelope(bytes, opts = {}) {
1677
+ const header = parsePrefixAndHeader(bytes).header;
1678
+ const env = header.publicEnvelope;
1679
+ if (!env) return void 0;
1680
+ if (opts.locale === void 0) return env;
1681
+ return {
1682
+ ...env,
1683
+ ...env.name !== void 0 ? { name: pickLocale(env.name, opts.locale, env.defaultLocale) } : {},
1684
+ ...env.description !== void 0 ? { description: pickLocale(env.description, opts.locale, env.defaultLocale) } : {}
1685
+ };
1686
+ }
1733
1687
  async function readNoydbBundle(bytes, opts = {}) {
1734
1688
  const { header, bodyOffset, algo } = parsePrefixAndHeader(bytes);
1735
1689
  const body = bytes.slice(bodyOffset);
@@ -1771,6 +1725,7 @@ var init_bundle = __esm({
1771
1725
  "use strict";
1772
1726
  init_format();
1773
1727
  init_errors();
1728
+ init_storage();
1774
1729
  cachedBrotliSupport = null;
1775
1730
  }
1776
1731
  });
@@ -2518,12 +2473,14 @@ var init_user_envelope = __esm({
2518
2473
  // src/team/keyring.ts
2519
2474
  function canGrant(callerRole, targetRole) {
2520
2475
  if (callerRole === "owner") return true;
2476
+ if (callerRole === "custodian") return false;
2521
2477
  if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
2522
2478
  return false;
2523
2479
  }
2524
2480
  function canRevoke(callerRole, targetRole) {
2525
2481
  if (targetRole === "owner") return false;
2526
2482
  if (callerRole === "owner") return true;
2483
+ if (callerRole === "custodian") return false;
2527
2484
  if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
2528
2485
  return false;
2529
2486
  }
@@ -2687,7 +2644,7 @@ async function grant(adapter, vault, callerKeyring, options) {
2687
2644
  wrappedDeks[collName] = await wrapKey(dek, newKek);
2688
2645
  }
2689
2646
  }
2690
- if (options.role === "owner" || options.role === "admin" || options.role === "viewer") {
2647
+ if (options.role === "owner" || options.role === "admin" || options.role === "custodian" || options.role === "viewer") {
2691
2648
  for (const [collName, dek] of callerKeyring.deks) {
2692
2649
  if (!(collName in wrappedDeks)) {
2693
2650
  wrappedDeks[collName] = await wrapKey(dek, newKek);
@@ -2835,6 +2792,11 @@ async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
2835
2792
  await writeKeyringFile(adapter, vault, options.userId, next);
2836
2793
  }
2837
2794
  async function rotateKeys(adapter, vault, callerKeyring, collections) {
2795
+ if (callerKeyring.role === "custodian") {
2796
+ throw new PermissionDeniedError(
2797
+ "custodian cannot rotate keys (FR-6: re-key is an owner-only meta-capability; use the Deed owner)"
2798
+ );
2799
+ }
2838
2800
  const newDeks = /* @__PURE__ */ new Map();
2839
2801
  for (const collName of collections) {
2840
2802
  newDeks.set(collName, await generateDEK());
@@ -2944,7 +2906,7 @@ async function buildRecipientKeyringFile(callerKeyring, recipient) {
2944
2906
  wrappedDeks[collName] = await wrapKey(dek, newKek);
2945
2907
  }
2946
2908
  }
2947
- if (role === "owner" || role === "admin" || role === "viewer") {
2909
+ if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") {
2948
2910
  for (const [collName, dek] of callerKeyring.deks) {
2949
2911
  if (!(collName in wrappedDeks)) {
2950
2912
  wrappedDeks[collName] = await wrapKey(dek, newKek);
@@ -3018,12 +2980,13 @@ async function ensureCollectionDEK(adapter, vault, keyring) {
3018
2980
  };
3019
2981
  }
3020
2982
  function hasWritePermission(keyring, collectionName) {
3021
- if (keyring.role === "owner" || keyring.role === "admin") return true;
2983
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return true;
3022
2984
  if (keyring.role === "viewer" || keyring.role === "client") return false;
3023
2985
  return keyring.permissions[collectionName] === "rw";
3024
2986
  }
3025
2987
  function hasAccess(keyring, collectionName) {
3026
- if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "viewer") return true;
2988
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian" || keyring.role === "viewer")
2989
+ return true;
3027
2990
  return collectionName in keyring.permissions;
3028
2991
  }
3029
2992
  async function persistKeyring(adapter, vault, keyring) {
@@ -3075,7 +3038,7 @@ function hasImportCapability(keyring, tier, format) {
3075
3038
  return cap?.bundle === true;
3076
3039
  }
3077
3040
  function resolvePermissions(role, explicit) {
3078
- if (role === "owner" || role === "admin" || role === "viewer") return {};
3041
+ if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") return {};
3079
3042
  return explicit ?? {};
3080
3043
  }
3081
3044
  async function writeKeyringFile(adapter, vault, userId, keyringFile) {
@@ -3781,15 +3744,6 @@ var init_store = __esm({
3781
3744
  }
3782
3745
  });
3783
3746
 
3784
- // src/federation/constants.ts
3785
- var STATE_VAULT_NAME;
3786
- var init_constants2 = __esm({
3787
- "src/federation/constants.ts"() {
3788
- "use strict";
3789
- STATE_VAULT_NAME = "__noydb_state__";
3790
- }
3791
- });
3792
-
3793
3747
  // src/policy/errors.ts
3794
3748
  var PolicyDeniedError, RecoveryNotEnrolledError, ManagedRecoveryNotEnrolledError, RecoveryProfileNotImplementedError;
3795
3749
  var init_errors2 = __esm({
@@ -4992,10 +4946,10 @@ function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAf
4992
4946
  }
4993
4947
  }
4994
4948
  function parseToScaledInt(input, scale, rounding) {
4995
- const canonical2 = toCanonicalDecimalString(input);
4996
- if (canonical2 === null) return { ok: false, reason: "nonfinite" };
4997
- const negative = canonical2.startsWith("-");
4998
- const unsigned = negative ? canonical2.slice(1) : canonical2;
4949
+ const canonical = toCanonicalDecimalString(input);
4950
+ if (canonical === null) return { ok: false, reason: "nonfinite" };
4951
+ const negative = canonical.startsWith("-");
4952
+ const unsigned = negative ? canonical.slice(1) : canonical;
4999
4953
  const dot = unsigned.indexOf(".");
5000
4954
  const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
5001
4955
  const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
@@ -5491,7 +5445,7 @@ function dekKey(collection, tier) {
5491
5445
  }
5492
5446
  function assertTierAccess(keyring, collection, tier) {
5493
5447
  if (tier <= 0) return;
5494
- if (keyring.role === "owner" || keyring.role === "admin") return;
5448
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return;
5495
5449
  if (!keyring.deks.has(dekKey(collection, tier))) {
5496
5450
  throw new TierNotGrantedError(collection, tier);
5497
5451
  }
@@ -6562,7 +6516,7 @@ function serializeClause(clause) {
6562
6516
  }
6563
6517
  function canonicalCtxHash(ctx) {
6564
6518
  if (ctx === void 0) return "";
6565
- const canonical2 = JSON.stringify(ctx, (_key, value) => {
6519
+ const canonical = JSON.stringify(ctx, (_key, value) => {
6566
6520
  if (value && typeof value === "object" && !Array.isArray(value)) {
6567
6521
  const sorted = {};
6568
6522
  for (const k of Object.keys(value).sort()) {
@@ -6573,8 +6527,8 @@ function canonicalCtxHash(ctx) {
6573
6527
  return value;
6574
6528
  });
6575
6529
  let h = 5381;
6576
- for (let i = 0; i < canonical2.length; i++) {
6577
- h = (h << 5) + h ^ canonical2.charCodeAt(i);
6530
+ for (let i = 0; i < canonical.length; i++) {
6531
+ h = (h << 5) + h ^ canonical.charCodeAt(i);
6578
6532
  }
6579
6533
  return (h >>> 0).toString(16).padStart(8, "0");
6580
6534
  }
@@ -7283,29 +7237,6 @@ var init_builder = __esm({
7283
7237
  }
7284
7238
  });
7285
7239
 
7286
- // src/aggregate/aggregation.ts
7287
- function reduceRecords(records, spec) {
7288
- const state = {};
7289
- for (const key of Object.keys(spec)) {
7290
- state[key] = spec[key].init();
7291
- }
7292
- for (const record of records) {
7293
- for (const key of Object.keys(spec)) {
7294
- state[key] = spec[key].step(state[key], record);
7295
- }
7296
- }
7297
- const result = {};
7298
- for (const key of Object.keys(spec)) {
7299
- result[key] = spec[key].finalize(state[key]);
7300
- }
7301
- return result;
7302
- }
7303
- var init_aggregation = __esm({
7304
- "src/aggregate/aggregation.ts"() {
7305
- "use strict";
7306
- }
7307
- });
7308
-
7309
7240
  // src/aggregate/canonical-key.ts
7310
7241
  function canonicalGroupKey(fields, row) {
7311
7242
  const sorted = [...fields].sort();
@@ -8534,7 +8465,7 @@ var init_transaction = __esm({
8534
8465
  const v = this._db.vault(name);
8535
8466
  if (this._amendment && !this._amendmentVaults.has(name)) {
8536
8467
  const role = v.role;
8537
- if (role !== "admin" && role !== "owner") {
8468
+ if (role !== "admin" && role !== "owner" && role !== "custodian") {
8538
8469
  throw new AmendmentForbiddenError(v.userId, role);
8539
8470
  }
8540
8471
  const reg = v._getGuardRegistry();
@@ -8904,12 +8835,12 @@ var init_dependency_analyzer = __esm({
8904
8835
 
8905
8836
  // src/materialized-views/query-hash.ts
8906
8837
  async function computeQueryHash(mvName, dependencies, queryPlanSummary) {
8907
- const canonical2 = JSON.stringify({
8838
+ const canonical = JSON.stringify({
8908
8839
  mvName,
8909
8840
  dependencies: [...dependencies].sort(),
8910
8841
  queryPlanSummary
8911
8842
  });
8912
- const bytes = new TextEncoder().encode(canonical2);
8843
+ const bytes = new TextEncoder().encode(canonical);
8913
8844
  const digest = await crypto.subtle.digest("SHA-256", bytes);
8914
8845
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
8915
8846
  }
@@ -9722,6 +9653,13 @@ var init_collection = __esm({
9722
9653
  * flag) still decrypts CEK records.
9723
9654
  */
9724
9655
  perRecordCek;
9656
+ /**
9657
+ * Per-record provenance opt-in (`provenance: true`). When set, `put()` calls
9658
+ * that supply a `source` option stamp `_source`/`_sourceTs` onto the
9659
+ * unencrypted envelope metadata. Off by default — zero cost for collections
9660
+ * that don't need lineage tracking (FR-5, #445).
9661
+ */
9662
+ provenance;
9725
9663
  /**
9726
9664
  * Session-scoped `(id) → CEK` cache for this collection. Lets updates
9727
9665
  * reuse a record's stable CEK and lets repeated reads skip the AES-KW
@@ -9881,6 +9819,7 @@ var init_collection = __esm({
9881
9819
  }
9882
9820
  this.perRecordCek = opts.perRecordKeys === true;
9883
9821
  this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
9822
+ this.provenance = opts.provenance === true;
9884
9823
  if (opts.crdt && opts.onRegisterConflictResolver) {
9885
9824
  const crdtMode = opts.crdt;
9886
9825
  const crdtResolver = async (id, local, remote) => {
@@ -10068,6 +10007,33 @@ var init_collection = __esm({
10068
10007
  if (json === null) return null;
10069
10008
  return JSON.parse(json);
10070
10009
  }
10010
+ /**
10011
+ * Read a record's unencrypted envelope metadata (version, timestamps,
10012
+ * provenance) without decrypting the body.
10013
+ *
10014
+ * Returns `null` when no envelope exists for `id` (record absent or never
10015
+ * written). Only `_source`/`_sourceTs` fields are populated when the
10016
+ * collection was opened with `provenance: true` AND the record was written
10017
+ * with a `source` option — but this method works on any collection because
10018
+ * it reads the raw envelope directly.
10019
+ *
10020
+ * @returns `{ version, timestamp, by?, source?, sourceTs? }` or `null`.
10021
+ *
10022
+ * @example
10023
+ * const meta = await clients.getMetadata('c1')
10024
+ * if (meta) console.log(meta.source, meta.timestamp)
10025
+ */
10026
+ async getMetadata(id) {
10027
+ const env = await this.adapter.get(this.vault, this.name, id);
10028
+ if (!env) return null;
10029
+ return {
10030
+ version: env._v,
10031
+ timestamp: env._ts,
10032
+ ...env._by !== void 0 ? { by: env._by } : {},
10033
+ ...env._source !== void 0 ? { source: env._source } : {},
10034
+ ...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
10035
+ };
10036
+ }
10071
10037
  /**
10072
10038
  * Return a presence handle for this collection.
10073
10039
  *
@@ -10105,6 +10071,14 @@ var init_collection = __esm({
10105
10071
  * `reason` is stamped onto the resulting ledger entry
10106
10072
  * so audit consumers can filter via
10107
10073
  * `entries.filter(e => e.reason?.startsWith('import:'))`.
10074
+ * `source` is an opaque source id (e.g. `'crm-sync'`, `'firm-A'`)
10075
+ * stamped onto the envelope as `_source`/`_sourceTs` when
10076
+ * the collection has `provenance: true`. Ignored otherwise
10077
+ * (zero cost). (FR-5, #445)
10078
+ * `sourceTs` is an optional ISO-8601 origin timestamp override;
10079
+ * when supplied together with `source` on a provenance collection,
10080
+ * replaces the machine-stamped `now()` so re-merges preserve the
10081
+ * ORIGIN refresh time across vaults. (FR-4)
10108
10082
  */
10109
10083
  async put(id, record, options) {
10110
10084
  await this.schemaUpdateGate?.assertWritable();
@@ -10136,6 +10110,20 @@ var init_collection = __esm({
10136
10110
  if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
10137
10111
  }
10138
10112
  }
10113
+ /**
10114
+ * Validate a record against this collection's schema WITHOUT writing it.
10115
+ * Returns the (possibly coerced) record on success; throws
10116
+ * {@link SchemaValidationError} (direction: `'input'`) on violation.
10117
+ * A no-op pass-through when no schema is declared.
10118
+ *
10119
+ * Used by FR-8 migrate-then-merge to pre-validate all staged records
10120
+ * before `mergeDecryptedRecords` writes anything — so a failed upgrade
10121
+ * never half-writes the receiver.
10122
+ */
10123
+ async validateInput(record) {
10124
+ if (this.schema === void 0) return record;
10125
+ return validateSchemaInput(this.schema, record, `validateInput(${this.name})`);
10126
+ }
10139
10127
  /** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
10140
10128
  #hooksActive() {
10141
10129
  return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
@@ -10293,7 +10281,7 @@ var init_collection = __esm({
10293
10281
  }
10294
10282
  const version2 = existingVersion + 1;
10295
10283
  const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
10296
- const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
10284
+ const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2, options?.source, options?.sourceTs);
10297
10285
  await this.adapter.put(this.vault, this.name, id, envelope2);
10298
10286
  const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
10299
10287
  const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
@@ -10372,7 +10360,7 @@ var init_collection = __esm({
10372
10360
  });
10373
10361
  }
10374
10362
  }
10375
- const envelope = await this.encryptRecord(record, version, cek);
10363
+ const envelope = await this.encryptRecord(record, version, cek, options?.source, options?.sourceTs);
10376
10364
  await this.adapter.put(this.vault, this.name, id, envelope);
10377
10365
  if (this.ledger) {
10378
10366
  const appendInput = {
@@ -10665,7 +10653,7 @@ var init_collection = __esm({
10665
10653
  priorEnvelope
10666
10654
  });
10667
10655
  }
10668
- await outputCollection.put(entry.key, entry.value);
10656
+ await outputCollection.put(entry.key, entry.value, { source: "derived" });
10669
10657
  }
10670
10658
  await saveFanoutSidecar2(this.adapter, this.vault, {
10671
10659
  source: spec.source,
@@ -10698,7 +10686,7 @@ var init_collection = __esm({
10698
10686
  priorEnvelope: prior
10699
10687
  });
10700
10688
  }
10701
- await outputCollection.put(run.runId, patched);
10689
+ await outputCollection.put(run.runId, patched, { source: "derived" });
10702
10690
  continue;
10703
10691
  }
10704
10692
  if (txCtx !== null) {
@@ -10713,7 +10701,7 @@ var init_collection = __esm({
10713
10701
  priorEnvelope: prior
10714
10702
  });
10715
10703
  }
10716
- await outputCollection.put(run.runId, out.value);
10704
+ await outputCollection.put(run.runId, out.value, { source: "derived" });
10717
10705
  }
10718
10706
  }
10719
10707
  }
@@ -12376,7 +12364,7 @@ var init_collection = __esm({
12376
12364
  * (see {@link encryptRecord}). Rejects `_`-prefixed record fields, which
12377
12365
  * would collide with the reserved metadata namespace.
12378
12366
  */
12379
- buildDebugEnvelope(record, version) {
12367
+ buildDebugEnvelope(record, version, source, sourceTs) {
12380
12368
  const rec = record;
12381
12369
  for (const key of Object.keys(rec)) {
12382
12370
  if (key.startsWith("_")) throw new DebugReservedFieldError(this.name, key);
@@ -12389,11 +12377,13 @@ var init_collection = __esm({
12389
12377
  _data: "",
12390
12378
  _by: this.keyring.userId,
12391
12379
  _debug: NOYDB_FORMAT_VERSION,
12380
+ ...this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {},
12392
12381
  ...rec
12393
12382
  };
12394
12383
  }
12395
- async encryptJsonString(json, version, cek) {
12384
+ async encryptJsonString(json, version, cek, source, sourceTs) {
12396
12385
  const by = this.keyring.userId;
12386
+ const provenanceFields = this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {};
12397
12387
  if (!this.encrypted) {
12398
12388
  return {
12399
12389
  _noydb: NOYDB_FORMAT_VERSION,
@@ -12401,7 +12391,8 @@ var init_collection = __esm({
12401
12391
  _ts: (/* @__PURE__ */ new Date()).toISOString(),
12402
12392
  _iv: "",
12403
12393
  _data: json,
12404
- _by: by
12394
+ _by: by,
12395
+ ...provenanceFields
12405
12396
  };
12406
12397
  }
12407
12398
  const dek = await this.getDEK(this.name);
@@ -12415,7 +12406,8 @@ var init_collection = __esm({
12415
12406
  _iv: iv2,
12416
12407
  _data: data2,
12417
12408
  _by: by,
12418
- _cek: wrapped
12409
+ _cek: wrapped,
12410
+ ...provenanceFields
12419
12411
  };
12420
12412
  }
12421
12413
  const { iv, data } = await encrypt(json, dek);
@@ -12425,14 +12417,15 @@ var init_collection = __esm({
12425
12417
  _ts: (/* @__PURE__ */ new Date()).toISOString(),
12426
12418
  _iv: iv,
12427
12419
  _data: data,
12428
- _by: by
12420
+ _by: by,
12421
+ ...provenanceFields
12429
12422
  };
12430
12423
  }
12431
- async encryptRecord(record, version, cek) {
12424
+ async encryptRecord(record, version, cek, source, sourceTs) {
12432
12425
  if (!this.encrypted && this.keyring.debugPlaintext === true && !this.name.startsWith("_")) {
12433
- return this.buildDebugEnvelope(record, version);
12426
+ return this.buildDebugEnvelope(record, version, source, sourceTs);
12434
12427
  }
12435
- const base = await this.encryptJsonString(JSON.stringify(record), version, cek);
12428
+ const base = await this.encryptJsonString(JSON.stringify(record), version, cek, source, sourceTs);
12436
12429
  if (!this.deterministicFields || !this.encrypted) return base;
12437
12430
  const dek = await this.getDEK(this.name);
12438
12431
  const rec = record;
@@ -12566,7 +12559,8 @@ var init_collection = __esm({
12566
12559
  _iv: iv,
12567
12560
  _data: data,
12568
12561
  _by: this.keyring.userId,
12569
- ...tier > 0 && { _tier: tier }
12562
+ ...tier > 0 && { _tier: tier },
12563
+ ...this.provenance && opts?.source !== void 0 ? { _source: opts.source, _sourceTs: opts.sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {}
12570
12564
  };
12571
12565
  await this.adapter.put(this.vault, this.name, id, envelope);
12572
12566
  if (tier > 0) {
@@ -12873,43 +12867,49 @@ function randomId() {
12873
12867
  const b = globalThis.crypto.getRandomValues(new Uint8Array(12));
12874
12868
  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
12875
12869
  }
12876
- async function freezeAndDeleteClosure(vault, collections, opts) {
12870
+ async function freezeSnapshotOnly(vault, collections, opts) {
12877
12871
  const { name: vaultName, adapter } = vault._introspectState();
12878
12872
  const closure = [];
12879
12873
  for (const c of collections) {
12880
12874
  for (const id of await adapter.list(vaultName, c)) closure.push({ collection: c, id });
12881
12875
  }
12882
- let snapshot;
12883
- if (opts.disposition === "freeze") {
12884
- const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
12885
- const snap = {};
12886
- for (const { collection, id } of closure) {
12887
- const env = await adapter.get(vaultName, collection, id);
12888
- if (env) (snap[collection] ??= {})[id] = env;
12889
- }
12890
- const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
12891
- const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
12892
- const sha = await sha256Hex2(ENC.encode(body));
12893
- await adapter.put(
12894
- vaultName,
12895
- FROZEN_SNAPSHOTS_COLLECTION,
12896
- withdrawalId,
12897
- { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
12898
- 0
12899
- );
12900
- await vault._getLedgerOrNull()?.append({
12901
- op: "lifecycle",
12902
- collection: "",
12903
- id: "",
12904
- version: 0,
12905
- actor: opts.actorUserId,
12906
- payloadHash: "",
12907
- reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
12908
- });
12909
- snapshot = { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
12910
- }
12876
+ const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
12877
+ const snap = {};
12911
12878
  for (const { collection, id } of closure) {
12912
- await vault.collection(collection).delete(id);
12879
+ const env = await adapter.get(vaultName, collection, id);
12880
+ if (env) (snap[collection] ??= {})[id] = env;
12881
+ }
12882
+ const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
12883
+ const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
12884
+ const sha = await sha256Hex2(ENC.encode(body));
12885
+ await adapter.put(
12886
+ vaultName,
12887
+ FROZEN_SNAPSHOTS_COLLECTION,
12888
+ withdrawalId,
12889
+ { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
12890
+ 0
12891
+ );
12892
+ await vault._getLedgerOrNull()?.append({
12893
+ op: "lifecycle",
12894
+ collection: "",
12895
+ id: "",
12896
+ version: 0,
12897
+ actor: opts.actorUserId,
12898
+ payloadHash: "",
12899
+ reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
12900
+ });
12901
+ return { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
12902
+ }
12903
+ async function freezeAndDeleteClosure(vault, collections, opts) {
12904
+ const snapshot = opts.disposition === "freeze" ? await freezeSnapshotOnly(vault, collections, {
12905
+ actorUserId: opts.actorUserId,
12906
+ ...opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}
12907
+ }) : void 0;
12908
+ const { name: vaultName, adapter } = vault._introspectState();
12909
+ for (const c of collections) {
12910
+ for (const id of await adapter.list(vaultName, c)) {
12911
+ await vault.collection(c).delete(id);
12912
+ }
12913
12913
  }
12914
12914
  return snapshot;
12915
12915
  }
@@ -12921,6 +12921,11 @@ async function withdrawAccessibleData(vault, opts) {
12921
12921
  "unilateralWithdrawal is the scoped self-service path; an owner/admin should use extractPartition"
12922
12922
  );
12923
12923
  }
12924
+ if (keyring.role === "custodian") {
12925
+ throw new ReadOnlyError(
12926
+ "a custodian cannot destructively withdraw/sever; use vault.custody.liberate for an audited ownership claim"
12927
+ );
12928
+ }
12924
12929
  if (keyring.role === "client" || keyring.role === "viewer") {
12925
12930
  throw new ReadOnlyError(
12926
12931
  "read-only role cannot self-serve a destructive withdrawal \u2014 use requestWithdrawal (two-party)"
@@ -14819,6 +14824,157 @@ var init_api = __esm({
14819
14824
  }
14820
14825
  });
14821
14826
 
14827
+ // src/custody/index.ts
14828
+ var CustodyApi;
14829
+ var init_custody = __esm({
14830
+ "src/custody/index.ts"() {
14831
+ "use strict";
14832
+ CustodyApi = class {
14833
+ constructor(_grantCustodian, _revokeCustodian, _liberate) {
14834
+ this._grantCustodian = _grantCustodian;
14835
+ this._revokeCustodian = _revokeCustodian;
14836
+ this._liberate = _liberate;
14837
+ }
14838
+ _grantCustodian;
14839
+ _revokeCustodian;
14840
+ _liberate;
14841
+ /**
14842
+ * Owner-only: grant the FR-6 `custodian` role. The custodian operates every
14843
+ * collection (rw + access) but is provably unable to grant / revoke / rotate /
14844
+ * extract-and-sever. Defended in depth (gate + owner-only role check) inside
14845
+ * the injected `Noydb.grantCustodian`.
14846
+ */
14847
+ async grantCustodian(options, factors) {
14848
+ return this._grantCustodian(options, factors);
14849
+ }
14850
+ /** Owner-only: revoke a custodian. */
14851
+ async revokeCustodian(options, factors) {
14852
+ return this._revokeCustodian(options, factors);
14853
+ }
14854
+ /**
14855
+ * Custodian-only: the audited claim of ownership over a sealed-owner (Deed)
14856
+ * vault. Mints a DISTINCT new owner re-wrapping the incumbent DEKs under a
14857
+ * fresh KEK (the latent owner is never impersonated), ledger-audited. See
14858
+ * {@link liberateVault}.
14859
+ */
14860
+ async liberate(opts) {
14861
+ return this._liberate(opts);
14862
+ }
14863
+ };
14864
+ }
14865
+ });
14866
+
14867
+ // src/team/deed.ts
14868
+ async function loadDeedMarker(store, vault) {
14869
+ const envelope = await store.get(vault, "_meta", DEED_RECORD_ID);
14870
+ if (!envelope) return null;
14871
+ let payload;
14872
+ try {
14873
+ payload = JSON.parse(envelope._data);
14874
+ } catch {
14875
+ return null;
14876
+ }
14877
+ if (typeof payload !== "object" || payload === null) return null;
14878
+ const r = payload;
14879
+ if (r._noydb_deed !== 1) return null;
14880
+ if (typeof r.ownerUserId !== "string" || typeof r.sealedUnder !== "string" || r.latent !== true || typeof r.issuedAt !== "string") {
14881
+ return null;
14882
+ }
14883
+ const marker = {
14884
+ ownerUserId: r.ownerUserId,
14885
+ sealedUnder: r.sealedUnder,
14886
+ latent: true,
14887
+ issuedAt: r.issuedAt,
14888
+ ...typeof r.liberatedAt === "string" ? { liberatedAt: r.liberatedAt } : {}
14889
+ };
14890
+ return marker;
14891
+ }
14892
+ async function saveDeedMarker(store, vault, marker) {
14893
+ const persisted = { _noydb_deed: 1, ...marker };
14894
+ const prior = await store.get(vault, "_meta", DEED_RECORD_ID);
14895
+ const env = {
14896
+ _noydb: NOYDB_FORMAT_VERSION,
14897
+ _v: (prior?._v ?? 0) + 1,
14898
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
14899
+ // AES-GCM bypassed — the marker is plaintext audit metadata.
14900
+ _iv: "",
14901
+ _data: JSON.stringify(persisted)
14902
+ };
14903
+ await store.put(vault, "_meta", DEED_RECORD_ID, env);
14904
+ }
14905
+ var DEED_RECORD_ID;
14906
+ var init_deed = __esm({
14907
+ "src/team/deed.ts"() {
14908
+ "use strict";
14909
+ init_types();
14910
+ DEED_RECORD_ID = "deed";
14911
+ }
14912
+ });
14913
+
14914
+ // src/custody/liberate.ts
14915
+ async function liberateVault(vault, opts) {
14916
+ await vault.noydb.checkGate(vault.name, "liberate-vault", opts.factors);
14917
+ const { name: vaultName, adapter, keyring } = vault._introspectState();
14918
+ if (keyring.role !== "custodian") {
14919
+ throw new PermissionDeniedError(
14920
+ "liberation is claimed only by the custodian (the de-facto authority holding the DEKs)"
14921
+ );
14922
+ }
14923
+ const existing = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
14924
+ if (existing) {
14925
+ throw new PermissionDeniedError(
14926
+ `liberateVault: newOwnerId "${opts.newOwnerId}" already exists as a principal; choose a fresh id (liberation mints a distinct owner, it never overwrites an existing keyring)`
14927
+ );
14928
+ }
14929
+ const collections = await listOperationalCollections(vault);
14930
+ const snapshot = await freezeSnapshotOnly(vault, collections, { actorUserId: keyring.userId });
14931
+ const newOwner = await createOwnerKeyring(adapter, vaultName, opts.newOwnerId, opts.newOwnerPassphrase);
14932
+ if (!newOwner.kek) {
14933
+ throw new PermissionDeniedError(
14934
+ `new owner keyring for "${opts.newOwnerId}" has no KEK to re-wrap the incumbent DEKs under`
14935
+ );
14936
+ }
14937
+ const env = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
14938
+ if (!env) {
14939
+ throw new PermissionDeniedError(`new owner keyring for "${opts.newOwnerId}" did not persist`);
14940
+ }
14941
+ const keyringFile = JSON.parse(env._data);
14942
+ const mergedDeks = { ...keyringFile.deks };
14943
+ for (const [collection, dek] of keyring.deks) {
14944
+ mergedDeks[collection] = await wrapKey(dek, newOwner.kek);
14945
+ }
14946
+ const mergedFile = { ...keyringFile, deks: mergedDeks };
14947
+ await adapter.put(vaultName, "_keyring", opts.newOwnerId, { ...env, _data: JSON.stringify(mergedFile) });
14948
+ await vault._getLedgerOrNull()?.append({
14949
+ op: "lifecycle",
14950
+ collection: "",
14951
+ id: "",
14952
+ version: 0,
14953
+ actor: opts.newOwnerId,
14954
+ payloadHash: "",
14955
+ reason: `liberation-claimed:${opts.newOwnerId}:${opts.legalBasis}`
14956
+ });
14957
+ const marker = await loadDeedMarker(adapter, vaultName);
14958
+ if (marker) {
14959
+ await saveDeedMarker(adapter, vaultName, { ...marker, liberatedAt: (/* @__PURE__ */ new Date()).toISOString() });
14960
+ }
14961
+ return { snapshot };
14962
+ }
14963
+ async function listOperationalCollections(vault) {
14964
+ const { keyring } = vault._introspectState();
14965
+ return [...keyring.deks.keys()].filter((c) => !c.startsWith("_"));
14966
+ }
14967
+ var init_liberate = __esm({
14968
+ "src/custody/liberate.ts"() {
14969
+ "use strict";
14970
+ init_errors();
14971
+ init_crypto();
14972
+ init_keyring();
14973
+ init_withdraw_accessible();
14974
+ init_deed();
14975
+ }
14976
+ });
14977
+
14822
14978
  // src/persisted-schemas/canonicalize.ts
14823
14979
  function canonicalize(value) {
14824
14980
  if (value === null || typeof value !== "object") {
@@ -14868,8 +15024,8 @@ async function derivePersistedSchema(validator) {
14868
15024
  if (kind === "Zod") {
14869
15025
  const convert = await loadZodConverter();
14870
15026
  const jsonSchema = convert(validator);
14871
- const canonical2 = canonicalize(jsonSchema);
14872
- const hash = await sha256Hex2(new TextEncoder().encode(canonical2));
15027
+ const canonical = canonicalize(jsonSchema);
15028
+ const hash = await sha256Hex2(new TextEncoder().encode(canonical));
14873
15029
  return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt };
14874
15030
  }
14875
15031
  return {
@@ -15912,7 +16068,7 @@ var init_read_only_facade = __esm({
15912
16068
 
15913
16069
  // src/derivations/strategy-hash.ts
15914
16070
  async function computeStrategyHash(source, outputKeys, derive, sources) {
15915
- const canonical2 = JSON.stringify({
16071
+ const canonical = JSON.stringify({
15916
16072
  source,
15917
16073
  outputs: [...outputKeys].sort(),
15918
16074
  derive: derive.toString(),
@@ -15921,7 +16077,7 @@ async function computeStrategyHash(source, outputKeys, derive, sources) {
15921
16077
  // so strategies without siblings keep their existing hash.
15922
16078
  ...sources?.length ? { sources: [...sources].sort() } : {}
15923
16079
  });
15924
- const bytes = new TextEncoder().encode(canonical2);
16080
+ const bytes = new TextEncoder().encode(canonical);
15925
16081
  const digest = await crypto.subtle.digest("SHA-256", bytes);
15926
16082
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
15927
16083
  }
@@ -16254,6 +16410,8 @@ var init_vault = __esm({
16254
16410
  init_blob_compaction();
16255
16411
  init_magic_link_grant();
16256
16412
  init_api();
16413
+ init_custody();
16414
+ init_liberate();
16257
16415
  init_register();
16258
16416
  init_gate();
16259
16417
  init_fence_controller();
@@ -16361,6 +16519,18 @@ var init_vault = __esm({
16361
16519
  * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
16362
16520
  */
16363
16521
  user;
16522
+ /**
16523
+ * FR-6 custody API — the sovereign-custody surface, mirroring `vault.user.*`.
16524
+ *
16525
+ * - `grantCustodian(opts)` / `revokeCustodian(opts)` — owner-only: mint /
16526
+ * remove a `custodian` who operates the vault fully but can never grant /
16527
+ * rotate / sever / extract.
16528
+ * - `liberate(opts)` — custodian-only: the audited claim of ownership over a
16529
+ * sealed-owner (Deed) vault (mints a DISTINCT new owner; ledger-audited).
16530
+ *
16531
+ * @see docs/superpowers/specs/2026-06-17-fr6-deed-custodian-liberate-design.md
16532
+ */
16533
+ custody;
16364
16534
  /**
16365
16535
  * Optional callback that re-derives an UnlockedKeyring from the
16366
16536
  * adapter using the active user's passphrase. Called by `load()`
@@ -16571,6 +16741,11 @@ var init_vault = __esm({
16571
16741
  (requestId, opts2) => approveWithdrawal(this, requestId, opts2),
16572
16742
  (requestId, opts2) => rejectWithdrawal(this, requestId, opts2)
16573
16743
  );
16744
+ this.custody = new CustodyApi(
16745
+ (options, factors) => this.noydb.grantCustodian(this.name, options, factors),
16746
+ (options, factors) => this.noydb.revokeCustodian(this.name, options, factors),
16747
+ (opts2) => liberateVault(this, opts2)
16748
+ );
16574
16749
  }
16575
16750
  /**
16576
16751
  * Construct (or reconstruct) the lazy DEK resolver. Captures the
@@ -16798,6 +16973,7 @@ var init_vault = __esm({
16798
16973
  }
16799
16974
  collOpts.perRecordKeys = true;
16800
16975
  }
16976
+ if (options?.provenance !== void 0) collOpts.provenance = options.provenance;
16801
16977
  if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
16802
16978
  if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
16803
16979
  collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
@@ -18620,7 +18796,7 @@ var init_vault = __esm({
18620
18796
  if (this.activeElevation) {
18621
18797
  throw new AlreadyElevatedError(this.activeElevation.tier);
18622
18798
  }
18623
- if (this.keyring.role !== "owner" && this.keyring.role !== "admin") {
18799
+ if (this.keyring.role !== "owner" && this.keyring.role !== "admin" && this.keyring.role !== "custodian") {
18624
18800
  const suffix = `#${tier}`;
18625
18801
  let found = false;
18626
18802
  for (const k of this.keyring.deks.keys()) {
@@ -20822,997 +20998,6 @@ var init_executor3 = __esm({
20822
20998
  }
20823
20999
  });
20824
21000
 
20825
- // src/federation/schema-manifest.ts
20826
- function captureBlueprint(configure) {
20827
- const recorded = [];
20828
- const collectionStub = new Proxy(
20829
- {},
20830
- {
20831
- get: () => () => collectionStub
20832
- }
20833
- );
20834
- const proxy = new Proxy(
20835
- {},
20836
- {
20837
- get: (_t, prop) => {
20838
- if (prop === "collection") {
20839
- return (name, opts) => {
20840
- recorded.push({
20841
- name,
20842
- indexes: opts?.indexes ?? [],
20843
- persistJsonSchema: !!opts?.persistJsonSchema
20844
- });
20845
- return collectionStub;
20846
- };
20847
- }
20848
- return () => proxy;
20849
- }
20850
- }
20851
- );
20852
- configure(proxy);
20853
- const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
20854
- const indexes = {};
20855
- const persistJsonSchema = [];
20856
- for (const c of sorted) {
20857
- indexes[c.name] = c.indexes;
20858
- if (c.persistJsonSchema) persistJsonSchema.push(c.name);
20859
- }
20860
- return {
20861
- // `persistJsonSchema` is already name-sorted: it is populated while
20862
- // iterating `sorted` (collections in name order).
20863
- collections: sorted.map((c) => c.name),
20864
- indexes,
20865
- persistJsonSchema
20866
- };
20867
- }
20868
- function canonical(value) {
20869
- if (value === null || typeof value !== "object") return JSON.stringify(value);
20870
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
20871
- const obj = value;
20872
- const keys = Object.keys(obj).sort();
20873
- return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
20874
- }
20875
- async function fingerprintBlueprint(bp) {
20876
- return sha256Hex2(new TextEncoder().encode(canonical(bp)));
20877
- }
20878
- var init_schema_manifest = __esm({
20879
- "src/federation/schema-manifest.ts"() {
20880
- "use strict";
20881
- init_crypto();
20882
- }
20883
- });
20884
-
20885
- // src/federation/state-vault.ts
20886
- var state_vault_exports = {};
20887
- __export(state_vault_exports, {
20888
- STATE_VAULT_NAME: () => STATE_VAULT_NAME,
20889
- StateManagementVault: () => StateManagementVault
20890
- });
20891
- var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, StateManagementVault;
20892
- var init_state_vault = __esm({
20893
- "src/federation/state-vault.ts"() {
20894
- "use strict";
20895
- init_schema_manifest();
20896
- init_constants2();
20897
- init_ulid();
20898
- init_constants2();
20899
- REGISTRY = "vaultRegistry";
20900
- MANIFEST = "schemaManifest";
20901
- EVENTS = "deploymentEvents";
20902
- MIGRATION_STATUS = "migrationStatus";
20903
- StateManagementVault = class _StateManagementVault {
20904
- constructor(registry, schemaManifest, events, migrationStatus) {
20905
- this.registry = registry;
20906
- this.schemaManifest = schemaManifest;
20907
- this.#events = events;
20908
- this.#migrationStatus = migrationStatus;
20909
- }
20910
- registry;
20911
- schemaManifest;
20912
- /**
20913
- * The append-only deployment-events log is kept truly private so the raw
20914
- * mutable Collection is never surfaced — events may only be written via
20915
- * `appendEvent` and read via `queryEvents`. (`registry` and
20916
- * `schemaManifest` are deliberately public: consumers read and write them.)
20917
- */
20918
- #events;
20919
- /** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
20920
- #migrationStatus;
20921
- /** Idempotently open the reserved state vault and bind the control-plane collections. */
20922
- static async open(db) {
20923
- const vault = await db.openVault(STATE_VAULT_NAME);
20924
- return new _StateManagementVault(
20925
- vault.collection(REGISTRY),
20926
- vault.collection(MANIFEST),
20927
- vault.collection(EVENTS),
20928
- vault.collection(MIGRATION_STATUS)
20929
- );
20930
- }
20931
- /** Read one shard's migration status (or null). */
20932
- async getMigrationStatus(vaultId) {
20933
- return this.#migrationStatus.get(vaultId);
20934
- }
20935
- /** All migration-status rows (hydrates first). */
20936
- async listMigrationStatus() {
20937
- await this.#migrationStatus.list();
20938
- return this.#migrationStatus.query().toArray();
20939
- }
20940
- /** Upsert one shard's migration status (keyed by vaultId). */
20941
- async upsertMigrationStatus(row) {
20942
- await this.#migrationStatus.put(row.vaultId, row);
20943
- }
20944
- /** Read-only query over the append-only deployment-events log. */
20945
- queryEvents() {
20946
- return this.#events.query();
20947
- }
20948
- /**
20949
- * Append a deployment event with a fresh unique (ULID) id. This is the
20950
- * only write path to the events log; no update/delete is exposed.
20951
- * Callers should treat failures as non-fatal — this method does not
20952
- * swallow errors, so wrap the call site in try/catch where appropriate.
20953
- */
20954
- async appendEvent(event) {
20955
- const ts = event.ts ?? Date.now();
20956
- const id = generateULID();
20957
- await this.#events.put(id, { ...event, id, ts });
20958
- }
20959
- /**
20960
- * Ensure a manifest row exists for `(templateName, template.version)`.
20961
- * Safe to call repeatedly: the `fingerprint` is a deterministic hash of
20962
- * the template's declared shape (stable across calls), though each call
20963
- * refreshes `recordedAt`.
20964
- */
20965
- async recordManifest(templateName, template) {
20966
- const bp = captureBlueprint(template.configure);
20967
- const fingerprint = await fingerprintBlueprint(bp);
20968
- await this.schemaManifest.put(`${templateName}:${template.version}`, {
20969
- templateName,
20970
- version: template.version,
20971
- collections: bp.collections,
20972
- indexes: bp.indexes,
20973
- persistJsonSchema: bp.persistJsonSchema,
20974
- fingerprint,
20975
- recordedAt: Date.now()
20976
- });
20977
- return fingerprint;
20978
- }
20979
- /**
20980
- * True when `template`'s current declared shape does not match the recorded
20981
- * manifest for `(templateName, template.version)`. Because shards carry no
20982
- * schema state independent of their template, this catches "a template's
20983
- * shape changed without bumping `version`" — not independent per-shard drift.
20984
- * A missing manifest is treated as drift (nothing to verify against).
20985
- */
20986
- async detectDrift(templateName, template) {
20987
- const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
20988
- if (!row) return true;
20989
- const current = await fingerprintBlueprint(captureBlueprint(template.configure));
20990
- return current !== row.fingerprint;
20991
- }
20992
- };
20993
- }
20994
- });
20995
-
20996
- // src/federation/classify-skip.ts
20997
- function classifyShardSkip(err) {
20998
- return err instanceof NoAccessError ? "no-grant" : "error";
20999
- }
21000
- var init_classify_skip = __esm({
21001
- "src/federation/classify-skip.ts"() {
21002
- "use strict";
21003
- init_errors();
21004
- }
21005
- });
21006
-
21007
- // src/federation/cross-shard-join.ts
21008
- function coerceKey(value) {
21009
- if (value === null || value === void 0) return null;
21010
- if (typeof value === "string") return value;
21011
- if (typeof value === "number" || typeof value === "bigint") return String(value);
21012
- return null;
21013
- }
21014
- function warnOnceBroadcastMiss(field, as, key) {
21015
- const dedup = `${field}\u2192${as}:${key}`;
21016
- if (warnedBroadcastKeys.has(dedup)) return;
21017
- warnedBroadcastKeys.add(dedup);
21018
- console.warn(
21019
- `[noy-db] broadcastJoin: no "${as}" dimension row for ${field}="${key}". Attaching null. Use mode: 'cascade' to silence.`
21020
- );
21021
- }
21022
- async function applyBroadcastLegs(rows, legs) {
21023
- if (legs.length === 0) return [...rows];
21024
- const indexes = [];
21025
- for (const leg of legs) {
21026
- const map = /* @__PURE__ */ new Map();
21027
- for (const rec of await leg.from.list()) {
21028
- const k = coerceKey(readPath(rec, leg.on));
21029
- if (k !== null && !map.has(k)) map.set(k, rec);
21030
- }
21031
- indexes.push({ leg, map });
21032
- }
21033
- return rows.map((row) => {
21034
- const out = { ...row };
21035
- for (const { leg, map } of indexes) {
21036
- const key = coerceKey(readPath(row, leg.field));
21037
- const match = key === null ? null : map.get(key) ?? null;
21038
- if (match === null && leg.mode === "warn") {
21039
- warnOnceBroadcastMiss(leg.field, leg.as, key ?? "<null>");
21040
- }
21041
- out[leg.as] = match;
21042
- }
21043
- return out;
21044
- });
21045
- }
21046
- var warnedBroadcastKeys;
21047
- var init_cross_shard_join = __esm({
21048
- "src/federation/cross-shard-join.ts"() {
21049
- "use strict";
21050
- init_predicate();
21051
- warnedBroadcastKeys = /* @__PURE__ */ new Set();
21052
- }
21053
- });
21054
-
21055
- // src/federation/cross-vault-live.ts
21056
- var CrossVaultLive;
21057
- var init_cross_vault_live = __esm({
21058
- "src/federation/cross-vault-live.ts"() {
21059
- "use strict";
21060
- CrossVaultLive = class {
21061
- snapshot;
21062
- error = null;
21063
- ready;
21064
- subs = /* @__PURE__ */ new Set();
21065
- unsubChange;
21066
- opts;
21067
- stopped = false;
21068
- computing = false;
21069
- dirty = false;
21070
- scheduled = false;
21071
- timer = null;
21072
- resolveReady;
21073
- settledOnce = false;
21074
- constructor(opts) {
21075
- this.opts = opts;
21076
- this.snapshot = opts.initialSnapshot;
21077
- this.ready = new Promise((res) => {
21078
- this.resolveReady = res;
21079
- });
21080
- this.unsubChange = opts.subscribeToChanges((e) => {
21081
- if (this.stopped || !opts.isRelevant(e)) return;
21082
- this.schedule();
21083
- });
21084
- this.schedule();
21085
- }
21086
- subscribe(cb) {
21087
- if (this.stopped) return () => {
21088
- };
21089
- this.subs.add(cb);
21090
- return () => this.subs.delete(cb);
21091
- }
21092
- stop() {
21093
- if (this.stopped) return;
21094
- this.stopped = true;
21095
- this.unsubChange();
21096
- if (this.timer !== null) clearTimeout(this.timer);
21097
- this.subs.clear();
21098
- if (!this.settledOnce) this.resolveReady();
21099
- }
21100
- schedule() {
21101
- if (this.stopped) return;
21102
- if (this.computing) {
21103
- this.dirty = true;
21104
- return;
21105
- }
21106
- if (this.scheduled) return;
21107
- this.scheduled = true;
21108
- const run = () => {
21109
- this.scheduled = false;
21110
- void this.runCompute();
21111
- };
21112
- const ms = this.opts.debounceMs ?? 0;
21113
- if (ms > 0) this.timer = setTimeout(run, ms);
21114
- else queueMicrotask(run);
21115
- }
21116
- async runCompute() {
21117
- if (this.stopped) return;
21118
- this.computing = true;
21119
- this.dirty = false;
21120
- try {
21121
- const next = await this.opts.compute();
21122
- if (this.stopped) return;
21123
- this.snapshot = next;
21124
- this.error = null;
21125
- } catch (err) {
21126
- if (this.stopped) return;
21127
- this.error = err instanceof Error ? err : new Error(String(err));
21128
- } finally {
21129
- this.computing = false;
21130
- if (!this.stopped) {
21131
- if (!this.settledOnce) {
21132
- this.settledOnce = true;
21133
- this.resolveReady();
21134
- }
21135
- for (const cb of this.subs) cb();
21136
- if (this.dirty) this.schedule();
21137
- }
21138
- }
21139
- }
21140
- };
21141
- }
21142
- });
21143
-
21144
- // src/federation/aggregate-across.ts
21145
- var CrossVaultAggregation, CrossVaultGroupedAggregation;
21146
- var init_aggregate_across = __esm({
21147
- "src/federation/aggregate-across.ts"() {
21148
- "use strict";
21149
- init_aggregation();
21150
- init_groupby();
21151
- init_cross_vault_live();
21152
- CrossVaultAggregation = class {
21153
- constructor(src, spec, bind) {
21154
- this.src = src;
21155
- this.spec = spec;
21156
- this.bind = bind;
21157
- }
21158
- src;
21159
- spec;
21160
- bind;
21161
- async run(options = {}) {
21162
- const { records, skippedVaults } = await this.src.fanoutRecords(options);
21163
- return { result: reduceRecords(records, this.spec), skippedVaults };
21164
- }
21165
- live(options = {}) {
21166
- if (!this.bind) throw new Error("CrossVaultAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.aggregate()");
21167
- const spec = this.spec;
21168
- const src = this.src;
21169
- const core = new CrossVaultLive({
21170
- subscribeToChanges: this.bind.subscribeToChanges,
21171
- isRelevant: this.bind.isRelevant,
21172
- compute: async () => {
21173
- const { records, skippedVaults } = await src.fanoutRecords(options);
21174
- return { value: reduceRecords(records, spec), skipped: skippedVaults };
21175
- },
21176
- initialSnapshot: { value: void 0, skipped: [] },
21177
- ...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
21178
- });
21179
- return {
21180
- get value() {
21181
- return core.snapshot.value;
21182
- },
21183
- get skippedVaults() {
21184
- return core.snapshot.skipped;
21185
- },
21186
- get error() {
21187
- return core.error;
21188
- },
21189
- ready: core.ready,
21190
- subscribe: (cb) => core.subscribe(cb),
21191
- stop: () => core.stop()
21192
- };
21193
- }
21194
- };
21195
- CrossVaultGroupedAggregation = class {
21196
- constructor(src, field, spec, bind) {
21197
- this.src = src;
21198
- this.field = field;
21199
- this.spec = spec;
21200
- this.bind = bind;
21201
- }
21202
- src;
21203
- field;
21204
- spec;
21205
- bind;
21206
- async run(options = {}) {
21207
- const { records, skippedVaults } = await this.src.fanoutRecords(options);
21208
- return {
21209
- results: groupAndReduce(records, this.field, this.spec),
21210
- skippedVaults
21211
- };
21212
- }
21213
- live(options = {}) {
21214
- if (!this.bind) throw new Error("CrossVaultGroupedAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.groupBy().aggregate()");
21215
- const field = this.field;
21216
- const spec = this.spec;
21217
- const src = this.src;
21218
- const core = new CrossVaultLive({
21219
- subscribeToChanges: this.bind.subscribeToChanges,
21220
- isRelevant: this.bind.isRelevant,
21221
- compute: async () => {
21222
- const { records, skippedVaults } = await src.fanoutRecords(options);
21223
- return {
21224
- records: groupAndReduce(records, field, spec),
21225
- skipped: skippedVaults
21226
- };
21227
- },
21228
- initialSnapshot: { records: [], skipped: [] },
21229
- ...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
21230
- });
21231
- return {
21232
- get value() {
21233
- return core.snapshot.records;
21234
- },
21235
- get skippedVaults() {
21236
- return core.snapshot.skipped;
21237
- },
21238
- get error() {
21239
- return core.error;
21240
- },
21241
- ready: core.ready,
21242
- subscribe: (cb) => core.subscribe(cb),
21243
- stop: () => core.stop()
21244
- };
21245
- }
21246
- };
21247
- }
21248
- });
21249
-
21250
- // src/federation/vault-group.ts
21251
- var vault_group_exports = {};
21252
- __export(vault_group_exports, {
21253
- ShardedCollection: () => ShardedCollection,
21254
- ShardedGroupedQuery: () => ShardedGroupedQuery,
21255
- ShardedQuery: () => ShardedQuery,
21256
- VaultGroup: () => VaultGroup
21257
- });
21258
- function assertSafePartitionKey(partitionKey) {
21259
- if (partitionKey.length === 0) {
21260
- throw new ValidationError("partitionKey must be a non-empty string");
21261
- }
21262
- if (partitionKey === STATE_VAULT_NAME) {
21263
- throw new ReservedVaultNameError(partitionKey);
21264
- }
21265
- if (!SAFE_PARTITION_KEY.test(partitionKey)) {
21266
- throw new ValidationError(
21267
- `partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
21268
- );
21269
- }
21270
- if (partitionKey.includes(SHARD_SEPARATOR)) {
21271
- throw new ValidationError(
21272
- `partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
21273
- );
21274
- }
21275
- }
21276
- var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
21277
- var init_vault_group = __esm({
21278
- "src/federation/vault-group.ts"() {
21279
- "use strict";
21280
- init_state_vault();
21281
- init_errors();
21282
- init_constants2();
21283
- init_classify_skip();
21284
- init_cross_shard_join();
21285
- init_cross_vault_live();
21286
- init_aggregate_across();
21287
- SHARD_SEPARATOR = "--";
21288
- SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
21289
- VaultGroup = class {
21290
- constructor(db, name, registry, sharding, template, migrateOnOpen = false) {
21291
- this.db = db;
21292
- this.name = name;
21293
- this.registry = registry;
21294
- this.sharding = sharding;
21295
- this.template = template;
21296
- this.migrateOnOpen = migrateOnOpen;
21297
- if (name.includes(SHARD_SEPARATOR)) {
21298
- throw new ValidationError(
21299
- `VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
21300
- );
21301
- }
21302
- }
21303
- db;
21304
- name;
21305
- registry;
21306
- sharding;
21307
- template;
21308
- migrateOnOpen;
21309
- /** @internal — set when the group is managed (no explicit registry). */
21310
- stateVault;
21311
- /** @internal */
21312
- _attachStateVault(sv) {
21313
- this.stateVault = sv;
21314
- }
21315
- /** Deterministic vault name for a partition key, namespaced by the group. */
21316
- shardVaultId(partitionKey) {
21317
- assertSafePartitionKey(partitionKey);
21318
- return `${this.name}${SHARD_SEPARATOR}${partitionKey}`;
21319
- }
21320
- /**
21321
- * @internal — group-qualified registry record key (avoids cross-group key
21322
- * collisions). Identical to the shard vault id by design — the registry row
21323
- * for a shard is keyed by that shard's vault id — so it delegates to
21324
- * `shardVaultId`, reusing its partition-key validation.
21325
- */
21326
- registryId(partitionKey) {
21327
- return this.shardVaultId(partitionKey);
21328
- }
21329
- /**
21330
- * Registry rows for THIS group (hydrates the registry collection first).
21331
- * The registry may be shared across groups (the auto-wired StateManagement
21332
- * vault holds one `vaultRegistry` for the whole instance), so rows are
21333
- * filtered by `group` — without this, a group's fan-out reads would leak
21334
- * across into other groups' shards. Mirrors the `${group}--` scoping that
21335
- * `liveBinding().isRelevant` already applies to the reactive path.
21336
- */
21337
- async allRows() {
21338
- await this.registry.list();
21339
- const rows = this.registry.query().toArray();
21340
- return rows.filter((r) => r.group === this.name);
21341
- }
21342
- /**
21343
- * Open an existing shard and apply the template. When `migrateOnOpen` is set
21344
- * (#271) and the shard's registry version is behind the template, its cutover
21345
- * runs inline first — so a behind shard never surfaces a stale handle.
21346
- */
21347
- async openShard(partitionKey) {
21348
- if (this.migrateOnOpen) {
21349
- const row = await this.registry.get(this.registryId(partitionKey));
21350
- if (row && row.schemaVersion < this.template.version) {
21351
- await this.migrateShard(partitionKey);
21352
- }
21353
- }
21354
- return this._openShardRaw(partitionKey);
21355
- }
21356
- /** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
21357
- async _openShardRaw(partitionKey) {
21358
- const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
21359
- this.template.configure(vault);
21360
- return vault;
21361
- }
21362
- /**
21363
- * Idempotently provision a shard for `partitionKey`. Returns the
21364
- * configured vault handle.
21365
- *
21366
- * - row + vault present → no-op, return handle
21367
- * - row present, vault gone → ShardProvisioningError
21368
- * - row absent (vault present or not) → open-or-create, configure, write row
21369
- *
21370
- * When `region` is given (the routing `put` passes `sharding.regionOf(record)`),
21371
- * the candidate backend's `capabilities.region` must match or this throws
21372
- * `DataResidencyError` BEFORE provisioning (#271 data-residency guard).
21373
- */
21374
- async createShard(partitionKey, region) {
21375
- const vaultId = this.shardVaultId(partitionKey);
21376
- const row = await this.registry.get(this.registryId(partitionKey));
21377
- const provisioned = await this.db._shardVaultProvisioned(vaultId);
21378
- if (row && !provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
21379
- if (row && provisioned) return this.openShard(partitionKey);
21380
- if (region !== void 0) {
21381
- const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
21382
- if (backendRegion !== region) throw new DataResidencyError(vaultId, region, backendRegion);
21383
- }
21384
- const vault = await this.db.openVault(vaultId);
21385
- this.template.configure(vault);
21386
- await this.registry.put(this.registryId(partitionKey), {
21387
- vaultId,
21388
- partitionKey,
21389
- templateName: this.sharding.vaultTemplate,
21390
- schemaVersion: this.template.version,
21391
- createdAt: Date.now(),
21392
- group: this.name
21393
- });
21394
- if (this.stateVault) {
21395
- try {
21396
- await this.stateVault.appendEvent({
21397
- type: "shard-created",
21398
- group: this.name,
21399
- vaultId,
21400
- templateName: this.sharding.vaultTemplate,
21401
- version: this.template.version
21402
- });
21403
- } catch {
21404
- }
21405
- }
21406
- return vault;
21407
- }
21408
- /**
21409
- * Drill down to a single shard's full Collection API. Throws if the shard is unknown.
21410
- * Also throws ShardProvisioningError if the registry row exists but the vault has been deleted
21411
- * (registry/store divergence).
21412
- */
21413
- async shard(partitionKey) {
21414
- const vaultId = this.shardVaultId(partitionKey);
21415
- const row = await this.registry.get(this.registryId(partitionKey));
21416
- if (!row) throw new UnknownShardError(partitionKey, this.name);
21417
- const provisioned = await this.db._shardVaultProvisioned(vaultId);
21418
- if (!provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
21419
- return this.openShard(partitionKey);
21420
- }
21421
- /** A sharded view over one logical collection across all shards. */
21422
- collection(collectionName) {
21423
- return new ShardedCollection(this, collectionName);
21424
- }
21425
- /** @internal — eligible (openable-candidate) rows + drift/divergence skips. */
21426
- async resolveEligible(options = {}) {
21427
- const rows = await this.allRows();
21428
- const skipped = [];
21429
- const versionOk = [];
21430
- for (const row of rows) {
21431
- if (options.minVersion !== void 0 && row.schemaVersion < options.minVersion) {
21432
- skipped.push({ vaultId: row.vaultId, reason: "schema-drift" });
21433
- } else versionOk.push(row);
21434
- }
21435
- const provisioned = await Promise.all(versionOk.map((r) => this.db._shardVaultProvisioned(r.vaultId)));
21436
- const eligible = [];
21437
- versionOk.forEach((row, i) => {
21438
- if (provisioned[i]) eligible.push(row);
21439
- else skipped.push({ vaultId: row.vaultId, reason: "error", error: new ShardProvisioningError(row.vaultId, row.partitionKey) });
21440
- });
21441
- return { eligible, skipped };
21442
- }
21443
- /** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
21444
- crossVaultDerivations = [];
21445
- /**
21446
- * Register a push-model cross-vault derivation — the Insight Vault pattern
21447
- * (#271, Layer 4). Drive it with {@link refreshInsights}.
21448
- *
21449
- * For each shard, `derive(records, ctx)` runs on that shard's `source`
21450
- * records and its return value is written into the analytics
21451
- * (`target.vault` / `target.collection`) vault, keyed by partition key —
21452
- * one summary row per shard. The derivation runs in-process under THIS
21453
- * group's `Noydb` (which already holds both the shard and Insight Vault
21454
- * keyrings); the shard's decrypted records are reduced to a summary that is
21455
- * re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
21456
- * crosses a DEK boundary.
21457
- *
21458
- * **Zero-knowledge note:** the Insight Vault backend sees aggregated
21459
- * structure (totals, counts, timestamps) drawn from many shards — a weaker
21460
- * ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
21461
- * scalars (no embeddings / no raw records).
21462
- *
21463
- * v1 is explicit-refresh (no write-path push); call `refreshInsights()`
21464
- * after a batch of writes, or on a schedule.
21465
- *
21466
- * The `target.vault` must NOT be the group itself or one of its shards —
21467
- * a summary writing back into client-shard data would breach the Insight
21468
- * Vault's separate-DEK-boundary contract. Such a target throws a
21469
- * `ValidationError` at registration (#271 Insight-write isolation).
21470
- */
21471
- withCrossVaultDerivation(spec) {
21472
- const target = spec.target.vault;
21473
- if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
21474
- throw new ValidationError(
21475
- `withCrossVaultDerivation: target.vault "${target}" is the "${this.name}" group itself or one of its shards \u2014 an Insight summary must target a SEPARATE analytics vault, never write back into client-shard data (it would breach the per-shard DEK boundary). Use a distinct vault name.`
21476
- );
21477
- }
21478
- this.crossVaultDerivations.push(spec);
21479
- }
21480
- /**
21481
- * Run every registered {@link withCrossVaultDerivation}: read each eligible
21482
- * shard's source records, derive a per-shard summary, and write it into the
21483
- * Insight Vault keyed by partition key. Shards behind `minVersion`,
21484
- * unprovisioned, or whose read errors are reported in `skippedVaults` and
21485
- * are not written (a stale summary is never left behind for a failed shard).
21486
- */
21487
- async refreshInsights(options = {}) {
21488
- if (this.crossVaultDerivations.length === 0) return { written: 0, skippedVaults: [] };
21489
- const { eligible, skipped } = await this.resolveEligible(
21490
- options.minVersion !== void 0 ? { minVersion: options.minVersion } : {}
21491
- );
21492
- let written = 0;
21493
- for (const spec of this.crossVaultDerivations) {
21494
- const results = await this.db.queryAcross(
21495
- eligible.map((r) => r.vaultId),
21496
- async (vault) => {
21497
- this.template.configure(vault);
21498
- return vault.collection(spec.source).list();
21499
- },
21500
- { create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
21501
- );
21502
- const insight = await this.db.openVault(spec.target.vault);
21503
- const out = insight.collection(spec.target.collection);
21504
- for (let i = 0; i < eligible.length; i++) {
21505
- const row = eligible[i];
21506
- const res = results[i];
21507
- if (!res || res.result === void 0) {
21508
- skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
21509
- continue;
21510
- }
21511
- const ctx = {
21512
- vaultId: row.vaultId,
21513
- partitionKey: row.partitionKey,
21514
- schemaVersion: row.schemaVersion
21515
- };
21516
- const summary = spec.derive(res.result, ctx);
21517
- await out.put(row.partitionKey, summary);
21518
- written++;
21519
- }
21520
- }
21521
- return { written, skippedVaults: skipped };
21522
- }
21523
- /** @internal — the control-plane vault for migration status; lazily opened. */
21524
- async ensureStateVault() {
21525
- if (!this.stateVault) this.stateVault = await StateManagementVault.open(this.db);
21526
- return this.stateVault;
21527
- }
21528
- /**
21529
- * Migrate ONE shard to the template's current version (#271 fleet runner,
21530
- * per-shard step). Opens the shard (applying the template, which arms the
21531
- * M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
21532
- * (the per-vault drain-barrier-transform protocol), then advances the
21533
- * registry row's `schemaVersion` and records `migration-status`. A shard
21534
- * already at the template version is a no-op (`status: 'done'`, migrated 0).
21535
- * Never throws on a cutover failure — it records `status: 'failed'` and
21536
- * returns the row, so a fleet run continues past a bad shard.
21537
- */
21538
- async migrateShard(partitionKey) {
21539
- const vaultId = this.shardVaultId(partitionKey);
21540
- const row = await this.registry.get(this.registryId(partitionKey));
21541
- if (!row) throw new UnknownShardError(partitionKey, this.name);
21542
- const target = this.template.version;
21543
- const sv = await this.ensureStateVault();
21544
- const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
21545
- if (row.schemaVersion >= target) {
21546
- const done = { ...base, status: "done", migrated: 0, finishedAt: Date.now() };
21547
- await sv.upsertMigrationStatus(done);
21548
- return done;
21549
- }
21550
- await sv.upsertMigrationStatus({ ...base, status: "running", startedAt: Date.now() });
21551
- try {
21552
- await sv.appendEvent({ type: "migration-started", group: this.name, vaultId, version: target });
21553
- } catch {
21554
- }
21555
- try {
21556
- const vault = await this._openShardRaw(partitionKey);
21557
- await vault._drainPendingSchemaWrites();
21558
- const { migrated } = await vault.runSchemaCutover();
21559
- await this.registry.put(this.registryId(partitionKey), { ...row, schemaVersion: target });
21560
- const done = { ...base, currentVersion: target, status: "done", migrated, finishedAt: Date.now() };
21561
- await sv.upsertMigrationStatus(done);
21562
- try {
21563
- await sv.appendEvent({ type: "migration-completed", group: this.name, vaultId, version: target });
21564
- } catch {
21565
- }
21566
- return done;
21567
- } catch (err) {
21568
- const error = err instanceof Error ? err.message : String(err);
21569
- const failed = { ...base, status: "failed", error, finishedAt: Date.now() };
21570
- await sv.upsertMigrationStatus(failed);
21571
- try {
21572
- await sv.appendEvent({ type: "migration-failed", group: this.name, vaultId, version: target, detail: error });
21573
- } catch {
21574
- }
21575
- return failed;
21576
- }
21577
- }
21578
- /**
21579
- * Active batch runner (#271): migrate every shard behind the template version
21580
- * to it, in controlled batches. **Resumable + crash-safe** — shards already at
21581
- * the target are skipped (the registry version is the source of truth), so a
21582
- * re-run after a crash only picks up the unfinished + previously-failed shards.
21583
- *
21584
- * - `cohort` — restrict to these partition keys (the staged / canary rollout:
21585
- * migrate a small cohort, verify the Insight Vault, then run the rest).
21586
- * - `batchSize` — max shards migrated concurrently per batch (back-pressure).
21587
- * Default 4. Batches run sequentially; shards within a batch run in parallel.
21588
- */
21589
- async migrateFleet(options = {}) {
21590
- const target = this.template.version;
21591
- const rows = await this.allRows();
21592
- const cohort = options.cohort;
21593
- const todo = rows.filter(
21594
- (r) => r.schemaVersion < target && (cohort === void 0 || cohort.includes(r.partitionKey))
21595
- );
21596
- const batchSize = Math.max(1, options.batchSize ?? 4);
21597
- const migrated = [];
21598
- const failed = [];
21599
- for (let i = 0; i < todo.length; i += batchSize) {
21600
- const batch = todo.slice(i, i + batchSize);
21601
- const settled = await Promise.all(batch.map((r) => this.migrateShard(r.partitionKey)));
21602
- for (const res of settled) {
21603
- if (res.status === "done") migrated.push(res.vaultId);
21604
- else failed.push({ vaultId: res.vaultId, error: res.error ?? "unknown" });
21605
- }
21606
- }
21607
- return { target, migrated, failed };
21608
- }
21609
- };
21610
- ShardedCollection = class {
21611
- constructor(group, collectionName) {
21612
- this.group = group;
21613
- this.collectionName = collectionName;
21614
- }
21615
- group;
21616
- collectionName;
21617
- /** Route a write to the shard owning `keyOf(record)`. */
21618
- async put(id, record) {
21619
- const key = this.group.sharding.keyOf(record);
21620
- const row = await this.group.registry.get(this.group.registryId(key));
21621
- let vault;
21622
- if (!row) {
21623
- if (this.group.sharding.autoCreate === false) {
21624
- throw new UnknownShardError(key, this.group.name);
21625
- }
21626
- vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
21627
- } else {
21628
- vault = await this.group.openShard(key);
21629
- }
21630
- await vault.collection(this.collectionName).put(id, record);
21631
- }
21632
- /** Begin a cross-shard fan-out query. */
21633
- query() {
21634
- return new ShardedQuery(this.group, this.collectionName, []);
21635
- }
21636
- };
21637
- ShardedQuery = class _ShardedQuery {
21638
- constructor(group, collectionName, clauses, coPartitionedLegs = [], broadcastLegs = []) {
21639
- this.group = group;
21640
- this.collectionName = collectionName;
21641
- this.clauses = clauses;
21642
- this.coPartitionedLegs = coPartitionedLegs;
21643
- this.broadcastLegs = broadcastLegs;
21644
- }
21645
- group;
21646
- collectionName;
21647
- clauses;
21648
- coPartitionedLegs;
21649
- broadcastLegs;
21650
- where(field, op, value) {
21651
- return new _ShardedQuery(
21652
- this.group,
21653
- this.collectionName,
21654
- [...this.clauses, { field, op, value }],
21655
- this.coPartitionedLegs,
21656
- this.broadcastLegs
21657
- );
21658
- }
21659
- /** Co-partitioned join: each shard joins its own same-vault right collection (resolved via ref()), then union. */
21660
- crossShardJoin(field, opts) {
21661
- const leg = { field, as: opts.as, maxRows: opts.maxRows, strategy: opts.strategy };
21662
- return new _ShardedQuery(
21663
- this.group,
21664
- this.collectionName,
21665
- this.clauses,
21666
- [...this.coPartitionedLegs, leg],
21667
- this.broadcastLegs
21668
- );
21669
- }
21670
- /** Broadcast dimension join: enrich every merged row from a single shared collection. */
21671
- broadcastJoin(field, opts) {
21672
- const leg = {
21673
- field,
21674
- as: opts.as,
21675
- from: opts.from,
21676
- on: opts.on ?? "id",
21677
- mode: opts.mode ?? "warn"
21678
- };
21679
- return new _ShardedQuery(
21680
- this.group,
21681
- this.collectionName,
21682
- this.clauses,
21683
- this.coPartitionedLegs,
21684
- [...this.broadcastLegs, leg]
21685
- );
21686
- }
21687
- /** @internal — fan out the where-filtered records across eligible shards. */
21688
- async fanoutRecords(options = {}) {
21689
- const { eligible, skipped } = await this.group.resolveEligible(options);
21690
- const probeRow = eligible[0];
21691
- if (this.coPartitionedLegs.length > 0 && probeRow) {
21692
- const probe = await this.group.openShard(probeRow.partitionKey);
21693
- this.group.template.configure(probe);
21694
- for (const leg of this.coPartitionedLegs) {
21695
- if (!probe.resolveRef(this.collectionName, leg.field)) {
21696
- throw new CrossShardJoinError(
21697
- `crossShardJoin("${leg.field}"): no ref() declared for "${leg.field}" on collection "${this.collectionName}" in template "${this.group.sharding.vaultTemplate}". Add refs: { ${leg.field}: ref('<target>') } to the template's collection options.`
21698
- );
21699
- }
21700
- }
21701
- }
21702
- const across = await this.group.db.queryAcross(
21703
- eligible.map((r) => r.vaultId),
21704
- async (vault) => {
21705
- this.group.template.configure(vault);
21706
- const coll = vault.collection(this.collectionName);
21707
- await coll.list();
21708
- for (const leg of this.coPartitionedLegs) {
21709
- const desc = vault.resolveRef(this.collectionName, leg.field);
21710
- if (desc) await vault.collection(desc.target).list();
21711
- }
21712
- let q = coll.query();
21713
- for (const c of this.clauses) q = q.where(c.field, c.op, c.value);
21714
- for (const leg of this.coPartitionedLegs) {
21715
- q = q.join(leg.field, {
21716
- as: leg.as,
21717
- ...leg.maxRows !== void 0 ? { maxRows: leg.maxRows } : {},
21718
- ...leg.strategy ? { strategy: leg.strategy } : {}
21719
- });
21720
- }
21721
- return q.toArray();
21722
- },
21723
- { concurrency: options.concurrency ?? 1, create: false }
21724
- );
21725
- const results = [];
21726
- for (const r of across) {
21727
- if (r.error) skipped.push({ vaultId: r.vault, reason: classifyShardSkip(r.error), error: r.error });
21728
- else for (const item of r.result) results.push(item);
21729
- }
21730
- return { records: results, skippedVaults: skipped };
21731
- }
21732
- /** Fan out across eligible shards, merge, then apply any broadcast dimension legs. */
21733
- async toArray(options = {}) {
21734
- const { records, skippedVaults } = await this.fanoutRecords(options);
21735
- const results = await applyBroadcastLegs(records, this.broadcastLegs);
21736
- return { results, skippedVaults };
21737
- }
21738
- /** @internal — build the change-subscription + relevance binding for this query's group+collection. */
21739
- liveBinding() {
21740
- const group = this.group;
21741
- const collectionName = this.collectionName;
21742
- return {
21743
- subscribeToChanges: (h) => {
21744
- group.db.on("change", h);
21745
- return () => group.db.off("change", h);
21746
- },
21747
- isRelevant: (e) => e.collection === collectionName && e.vault.startsWith(`${group.name}--`)
21748
- };
21749
- }
21750
- /** @internal — joined queries don't support reactive/aggregate surfaces in v1. */
21751
- assertNoJoinLegs(surface) {
21752
- if (this.coPartitionedLegs.length || this.broadcastLegs.length) {
21753
- throw new CrossShardJoinError(
21754
- `${surface}() is not supported on a ShardedQuery with crossShardJoin/broadcastJoin legs in v1. Use toArray() for joined cross-shard queries.`
21755
- );
21756
- }
21757
- }
21758
- /** Returns a reactive cross-shard live query — a facade over CrossVaultLive. */
21759
- live(options = {}) {
21760
- this.assertNoJoinLegs("live");
21761
- const bind = this.liveBinding();
21762
- const core = new CrossVaultLive({
21763
- ...bind,
21764
- compute: async () => {
21765
- const { records, skippedVaults } = await this.fanoutRecords(options);
21766
- return { records, skipped: skippedVaults };
21767
- },
21768
- initialSnapshot: { records: [], skipped: [] },
21769
- ...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
21770
- });
21771
- return {
21772
- get value() {
21773
- return core.snapshot.records;
21774
- },
21775
- get skippedVaults() {
21776
- return core.snapshot.skipped;
21777
- },
21778
- get error() {
21779
- return core.error;
21780
- },
21781
- ready: core.ready,
21782
- subscribe: (cb) => core.subscribe(cb),
21783
- stop: () => core.stop()
21784
- };
21785
- }
21786
- /** One-shot distributed aggregate — central reduce over all shard records. */
21787
- aggregate(spec) {
21788
- this.assertNoJoinLegs("aggregate");
21789
- return new CrossVaultAggregation(this, spec, this.liveBinding());
21790
- }
21791
- /** Begin a grouped cross-shard aggregate. */
21792
- groupBy(field) {
21793
- this.assertNoJoinLegs("groupBy");
21794
- return new ShardedGroupedQuery(this, field);
21795
- }
21796
- };
21797
- ShardedGroupedQuery = class {
21798
- constructor(query, field) {
21799
- this.query = query;
21800
- this.field = field;
21801
- }
21802
- query;
21803
- field;
21804
- aggregate(spec) {
21805
- return new CrossVaultGroupedAggregation(
21806
- { fanoutRecords: (o) => this.query.fanoutRecords(o) },
21807
- this.field,
21808
- spec,
21809
- this.query.liveBinding()
21810
- );
21811
- }
21812
- };
21813
- }
21814
- });
21815
-
21816
21001
  // src/noydb.ts
21817
21002
  var noydb_exports = {};
21818
21003
  __export(noydb_exports, {
@@ -21873,7 +21058,6 @@ var init_noydb = __esm({
21873
21058
  "src/noydb.ts"() {
21874
21059
  "use strict";
21875
21060
  init_errors();
21876
- init_constants2();
21877
21061
  init_storage3();
21878
21062
  init_rotate_recover();
21879
21063
  init_peer_recover();
@@ -21907,6 +21091,12 @@ var init_noydb = __esm({
21907
21091
  client: 1,
21908
21092
  viewer: 2,
21909
21093
  operator: 3,
21094
+ // FR-6: custodian is operationally admin-rank (rw + access on every
21095
+ // collection) — it ranks alongside admin for "how much can this
21096
+ // principal see/operate." It is NOT above admin, and explicitly below
21097
+ // owner: a custodian can never grant/revoke/rotate/sever (those are
21098
+ // owner meta-capabilities), so it must not outrank or equal the owner.
21099
+ custodian: 4,
21910
21100
  admin: 4,
21911
21101
  owner: 5
21912
21102
  };
@@ -21955,7 +21145,6 @@ var init_noydb = __esm({
21955
21145
  writeRelay;
21956
21146
  /** Per-vault policy enforcers. */
21957
21147
  policyEnforcers = /* @__PURE__ */ new Map();
21958
- vaultTemplates = /* @__PURE__ */ new Map();
21959
21148
  txStrategy;
21960
21149
  forgetStrategy;
21961
21150
  sessionStrategy;
@@ -22405,6 +21594,37 @@ var init_noydb = __esm({
22405
21594
  const keyring = await this.getKeyringInternal(vault);
22406
21595
  await revoke(this.options.store, vault, keyring, options);
22407
21596
  }
21597
+ /**
21598
+ * Grant the FR-6 `custodian` role to a user (owner-only custody API).
21599
+ *
21600
+ * A custodian operates every collection (rw + access) but is provably
21601
+ * unable to grant / revoke / rotate / extract-and-sever. Only the Deed
21602
+ * owner may mint one. Defended in depth: the `grant-custodian` gate
21603
+ * (fail-closed) AND an explicit `keyring.role !== 'owner'` check — the
21604
+ * gate enforces host policy, the role check enforces the cryptographic
21605
+ * owner-only invariant even if a host mis-configures the gate.
21606
+ */
21607
+ async grantCustodian(vault, options, factors) {
21608
+ this.checkPolicyOperation(vault, "grant");
21609
+ await this.checkGate(vault, "grant-custodian", factors);
21610
+ const keyring = await this.getKeyringInternal(vault);
21611
+ if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can grant a custodian");
21612
+ await grant(this.options.store, vault, keyring, { ...options, role: "custodian" });
21613
+ }
21614
+ /**
21615
+ * Revoke a custodian (owner-only custody API).
21616
+ *
21617
+ * Mirrors {@link revoke} but pins the caller to the Deed owner: defended
21618
+ * in depth by the `revoke-user` gate AND an explicit `keyring.role !==
21619
+ * 'owner'` check, so an admin cannot unwind a custodianship.
21620
+ */
21621
+ async revokeCustodian(vault, options, factors) {
21622
+ this.checkPolicyOperation(vault, "revoke");
21623
+ await this.checkGate(vault, "revoke-user", factors);
21624
+ const keyring = await this.getKeyringInternal(vault);
21625
+ if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can revoke a custodian");
21626
+ await revoke(this.options.store, vault, keyring, options);
21627
+ }
22408
21628
  /**
22409
21629
  * Mutate post-grant identity fields on an existing keyring — `role`,
22410
21630
  * `displayName`, and/or `permissions`. Pure plaintext-header rewrite:
@@ -22674,52 +21894,24 @@ var init_noydb = __esm({
22674
21894
  return results;
22675
21895
  }
22676
21896
  /**
22677
- * Register a shard schema blueprint. `createShard` / `openVaultGroup`
22678
- * stamp shards from the named template. See the MVF design spec.
21897
+ * @internal True once `close()` has been called. Read by
21898
+ * `@klum-db/lobby`'s Lobby entry points (which can't see the private
21899
+ * `closed` field).
22679
21900
  */
22680
- withVaultTemplate(name, template) {
22681
- this.vaultTemplates.set(name, template);
21901
+ get isClosed() {
21902
+ return this.closed;
22682
21903
  }
22683
- /**
22684
- * Open a VaultGroup — transparent routing over per-partition shard
22685
- * vaults, with shard discovery backed by the supplied `vault-registry`
22686
- * collection.
22687
- */
22688
- async openVaultGroup(name, opts) {
22689
- if (this.closed) throw new ValidationError("Instance is closed");
22690
- if (name === STATE_VAULT_NAME) throw new ReservedVaultNameError(name);
22691
- const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
22692
- if (!template) throw new VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
22693
- const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
22694
- const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
22695
- const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
22696
- const registry = opts.registry ?? stateVault.registry;
22697
- const group = new VaultGroup2(this, name, registry, opts.sharding, template, opts.migrateOnOpen ?? false);
22698
- if (stateVault) {
22699
- group._attachStateVault(stateVault);
22700
- await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
22701
- try {
22702
- await stateVault.appendEvent({
22703
- type: "manifest-recorded",
22704
- group: name,
22705
- templateName: opts.sharding.vaultTemplate,
22706
- version: template.version
22707
- });
22708
- await stateVault.appendEvent({ type: "group-opened", group: name });
22709
- } catch {
22710
- }
22711
- }
22712
- return group;
21904
+ /** @deprecated Federation moved to @klum-db/lobby. Use `createLobby(db).withVaultTemplate(...)`. */
21905
+ withVaultTemplate() {
21906
+ throw new FederationMovedError("withVaultTemplate");
22713
21907
  }
22714
- /**
22715
- * Open the reserved StateManagement control-plane vault (registry +
22716
- * schema-manifest + deployment-events). Lazy-loaded so the federation
22717
- * chunk stays out of the core graph until used.
22718
- */
21908
+ /** @deprecated Federation moved to @klum-db/lobby. Use `createLobby(db).openVaultGroup(...)`. */
21909
+ async openVaultGroup() {
21910
+ throw new FederationMovedError("openVaultGroup");
21911
+ }
21912
+ /** @deprecated Federation moved to @klum-db/lobby. Use `createLobby(db).openStateManagementVault()`. */
22719
21913
  async openStateManagementVault() {
22720
- if (this.closed) throw new ValidationError("Instance is closed");
22721
- const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
22722
- return StateManagementVault2.open(this);
21914
+ throw new FederationMovedError("openStateManagementVault");
22723
21915
  }
22724
21916
  /**
22725
21917
  * @internal — true when an encrypted shard vault is provisioned
@@ -24211,21 +23403,30 @@ __export(bundle_exports, {
24211
23403
  NOYDB_BUNDLE_FORMAT_VERSION: () => NOYDB_BUNDLE_FORMAT_VERSION,
24212
23404
  NOYDB_BUNDLE_MAGIC: () => NOYDB_BUNDLE_MAGIC,
24213
23405
  NOYDB_BUNDLE_PREFIX_BYTES: () => NOYDB_BUNDLE_PREFIX_BYTES,
23406
+ NOYDB_MULTI_BUNDLE_MAGIC: () => NOYDB_MULTI_BUNDLE_MAGIC,
23407
+ NOYDB_MULTI_BUNDLE_PREFIX_BYTES: () => NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
23408
+ NOYDB_MULTI_BUNDLE_VERSION: () => NOYDB_MULTI_BUNDLE_VERSION,
24214
23409
  PartitionExtractionError: () => PartitionExtractionError,
24215
23410
  TransferSealError: () => TransferSealError,
24216
23411
  adoptPartition: () => adoptPartition,
24217
23412
  createOwnerOnAdoptedPartition: () => createOwnerOnAdoptedPartition,
23413
+ decodeMultiBundle: () => decodeMultiBundle,
23414
+ decryptExtractedPartition: () => decryptExtractedPartition,
24218
23415
  describeExtraction: () => describeExtraction,
24219
23416
  encodeBundleHeader: () => encodeBundleHeader,
23417
+ encodeMultiBundle: () => encodeMultiBundle,
24220
23418
  extractPartition: () => extractPartition,
24221
23419
  generateULID: () => generateULID,
24222
23420
  isULID: () => isULID,
23421
+ readMultiVaultBundleCompartment: () => readMultiVaultBundleCompartment,
24223
23422
  readNoydbBundle: () => readNoydbBundle,
24224
23423
  readNoydbBundleHeader: () => readNoydbBundleHeader,
23424
+ readNoydbBundleManifest: () => readNoydbBundleManifest,
24225
23425
  resetBrotliSupportCache: () => resetBrotliSupportCache,
24226
23426
  unsealDeks: () => unsealDeks,
24227
23427
  validateBundleHeader: () => validateBundleHeader,
24228
23428
  walkClosure: () => walkClosure,
23429
+ writeMultiVaultBundle: () => writeMultiVaultBundle,
24229
23430
  writeNoydbBundle: () => writeNoydbBundle
24230
23431
  });
24231
23432
  module.exports = __toCommonJS(bundle_exports);
@@ -24233,6 +23434,160 @@ init_bundle();
24233
23434
  init_format();
24234
23435
  init_ulid();
24235
23436
 
23437
+ // src/bundle/multi-bundle.ts
23438
+ init_crypto();
23439
+ init_ulid();
23440
+ init_format();
23441
+ init_bundle();
23442
+ var NOYDB_MULTI_BUNDLE_MAGIC = new Uint8Array([78, 68, 66, 77]);
23443
+ var NOYDB_MULTI_BUNDLE_PREFIX_BYTES = 10;
23444
+ var NOYDB_MULTI_BUNDLE_VERSION = 1;
23445
+ function encodeMultiBundle(manifest, inner) {
23446
+ validateManifest(manifest);
23447
+ if (manifest.compartments.length !== inner.length) {
23448
+ throw new Error(`multi-bundle: manifest has ${manifest.compartments.length} compartments but ${inner.length} inner bundles were provided.`);
23449
+ }
23450
+ for (let i = 0; i < inner.length; i++) {
23451
+ if (manifest.compartments[i].innerBytes !== inner[i].length) {
23452
+ throw new Error(`multi-bundle: compartment ${i} declares innerBytes ${manifest.compartments[i].innerBytes} but ${inner[i].length} bytes were provided.`);
23453
+ }
23454
+ }
23455
+ const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));
23456
+ const bodyLen = inner.reduce((n, b) => n + b.length, 0);
23457
+ const out = new Uint8Array(NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestBytes.length + bodyLen);
23458
+ out.set(NOYDB_MULTI_BUNDLE_MAGIC, 0);
23459
+ out[4] = NOYDB_MULTI_BUNDLE_VERSION;
23460
+ out[5] = 0;
23461
+ writeUint32BE(out, 6, manifestBytes.length);
23462
+ out.set(manifestBytes, NOYDB_MULTI_BUNDLE_PREFIX_BYTES);
23463
+ let off = NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestBytes.length;
23464
+ for (const b of inner) {
23465
+ out.set(b, off);
23466
+ off += b.length;
23467
+ }
23468
+ return out;
23469
+ }
23470
+ function hasMultiMagic(bytes) {
23471
+ if (bytes.length < NOYDB_MULTI_BUNDLE_MAGIC.length) return false;
23472
+ for (let i = 0; i < NOYDB_MULTI_BUNDLE_MAGIC.length; i++) if (bytes[i] !== NOYDB_MULTI_BUNDLE_MAGIC[i]) return false;
23473
+ return true;
23474
+ }
23475
+ function validateManifest(parsed) {
23476
+ if (parsed === null || typeof parsed !== "object") throw new Error("multi-bundle manifest must be a JSON object.");
23477
+ const m = parsed;
23478
+ if (m["multiFormatVersion"] !== NOYDB_MULTI_BUNDLE_VERSION) throw new Error(`multi-bundle manifest.multiFormatVersion must be ${NOYDB_MULTI_BUNDLE_VERSION}, got ${String(m["multiFormatVersion"])}.`);
23479
+ if (typeof m["handle"] !== "string" || m["handle"].length === 0) throw new Error("multi-bundle manifest.handle must be a non-empty string.");
23480
+ if (!Array.isArray(m["compartments"])) throw new Error("multi-bundle manifest.compartments must be an array.");
23481
+ const seenHandles = /* @__PURE__ */ new Set();
23482
+ for (const c of m["compartments"]) {
23483
+ if (c === null || typeof c !== "object") throw new Error("multi-bundle compartment must be an object.");
23484
+ const e = c;
23485
+ if (typeof e["handle"] !== "string" || e["handle"].length === 0) throw new Error("multi-bundle compartment.handle must be a non-empty string.");
23486
+ if (seenHandles.has(e["handle"])) {
23487
+ throw new Error(`multi-bundle manifest has a duplicate compartment handle "${e["handle"]}".`);
23488
+ }
23489
+ seenHandles.add(e["handle"]);
23490
+ if (typeof e["innerBytes"] !== "number" || !Number.isInteger(e["innerBytes"]) || e["innerBytes"] < 0) throw new Error("multi-bundle compartment.innerBytes must be a non-negative integer.");
23491
+ if (typeof e["innerSha256"] !== "string" || !/^[0-9a-f]{64}$/.test(e["innerSha256"])) throw new Error("multi-bundle compartment.innerSha256 must be 64-char lowercase hex.");
23492
+ }
23493
+ }
23494
+ function decodeMultiBundle(bytes) {
23495
+ if (!hasMultiMagic(bytes)) throw new Error("not a NOYDB multi-bundle: missing NDBM magic.");
23496
+ if (bytes.length < NOYDB_MULTI_BUNDLE_PREFIX_BYTES) throw new Error("multi-bundle truncated: shorter than the fixed prefix.");
23497
+ if (bytes[4] !== NOYDB_MULTI_BUNDLE_VERSION) throw new Error(`unsupported multi-bundle version ${String(bytes[4])}.`);
23498
+ const manifestLen = readUint32BE(bytes, 6);
23499
+ const manifestEnd = NOYDB_MULTI_BUNDLE_PREFIX_BYTES + manifestLen;
23500
+ if (manifestEnd > bytes.length) throw new Error("multi-bundle truncated: manifest length overruns the buffer.");
23501
+ const manifestJson = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(NOYDB_MULTI_BUNDLE_PREFIX_BYTES, manifestEnd));
23502
+ let parsed;
23503
+ try {
23504
+ parsed = JSON.parse(manifestJson);
23505
+ } catch (err) {
23506
+ throw new Error(`multi-bundle manifest is not valid JSON: ${err.message}`);
23507
+ }
23508
+ validateManifest(parsed);
23509
+ const inner = [];
23510
+ let off = manifestEnd;
23511
+ for (const c of parsed.compartments) {
23512
+ const end = off + c.innerBytes;
23513
+ if (end > bytes.length) throw new Error(`multi-bundle truncated: compartment "${c.handle}" innerBytes overruns the buffer.`);
23514
+ inner.push(bytes.subarray(off, end));
23515
+ off = end;
23516
+ }
23517
+ if (off !== bytes.length) {
23518
+ throw new Error(`multi-bundle: ${bytes.length - off} trailing byte(s) after the last compartment \u2014 buffer may be corrupt.`);
23519
+ }
23520
+ return { manifest: parsed, inner };
23521
+ }
23522
+ async function writeMultiVaultBundle(compartments, opts = {}) {
23523
+ if (compartments.length === 0) throw new Error("writeMultiVaultBundle: at least one compartment is required.");
23524
+ const inner = [];
23525
+ const entries = [];
23526
+ for (const c of compartments) {
23527
+ const innerBytes = await writeNoydbBundle(c.vault, c.bundleOptions ?? {});
23528
+ const header = readNoydbBundleHeader(innerBytes);
23529
+ const entry = {
23530
+ handle: header.handle,
23531
+ exportedAt: c.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
23532
+ innerBytes: innerBytes.length,
23533
+ innerSha256: await sha256Hex2(innerBytes)
23534
+ };
23535
+ if (c.roleTag !== void 0) entry.roleTag = c.roleTag;
23536
+ if (c.disclose?.name !== void 0 && c.disclose.name !== false) {
23537
+ entry.name = c.disclose.name === true ? c.vault.name : c.disclose.name;
23538
+ }
23539
+ if (c.disclose?.collections === true) {
23540
+ const names = await c.vault.collections();
23541
+ entry.collections = await Promise.all(
23542
+ names.map(async (n) => ({ name: n, count: await c.vault.collection(n).count() }))
23543
+ );
23544
+ }
23545
+ if (c.disclose?.publicEnvelope === true) {
23546
+ const env = readNoydbBundlePublicEnvelope(innerBytes);
23547
+ if (env !== void 0) entry.publicEnvelope = env;
23548
+ }
23549
+ const fence = await c.vault.schemaFenceState();
23550
+ entry.schemaVersion = fence.currentSchemaVersion;
23551
+ inner.push(innerBytes);
23552
+ entries.push(entry);
23553
+ }
23554
+ const manifest = {
23555
+ multiFormatVersion: NOYDB_MULTI_BUNDLE_VERSION,
23556
+ handle: opts.handle ?? generateULID(),
23557
+ compartments: entries
23558
+ };
23559
+ return encodeMultiBundle(manifest, inner);
23560
+ }
23561
+ async function readNoydbBundleManifest(bytes) {
23562
+ if (hasMultiMagic(bytes)) return [...decodeMultiBundle(bytes).manifest.compartments];
23563
+ if (hasNoydbBundleMagic(bytes)) {
23564
+ const header = readNoydbBundleHeader(bytes);
23565
+ const env = readNoydbBundlePublicEnvelope(bytes);
23566
+ const entry = {
23567
+ handle: header.handle,
23568
+ innerBytes: bytes.length,
23569
+ innerSha256: await sha256Hex2(bytes)
23570
+ };
23571
+ if (env !== void 0) entry.publicEnvelope = env;
23572
+ return [entry];
23573
+ }
23574
+ throw new Error("readNoydbBundleManifest: not a NOYDB bundle (no NDB1 or NDBM magic).");
23575
+ }
23576
+ function readMultiVaultBundleCompartment(bytes, selector) {
23577
+ if (typeof selector === "number" && !Number.isInteger(selector)) {
23578
+ throw new Error(`readMultiVaultBundleCompartment: numeric selector must be an integer, got ${selector}.`);
23579
+ }
23580
+ if (hasNoydbBundleMagic(bytes) && !hasMultiMagic(bytes)) {
23581
+ const header = readNoydbBundleHeader(bytes);
23582
+ if (selector === 0 || selector === header.handle) return bytes;
23583
+ throw new Error(`readMultiVaultBundleCompartment: single v1 bundle has only compartment "${header.handle}".`);
23584
+ }
23585
+ const { manifest, inner } = decodeMultiBundle(bytes);
23586
+ const idx = typeof selector === "number" ? selector : manifest.compartments.findIndex((c) => c.handle === selector);
23587
+ if (idx < 0 || idx >= inner.length) throw new Error(`readMultiVaultBundleCompartment: no compartment ${typeof selector === "number" ? `at index ${selector}` : `"${selector}"`}.`);
23588
+ return inner[idx];
23589
+ }
23590
+
24236
23591
  // src/bundle/walk-closure.ts
24237
23592
  init_errors();
24238
23593
  async function walkClosure(vault, opts) {
@@ -24388,7 +23743,7 @@ init_constants();
24388
23743
  init_entry();
24389
23744
  init_hash();
24390
23745
  init_bundle();
24391
- async function reKeyClosure(vault, closure) {
23746
+ async function reKeyClosure(vault, closure, fieldProjection) {
24392
23747
  const { name: vaultName, adapter, getDEK } = vault._introspectState();
24393
23748
  const collections = {};
24394
23749
  const deks = /* @__PURE__ */ new Map();
@@ -24397,29 +23752,40 @@ async function reKeyClosure(vault, closure) {
24397
23752
  const destDek = await generateDEK();
24398
23753
  deks.set(collectionName, destDek);
24399
23754
  const out = {};
23755
+ const projList = fieldProjection?.[collectionName];
23756
+ const proj = projList ? new Set(projList) : void 0;
23757
+ const project = (plaintext) => {
23758
+ if (!proj) return plaintext;
23759
+ const rec = JSON.parse(plaintext);
23760
+ const kept = {};
23761
+ if ("id" in rec) kept["id"] = rec["id"];
23762
+ for (const f of proj) if (f in rec) kept[f] = rec[f];
23763
+ return JSON.stringify(kept);
23764
+ };
24400
23765
  for (const id of ids) {
24401
23766
  const env = await adapter.get(vaultName, collectionName, id);
24402
23767
  if (!env) continue;
24403
23768
  if (env._cek !== void 0) {
24404
23769
  const cek = await unwrapCek(env._cek, srcDek);
24405
23770
  const plaintext2 = await decrypt(env._iv, env._data, cek);
24406
- const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
23771
+ const { iv: iv2, data: data2 } = await encrypt(project(plaintext2), cek);
24407
23772
  const wrapped = await wrapCek(cek, destDek);
24408
23773
  out[id] = { ...env, _iv: iv2, _data: data2, _cek: wrapped };
24409
23774
  continue;
24410
23775
  }
24411
23776
  const plaintext = await decrypt(env._iv, env._data, srcDek);
24412
- const { iv, data } = await encrypt(plaintext, destDek);
23777
+ const { iv, data } = await encrypt(project(plaintext), destDek);
24413
23778
  out[id] = { ...env, _iv: iv, _data: data };
24414
23779
  }
24415
23780
  collections[collectionName] = out;
24416
23781
  }
24417
23782
  return { collections, deks };
24418
23783
  }
24419
- async function reKeySchemas(vault, closure, destDeks) {
23784
+ async function reKeySchemas(vault, closure, destDeks, fieldProjection) {
24420
23785
  const { name: vaultName, adapter, getDEK } = vault._introspectState();
24421
23786
  const out = {};
24422
23787
  for (const collectionName of closure.keys()) {
23788
+ if (fieldProjection?.[collectionName]) continue;
24423
23789
  const env = await adapter.get(vaultName, SCHEMAS_COLLECTION, collectionName);
24424
23790
  if (!env) continue;
24425
23791
  const destDek = destDeks.get(collectionName);
@@ -24511,6 +23877,11 @@ async function sealDeks(deks) {
24511
23877
  };
24512
23878
  }
24513
23879
  async function extractPartition(vault, opts) {
23880
+ if (vault.role === "custodian") {
23881
+ throw new PartitionExtractionError(
23882
+ "extractPartition is owner-only; a custodian cannot extract-and-sever (FR-6: producing a re-keyed standalone partition is an ownership operation; use the Deed owner)."
23883
+ );
23884
+ }
24514
23885
  if (vault.role !== "owner") {
24515
23886
  throw new PartitionExtractionError(
24516
23887
  `extractPartition requires the 'owner' role on the source vault; caller is '${vault.role}'. Producing a re-keyed standalone partition is an ownership operation.`
@@ -24518,7 +23889,7 @@ async function extractPartition(vault, opts) {
24518
23889
  }
24519
23890
  if (opts.carrySchemas) await vault._drainPendingSchemaWrites();
24520
23891
  const { closure } = await walkClosure(vault, opts);
24521
- const { collections, deks } = await reKeyClosure(vault, closure);
23892
+ const { collections, deks } = await reKeyClosure(vault, closure, opts.fieldProjection);
24522
23893
  let ledgerHead;
24523
23894
  let ledgerEntries;
24524
23895
  if (opts.carryLedger && vault._getLedgerOrNull() !== null) {
@@ -24530,7 +23901,7 @@ async function extractPartition(vault, opts) {
24530
23901
  deks.set(LEDGER_COLLECTION, ledgerDek);
24531
23902
  }
24532
23903
  }
24533
- const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks) : {};
23904
+ const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks, opts.fieldProjection) : {};
24534
23905
  const internal = {};
24535
23906
  if (Object.keys(internalSchemas).length > 0) internal[SCHEMAS_COLLECTION] = internalSchemas;
24536
23907
  if (ledgerEntries) internal[LEDGER_COLLECTION] = ledgerEntries;
@@ -24745,6 +24116,41 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
24745
24116
  return { vaultName, userId };
24746
24117
  }
24747
24118
 
24119
+ // src/bundle/decrypt-partition.ts
24120
+ init_crypto();
24121
+ init_record_keys();
24122
+ init_bundle();
24123
+ async function decryptExtractedPartition(bundleBytes, transferKey) {
24124
+ const header = readNoydbBundleHeader(bundleBytes);
24125
+ if (header.bundleKind !== "extracted-partition" || header.transferSeal === void 0) {
24126
+ throw new Error("decryptExtractedPartition: bundle is not an extracted-partition.");
24127
+ }
24128
+ const { dumpJson } = await readNoydbBundle(bundleBytes);
24129
+ const { dump, seal } = parseExtractedPartitionBody(dumpJson);
24130
+ const deks = await unsealDeks(seal, transferKey);
24131
+ const backup = JSON.parse(dump);
24132
+ const out = {};
24133
+ for (const [collection, byId] of Object.entries(backup.collections)) {
24134
+ const dek = deks.get(collection);
24135
+ if (dek === void 0) continue;
24136
+ const recs = [];
24137
+ for (const [id, env] of Object.entries(byId)) {
24138
+ const plaintext = env._cek !== void 0 ? await decrypt(env._iv, env._data, await unwrapCek(env._cek, dek)) : await decrypt(env._iv, env._data, dek);
24139
+ const body = JSON.parse(plaintext);
24140
+ recs.push({
24141
+ id,
24142
+ record: { ...body, id },
24143
+ ts: env._ts,
24144
+ version: env._v,
24145
+ ...env._source !== void 0 ? { source: env._source } : {},
24146
+ ...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
24147
+ });
24148
+ }
24149
+ out[collection] = recs;
24150
+ }
24151
+ return out;
24152
+ }
24153
+
24748
24154
  // src/bundle/index.ts
24749
24155
  init_errors();
24750
24156
  init_errors();
@@ -24764,21 +24170,30 @@ init_errors();
24764
24170
  NOYDB_BUNDLE_FORMAT_VERSION,
24765
24171
  NOYDB_BUNDLE_MAGIC,
24766
24172
  NOYDB_BUNDLE_PREFIX_BYTES,
24173
+ NOYDB_MULTI_BUNDLE_MAGIC,
24174
+ NOYDB_MULTI_BUNDLE_PREFIX_BYTES,
24175
+ NOYDB_MULTI_BUNDLE_VERSION,
24767
24176
  PartitionExtractionError,
24768
24177
  TransferSealError,
24769
24178
  adoptPartition,
24770
24179
  createOwnerOnAdoptedPartition,
24180
+ decodeMultiBundle,
24181
+ decryptExtractedPartition,
24771
24182
  describeExtraction,
24772
24183
  encodeBundleHeader,
24184
+ encodeMultiBundle,
24773
24185
  extractPartition,
24774
24186
  generateULID,
24775
24187
  isULID,
24188
+ readMultiVaultBundleCompartment,
24776
24189
  readNoydbBundle,
24777
24190
  readNoydbBundleHeader,
24191
+ readNoydbBundleManifest,
24778
24192
  resetBrotliSupportCache,
24779
24193
  unsealDeks,
24780
24194
  validateBundleHeader,
24781
24195
  walkClosure,
24196
+ writeMultiVaultBundle,
24782
24197
  writeNoydbBundle
24783
24198
  });
24784
24199
  //# sourceMappingURL=index.cjs.map