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

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 +421 -1209
  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 +42 -193
  18. package/dist/bundle/index.js.map +1 -1
  19. package/dist/{chunk-SQOK5UM6.js → chunk-2KA3PDUR.js} +2 -2
  20. package/dist/{chunk-HYJMAV53.js → chunk-2RHBFCWQ.js} +93 -93
  21. package/dist/chunk-2RHBFCWQ.js.map +1 -0
  22. package/dist/{chunk-U2XSUCDF.js → chunk-3BANVNDH.js} +2 -2
  23. package/dist/{chunk-P65YMN5V.js → chunk-56ENKU46.js} +397 -165
  24. package/dist/chunk-56ENKU46.js.map +1 -0
  25. package/dist/{chunk-37VGJM3T.js → chunk-7JSP3E67.js} +2 -2
  26. package/dist/{chunk-F5ILTHMU.js → chunk-ANLOD6IS.js} +5 -5
  27. package/dist/{chunk-JYNH4FIM.js → chunk-C7UIT5XY.js} +4 -4
  28. package/dist/{chunk-OTWT6BAJ.js → chunk-DDOYOMAD.js} +2 -2
  29. package/dist/chunk-DDOYOMAD.js.map +1 -0
  30. package/dist/{chunk-TGIJTNM3.js → chunk-E5TJAQS7.js} +2 -2
  31. package/dist/{chunk-IY24WS2P.js → chunk-EJJTUDNI.js} +4 -4
  32. package/dist/{chunk-IY24WS2P.js.map → chunk-EJJTUDNI.js.map} +1 -1
  33. package/dist/{chunk-GJTKMME7.js → chunk-EW3H5Y7N.js} +2 -2
  34. package/dist/{chunk-JDCPRJVS.js → chunk-EYZJULEN.js} +4 -4
  35. package/dist/{chunk-I3IYTUUI.js → chunk-FCIZXX56.js} +3 -3
  36. package/dist/{chunk-C2RJVZZL.js → chunk-FJ3C3ELF.js} +2 -2
  37. package/dist/{chunk-ZONKSLF2.js → chunk-FO5WEDKF.js} +2 -2
  38. package/dist/{chunk-SQKAECUL.js → chunk-FUDVHE2U.js} +2 -2
  39. package/dist/{chunk-IVZWHIEK.js → chunk-GHXOVGTX.js} +5 -5
  40. package/dist/{chunk-UU6M64HI.js → chunk-GPZHHTJU.js} +4 -4
  41. package/dist/{chunk-3HNKR65T.js → chunk-H4XFA2LM.js} +3 -3
  42. package/dist/{chunk-JOK73NDT.js → chunk-HUXDQIVU.js} +3 -3
  43. package/dist/{chunk-F5GWNSE2.js → chunk-J73KU4AE.js} +3 -3
  44. package/dist/{chunk-F5GWNSE2.js.map → chunk-J73KU4AE.js.map} +1 -1
  45. package/dist/{chunk-O5XKZCUD.js → chunk-JJKXJAH2.js} +5 -5
  46. package/dist/{chunk-TNH5SLCD.js → chunk-KD253AI5.js} +2 -2
  47. package/dist/{chunk-WWVJXBOT.js → chunk-KJ37E3R5.js} +5 -5
  48. package/dist/{chunk-S45MDEEF.js → chunk-KNJ7MK4B.js} +2 -2
  49. package/dist/{chunk-TA6HPKWQ.js → chunk-LR7CODVN.js} +1 -1
  50. package/dist/chunk-LR7CODVN.js.map +1 -0
  51. package/dist/{chunk-J6RGRZOY.js → chunk-LX4CPLU6.js} +2 -2
  52. package/dist/{chunk-WE2BUQD2.js → chunk-N4EXCKWP.js} +3 -3
  53. package/dist/{chunk-EYK72OTL.js → chunk-OCRDV3NU.js} +5 -5
  54. package/dist/chunk-OCRDV3NU.js.map +1 -0
  55. package/dist/{chunk-JBBWALNI.js → chunk-OMBPGXCL.js} +2 -2
  56. package/dist/{chunk-NV4IHBZS.js → chunk-PS6PSEZL.js} +5 -5
  57. package/dist/{chunk-6QE4DUYC.js → chunk-Q7P4WHTL.js} +2 -2
  58. package/dist/{chunk-TAMRU7A2.js → chunk-QYQRAOEF.js} +4 -4
  59. package/dist/{chunk-6QAZ5O6X.js → chunk-RHVYFAVQ.js} +2 -2
  60. package/dist/chunk-RZOGD7IF.js +232 -0
  61. package/dist/chunk-RZOGD7IF.js.map +1 -0
  62. package/dist/{chunk-YPIOFSN3.js → chunk-SKYBEGHB.js} +2 -2
  63. package/dist/{chunk-7MRT7EPB.js → chunk-TESFHBOW.js} +3 -3
  64. package/dist/{chunk-CQYEDODS.js → chunk-TSUICI5N.js} +3 -3
  65. package/dist/{chunk-FRRJIUSI.js → chunk-UNBX2HMA.js} +17 -9
  66. package/dist/chunk-UNBX2HMA.js.map +1 -0
  67. package/dist/{chunk-TYMDCIQM.js → chunk-VGAN5RLD.js} +4 -4
  68. package/dist/{chunk-5YTXYPES.js → chunk-VJNV2GRF.js} +5 -5
  69. package/dist/{chunk-NSXNXLYM.js → chunk-VUUQYWF5.js} +2 -2
  70. package/dist/{chunk-IW4L4X65.js → chunk-WVYL6HM7.js} +2 -2
  71. package/dist/{chunk-BZW5IL43.js → chunk-Y5CTT6K5.js} +4 -4
  72. package/dist/{chunk-C6W5KVDV.js → chunk-YP2AYE5W.js} +35 -35
  73. package/dist/chunk-YP2AYE5W.js.map +1 -0
  74. package/dist/{chunk-KOAJ3TZM.js → chunk-YRQPI67X.js} +2 -2
  75. package/dist/{chunk-MBXKRHSS.js → chunk-YYTM4U4J.js} +2 -2
  76. package/dist/{chunk-2XA2ZML4.js → chunk-ZCBJIDT4.js} +3 -3
  77. package/dist/{chunk-AI4USDRI.js → chunk-ZW2YSN6G.js} +4 -4
  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-YBKBNPVM.js} +3 -3
  83. package/dist/{ulid-Dwt3JEcy.d.ts → decrypt-partition-C71vhnND.d.cts} +19 -64
  84. package/dist/{ulid-Bg-IBJyA.d.cts → decrypt-partition-CyyJUWLR.d.ts} +19 -64
  85. package/dist/{delegation-DP4COTXB.js → delegation-4JSMM6BB.js} +5 -5
  86. package/dist/derivations/index.cjs.map +1 -1
  87. package/dist/derivations/index.d.cts +6 -6
  88. package/dist/derivations/index.d.ts +6 -6
  89. package/dist/derivations/index.js +4 -4
  90. package/dist/{dev-unlock-Bw7iBD1D.d.cts → dev-unlock-BdrE0kbS.d.cts} +1 -1
  91. package/dist/{dev-unlock-DzDzLTdZ.d.ts → dev-unlock-ByBkl99-.d.ts} +1 -1
  92. package/dist/{errors-Dkc_fi-S.d.cts → errors-Dwk2k1xY.d.cts} +14 -5
  93. package/dist/{errors-Dkc_fi-S.d.ts → errors-Dwk2k1xY.d.ts} +14 -5
  94. package/dist/executor-3SVNESQ3.js +8 -0
  95. package/dist/executor-BIW4FT5R.js +12 -0
  96. package/dist/executor-VEZUBJNQ.js +8 -0
  97. package/dist/{fanout-sidecar-YXNAEZ33.js → fanout-sidecar-ZQT4Y7PF.js} +2 -2
  98. package/dist/forget/index.js +4 -4
  99. package/dist/guards/index.cjs.map +1 -1
  100. package/dist/guards/index.d.cts +6 -6
  101. package/dist/guards/index.d.ts +6 -6
  102. package/dist/guards/index.js +6 -6
  103. package/dist/{hash-C52X_-m5.d.cts → hash-BUkDp_8Q.d.cts} +1 -1
  104. package/dist/{hash-DepR-xVc.d.ts → hash-CZxVv8RH.d.ts} +1 -1
  105. package/dist/history/index.cjs.map +1 -1
  106. package/dist/history/index.d.cts +6 -6
  107. package/dist/history/index.d.ts +6 -6
  108. package/dist/history/index.js +5 -5
  109. package/dist/i18n/index.cjs.map +1 -1
  110. package/dist/i18n/index.d.cts +5 -5
  111. package/dist/i18n/index.d.ts +5 -5
  112. package/dist/i18n/index.js +6 -6
  113. package/dist/index-CBUhOmrM.d.cts +70 -0
  114. package/dist/index-DFhKV-6A.d.ts +70 -0
  115. package/dist/{index-tZqVB9g5.d.cts → index-DoxKSsMj.d.cts} +2 -2
  116. package/dist/{index-Bm9hIY7t.d.ts → index-LaexBi3v.d.ts} +2 -2
  117. package/dist/index.cjs +25660 -25495
  118. package/dist/index.cjs.map +1 -1
  119. package/dist/index.d.cts +135 -80
  120. package/dist/index.d.ts +135 -80
  121. package/dist/index.js +70 -51
  122. package/dist/index.js.map +1 -1
  123. package/dist/indexing/index.cjs.map +1 -1
  124. package/dist/indexing/index.js +4 -4
  125. package/dist/issue-LEBPVF3Y.js +12 -0
  126. package/dist/kernel/index.cjs +657 -0
  127. package/dist/kernel/index.cjs.map +1 -0
  128. package/dist/kernel/index.d.cts +11 -0
  129. package/dist/kernel/index.d.ts +11 -0
  130. package/dist/kernel/index.js +40 -0
  131. package/dist/{ledger-I7JUYP4L.js → ledger-FLRTSOYH.js} +5 -5
  132. package/dist/materialized-views/index.cjs.map +1 -1
  133. package/dist/materialized-views/index.d.cts +6 -6
  134. package/dist/materialized-views/index.d.ts +6 -6
  135. package/dist/materialized-views/index.js +8 -8
  136. package/dist/{mime-magic-Cxf9B_Dm.d.cts → mime-magic-BAhLjkHw.d.cts} +1 -1
  137. package/dist/{mime-magic-Dejetix_.d.ts → mime-magic-C1UbcBxP.d.ts} +1 -1
  138. package/dist/noydb-6FA46A4M.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-DBKJEBBF.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-CMEVTOCN.js +8 -0
  153. package/dist/{registry-NWHOLD5M.js → registry-OUZ3VBZA.js} +3 -3
  154. package/dist/registry-XUBRO5JJ.js +8 -0
  155. package/dist/{revoke-5IEK22KT.js → revoke-P5D3UTRX.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-NEQPCHMW.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-KKCHF2VB.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-D1zjEV3n.d.cts} +1 -1
  179. package/dist/{strategy-54eIwox5.d.ts → strategy-YQ1qJWyq.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-Ctxapq1b.d.ts → transition-guard-BSLdikC_.d.ts} +1 -1
  190. package/dist/{transition-guard-BcLyTGYq.d.cts → transition-guard-DPs6al8h.d.cts} +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-Bhs2i_Ll.d.cts → types-BCYvhKzr.d.cts} +282 -578
  197. package/dist/{types-DONgts0n.d.ts → types-CCq0WHh9.d.ts} +282 -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-BYb3p9wT.d.cts → with-materialized-view-CTHe6uh9.d.cts} +1 -1
  203. package/dist/{with-materialized-view-CyVLOr09.d.ts → with-materialized-view-DiD41wQp.d.ts} +1 -1
  204. package/dist/{with-overlayed-view-BhLRxqwI.d.ts → with-overlayed-view-DlbsJMhF.d.ts} +1 -1
  205. package/dist/{with-overlayed-view-LGrQ984e.d.cts → with-overlayed-view-Dlz5hcM8.d.cts} +1 -1
  206. package/dist/{with-rollup-Bj8c7ttB.d.cts → with-rollup-BBWdrCvu.d.cts} +1 -1
  207. package/dist/{with-rollup-CO8ibRcK.d.ts → with-rollup-mT4_CWaU.d.ts} +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-SQOK5UM6.js.map → chunk-2KA3PDUR.js.map} +0 -0
  231. /package/dist/{chunk-U2XSUCDF.js.map → chunk-3BANVNDH.js.map} +0 -0
  232. /package/dist/{chunk-37VGJM3T.js.map → chunk-7JSP3E67.js.map} +0 -0
  233. /package/dist/{chunk-F5ILTHMU.js.map → chunk-ANLOD6IS.js.map} +0 -0
  234. /package/dist/{chunk-JYNH4FIM.js.map → chunk-C7UIT5XY.js.map} +0 -0
  235. /package/dist/{chunk-TGIJTNM3.js.map → chunk-E5TJAQS7.js.map} +0 -0
  236. /package/dist/{chunk-GJTKMME7.js.map → chunk-EW3H5Y7N.js.map} +0 -0
  237. /package/dist/{chunk-JDCPRJVS.js.map → chunk-EYZJULEN.js.map} +0 -0
  238. /package/dist/{chunk-I3IYTUUI.js.map → chunk-FCIZXX56.js.map} +0 -0
  239. /package/dist/{chunk-C2RJVZZL.js.map → chunk-FJ3C3ELF.js.map} +0 -0
  240. /package/dist/{chunk-ZONKSLF2.js.map → chunk-FO5WEDKF.js.map} +0 -0
  241. /package/dist/{chunk-SQKAECUL.js.map → chunk-FUDVHE2U.js.map} +0 -0
  242. /package/dist/{chunk-IVZWHIEK.js.map → chunk-GHXOVGTX.js.map} +0 -0
  243. /package/dist/{chunk-UU6M64HI.js.map → chunk-GPZHHTJU.js.map} +0 -0
  244. /package/dist/{chunk-3HNKR65T.js.map → chunk-H4XFA2LM.js.map} +0 -0
  245. /package/dist/{chunk-JOK73NDT.js.map → chunk-HUXDQIVU.js.map} +0 -0
  246. /package/dist/{chunk-O5XKZCUD.js.map → chunk-JJKXJAH2.js.map} +0 -0
  247. /package/dist/{chunk-TNH5SLCD.js.map → chunk-KD253AI5.js.map} +0 -0
  248. /package/dist/{chunk-WWVJXBOT.js.map → chunk-KJ37E3R5.js.map} +0 -0
  249. /package/dist/{chunk-S45MDEEF.js.map → chunk-KNJ7MK4B.js.map} +0 -0
  250. /package/dist/{chunk-J6RGRZOY.js.map → chunk-LX4CPLU6.js.map} +0 -0
  251. /package/dist/{chunk-WE2BUQD2.js.map → chunk-N4EXCKWP.js.map} +0 -0
  252. /package/dist/{chunk-JBBWALNI.js.map → chunk-OMBPGXCL.js.map} +0 -0
  253. /package/dist/{chunk-NV4IHBZS.js.map → chunk-PS6PSEZL.js.map} +0 -0
  254. /package/dist/{chunk-6QE4DUYC.js.map → chunk-Q7P4WHTL.js.map} +0 -0
  255. /package/dist/{chunk-TAMRU7A2.js.map → chunk-QYQRAOEF.js.map} +0 -0
  256. /package/dist/{chunk-6QAZ5O6X.js.map → chunk-RHVYFAVQ.js.map} +0 -0
  257. /package/dist/{chunk-YPIOFSN3.js.map → chunk-SKYBEGHB.js.map} +0 -0
  258. /package/dist/{chunk-7MRT7EPB.js.map → chunk-TESFHBOW.js.map} +0 -0
  259. /package/dist/{chunk-CQYEDODS.js.map → chunk-TSUICI5N.js.map} +0 -0
  260. /package/dist/{chunk-TYMDCIQM.js.map → chunk-VGAN5RLD.js.map} +0 -0
  261. /package/dist/{chunk-5YTXYPES.js.map → chunk-VJNV2GRF.js.map} +0 -0
  262. /package/dist/{chunk-NSXNXLYM.js.map → chunk-VUUQYWF5.js.map} +0 -0
  263. /package/dist/{chunk-IW4L4X65.js.map → chunk-WVYL6HM7.js.map} +0 -0
  264. /package/dist/{chunk-BZW5IL43.js.map → chunk-Y5CTT6K5.js.map} +0 -0
  265. /package/dist/{chunk-KOAJ3TZM.js.map → chunk-YRQPI67X.js.map} +0 -0
  266. /package/dist/{chunk-MBXKRHSS.js.map → chunk-YYTM4U4J.js.map} +0 -0
  267. /package/dist/{chunk-2XA2ZML4.js.map → chunk-ZCBJIDT4.js.map} +0 -0
  268. /package/dist/{chunk-AI4USDRI.js.map → chunk-ZW2YSN6G.js.map} +0 -0
  269. /package/dist/{crypto-456N7UVX.js.map → crypto-YBKBNPVM.js.map} +0 -0
  270. /package/dist/{delegation-DP4COTXB.js.map → delegation-4JSMM6BB.js.map} +0 -0
  271. /package/dist/{executor-4IEW4KG5.js.map → executor-3SVNESQ3.js.map} +0 -0
  272. /package/dist/{executor-KYJCJCIN.js.map → executor-BIW4FT5R.js.map} +0 -0
  273. /package/dist/{executor-W7VIBOBZ.js.map → executor-VEZUBJNQ.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-LEBPVF3Y.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-FLRTSOYH.js.map} +0 -0
  278. /package/dist/{public-envelope-5XRTUNKF.js.map → noydb-6FA46A4M.js.map} +0 -0
  279. /package/dist/{registry-ATRHOG5B.js.map → public-envelope-DBKJEBBF.js.map} +0 -0
  280. /package/dist/{registry-LEHB26TY.js.map → registry-CMEVTOCN.js.map} +0 -0
  281. /package/dist/{registry-NWHOLD5M.js.map → registry-OUZ3VBZA.js.map} +0 -0
  282. /package/dist/{revoke-5IEK22KT.js.map → registry-XUBRO5JJ.js.map} +0 -0
  283. /package/dist/{signer-I6YARZQA.js.map → revoke-P5D3UTRX.js.map} +0 -0
  284. /package/dist/{stale-CPESGAPL.js.map → signer-NEQPCHMW.js.map} +0 -0
  285. /package/dist/{state-vault-JR3CFGNP.js.map → stale-KKCHF2VB.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, 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,60 +929,6 @@ 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) {
990
- 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.`
993
- );
994
- this.name = "VaultTemplateNotFoundError";
995
- this.templateName = templateName;
996
- }
997
- };
998
932
  ForgetStrategyNotConfiguredError = class extends NoydbError {
999
933
  constructor(message = 'vault.forget() requires a forget strategy. Pass `forgetStrategy: withForgetCascade({ subjects: { <collection>: <subjectField> } })` from "@noy-db/hub/forget" to createNoydb().') {
1000
934
  super("FORGET_NOT_CONFIGURED", message);
@@ -2518,12 +2452,14 @@ var init_user_envelope = __esm({
2518
2452
  // src/team/keyring.ts
2519
2453
  function canGrant(callerRole, targetRole) {
2520
2454
  if (callerRole === "owner") return true;
2455
+ if (callerRole === "custodian") return false;
2521
2456
  if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
2522
2457
  return false;
2523
2458
  }
2524
2459
  function canRevoke(callerRole, targetRole) {
2525
2460
  if (targetRole === "owner") return false;
2526
2461
  if (callerRole === "owner") return true;
2462
+ if (callerRole === "custodian") return false;
2527
2463
  if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
2528
2464
  return false;
2529
2465
  }
@@ -2687,7 +2623,7 @@ async function grant(adapter, vault, callerKeyring, options) {
2687
2623
  wrappedDeks[collName] = await wrapKey(dek, newKek);
2688
2624
  }
2689
2625
  }
2690
- if (options.role === "owner" || options.role === "admin" || options.role === "viewer") {
2626
+ if (options.role === "owner" || options.role === "admin" || options.role === "custodian" || options.role === "viewer") {
2691
2627
  for (const [collName, dek] of callerKeyring.deks) {
2692
2628
  if (!(collName in wrappedDeks)) {
2693
2629
  wrappedDeks[collName] = await wrapKey(dek, newKek);
@@ -2835,6 +2771,11 @@ async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
2835
2771
  await writeKeyringFile(adapter, vault, options.userId, next);
2836
2772
  }
2837
2773
  async function rotateKeys(adapter, vault, callerKeyring, collections) {
2774
+ if (callerKeyring.role === "custodian") {
2775
+ throw new PermissionDeniedError(
2776
+ "custodian cannot rotate keys (FR-6: re-key is an owner-only meta-capability; use the Deed owner)"
2777
+ );
2778
+ }
2838
2779
  const newDeks = /* @__PURE__ */ new Map();
2839
2780
  for (const collName of collections) {
2840
2781
  newDeks.set(collName, await generateDEK());
@@ -2944,7 +2885,7 @@ async function buildRecipientKeyringFile(callerKeyring, recipient) {
2944
2885
  wrappedDeks[collName] = await wrapKey(dek, newKek);
2945
2886
  }
2946
2887
  }
2947
- if (role === "owner" || role === "admin" || role === "viewer") {
2888
+ if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") {
2948
2889
  for (const [collName, dek] of callerKeyring.deks) {
2949
2890
  if (!(collName in wrappedDeks)) {
2950
2891
  wrappedDeks[collName] = await wrapKey(dek, newKek);
@@ -3018,12 +2959,13 @@ async function ensureCollectionDEK(adapter, vault, keyring) {
3018
2959
  };
3019
2960
  }
3020
2961
  function hasWritePermission(keyring, collectionName) {
3021
- if (keyring.role === "owner" || keyring.role === "admin") return true;
2962
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return true;
3022
2963
  if (keyring.role === "viewer" || keyring.role === "client") return false;
3023
2964
  return keyring.permissions[collectionName] === "rw";
3024
2965
  }
3025
2966
  function hasAccess(keyring, collectionName) {
3026
- if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "viewer") return true;
2967
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian" || keyring.role === "viewer")
2968
+ return true;
3027
2969
  return collectionName in keyring.permissions;
3028
2970
  }
3029
2971
  async function persistKeyring(adapter, vault, keyring) {
@@ -3075,7 +3017,7 @@ function hasImportCapability(keyring, tier, format) {
3075
3017
  return cap?.bundle === true;
3076
3018
  }
3077
3019
  function resolvePermissions(role, explicit) {
3078
- if (role === "owner" || role === "admin" || role === "viewer") return {};
3020
+ if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") return {};
3079
3021
  return explicit ?? {};
3080
3022
  }
3081
3023
  async function writeKeyringFile(adapter, vault, userId, keyringFile) {
@@ -3781,15 +3723,6 @@ var init_store = __esm({
3781
3723
  }
3782
3724
  });
3783
3725
 
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
3726
  // src/policy/errors.ts
3794
3727
  var PolicyDeniedError, RecoveryNotEnrolledError, ManagedRecoveryNotEnrolledError, RecoveryProfileNotImplementedError;
3795
3728
  var init_errors2 = __esm({
@@ -4992,10 +4925,10 @@ function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAf
4992
4925
  }
4993
4926
  }
4994
4927
  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;
4928
+ const canonical = toCanonicalDecimalString(input);
4929
+ if (canonical === null) return { ok: false, reason: "nonfinite" };
4930
+ const negative = canonical.startsWith("-");
4931
+ const unsigned = negative ? canonical.slice(1) : canonical;
4999
4932
  const dot = unsigned.indexOf(".");
5000
4933
  const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
5001
4934
  const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
@@ -5491,7 +5424,7 @@ function dekKey(collection, tier) {
5491
5424
  }
5492
5425
  function assertTierAccess(keyring, collection, tier) {
5493
5426
  if (tier <= 0) return;
5494
- if (keyring.role === "owner" || keyring.role === "admin") return;
5427
+ if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return;
5495
5428
  if (!keyring.deks.has(dekKey(collection, tier))) {
5496
5429
  throw new TierNotGrantedError(collection, tier);
5497
5430
  }
@@ -6562,7 +6495,7 @@ function serializeClause(clause) {
6562
6495
  }
6563
6496
  function canonicalCtxHash(ctx) {
6564
6497
  if (ctx === void 0) return "";
6565
- const canonical2 = JSON.stringify(ctx, (_key, value) => {
6498
+ const canonical = JSON.stringify(ctx, (_key, value) => {
6566
6499
  if (value && typeof value === "object" && !Array.isArray(value)) {
6567
6500
  const sorted = {};
6568
6501
  for (const k of Object.keys(value).sort()) {
@@ -6573,8 +6506,8 @@ function canonicalCtxHash(ctx) {
6573
6506
  return value;
6574
6507
  });
6575
6508
  let h = 5381;
6576
- for (let i = 0; i < canonical2.length; i++) {
6577
- h = (h << 5) + h ^ canonical2.charCodeAt(i);
6509
+ for (let i = 0; i < canonical.length; i++) {
6510
+ h = (h << 5) + h ^ canonical.charCodeAt(i);
6578
6511
  }
6579
6512
  return (h >>> 0).toString(16).padStart(8, "0");
6580
6513
  }
@@ -7283,29 +7216,6 @@ var init_builder = __esm({
7283
7216
  }
7284
7217
  });
7285
7218
 
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
7219
  // src/aggregate/canonical-key.ts
7310
7220
  function canonicalGroupKey(fields, row) {
7311
7221
  const sorted = [...fields].sort();
@@ -8534,7 +8444,7 @@ var init_transaction = __esm({
8534
8444
  const v = this._db.vault(name);
8535
8445
  if (this._amendment && !this._amendmentVaults.has(name)) {
8536
8446
  const role = v.role;
8537
- if (role !== "admin" && role !== "owner") {
8447
+ if (role !== "admin" && role !== "owner" && role !== "custodian") {
8538
8448
  throw new AmendmentForbiddenError(v.userId, role);
8539
8449
  }
8540
8450
  const reg = v._getGuardRegistry();
@@ -8904,12 +8814,12 @@ var init_dependency_analyzer = __esm({
8904
8814
 
8905
8815
  // src/materialized-views/query-hash.ts
8906
8816
  async function computeQueryHash(mvName, dependencies, queryPlanSummary) {
8907
- const canonical2 = JSON.stringify({
8817
+ const canonical = JSON.stringify({
8908
8818
  mvName,
8909
8819
  dependencies: [...dependencies].sort(),
8910
8820
  queryPlanSummary
8911
8821
  });
8912
- const bytes = new TextEncoder().encode(canonical2);
8822
+ const bytes = new TextEncoder().encode(canonical);
8913
8823
  const digest = await crypto.subtle.digest("SHA-256", bytes);
8914
8824
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
8915
8825
  }
@@ -9722,6 +9632,13 @@ var init_collection = __esm({
9722
9632
  * flag) still decrypts CEK records.
9723
9633
  */
9724
9634
  perRecordCek;
9635
+ /**
9636
+ * Per-record provenance opt-in (`provenance: true`). When set, `put()` calls
9637
+ * that supply a `source` option stamp `_source`/`_sourceTs` onto the
9638
+ * unencrypted envelope metadata. Off by default — zero cost for collections
9639
+ * that don't need lineage tracking (FR-5, #445).
9640
+ */
9641
+ provenance;
9725
9642
  /**
9726
9643
  * Session-scoped `(id) → CEK` cache for this collection. Lets updates
9727
9644
  * reuse a record's stable CEK and lets repeated reads skip the AES-KW
@@ -9881,6 +9798,7 @@ var init_collection = __esm({
9881
9798
  }
9882
9799
  this.perRecordCek = opts.perRecordKeys === true;
9883
9800
  this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
9801
+ this.provenance = opts.provenance === true;
9884
9802
  if (opts.crdt && opts.onRegisterConflictResolver) {
9885
9803
  const crdtMode = opts.crdt;
9886
9804
  const crdtResolver = async (id, local, remote) => {
@@ -10068,6 +9986,33 @@ var init_collection = __esm({
10068
9986
  if (json === null) return null;
10069
9987
  return JSON.parse(json);
10070
9988
  }
9989
+ /**
9990
+ * Read a record's unencrypted envelope metadata (version, timestamps,
9991
+ * provenance) without decrypting the body.
9992
+ *
9993
+ * Returns `null` when no envelope exists for `id` (record absent or never
9994
+ * written). Only `_source`/`_sourceTs` fields are populated when the
9995
+ * collection was opened with `provenance: true` AND the record was written
9996
+ * with a `source` option — but this method works on any collection because
9997
+ * it reads the raw envelope directly.
9998
+ *
9999
+ * @returns `{ version, timestamp, by?, source?, sourceTs? }` or `null`.
10000
+ *
10001
+ * @example
10002
+ * const meta = await clients.getMetadata('c1')
10003
+ * if (meta) console.log(meta.source, meta.timestamp)
10004
+ */
10005
+ async getMetadata(id) {
10006
+ const env = await this.adapter.get(this.vault, this.name, id);
10007
+ if (!env) return null;
10008
+ return {
10009
+ version: env._v,
10010
+ timestamp: env._ts,
10011
+ ...env._by !== void 0 ? { by: env._by } : {},
10012
+ ...env._source !== void 0 ? { source: env._source } : {},
10013
+ ...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
10014
+ };
10015
+ }
10071
10016
  /**
10072
10017
  * Return a presence handle for this collection.
10073
10018
  *
@@ -10105,6 +10050,14 @@ var init_collection = __esm({
10105
10050
  * `reason` is stamped onto the resulting ledger entry
10106
10051
  * so audit consumers can filter via
10107
10052
  * `entries.filter(e => e.reason?.startsWith('import:'))`.
10053
+ * `source` is an opaque source id (e.g. `'crm-sync'`, `'firm-A'`)
10054
+ * stamped onto the envelope as `_source`/`_sourceTs` when
10055
+ * the collection has `provenance: true`. Ignored otherwise
10056
+ * (zero cost). (FR-5, #445)
10057
+ * `sourceTs` is an optional ISO-8601 origin timestamp override;
10058
+ * when supplied together with `source` on a provenance collection,
10059
+ * replaces the machine-stamped `now()` so re-merges preserve the
10060
+ * ORIGIN refresh time across vaults. (FR-4)
10108
10061
  */
10109
10062
  async put(id, record, options) {
10110
10063
  await this.schemaUpdateGate?.assertWritable();
@@ -10136,6 +10089,20 @@ var init_collection = __esm({
10136
10089
  if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
10137
10090
  }
10138
10091
  }
10092
+ /**
10093
+ * Validate a record against this collection's schema WITHOUT writing it.
10094
+ * Returns the (possibly coerced) record on success; throws
10095
+ * {@link SchemaValidationError} (direction: `'input'`) on violation.
10096
+ * A no-op pass-through when no schema is declared.
10097
+ *
10098
+ * Used by FR-8 migrate-then-merge to pre-validate all staged records
10099
+ * before `mergeDecryptedRecords` writes anything — so a failed upgrade
10100
+ * never half-writes the receiver.
10101
+ */
10102
+ async validateInput(record) {
10103
+ if (this.schema === void 0) return record;
10104
+ return validateSchemaInput(this.schema, record, `validateInput(${this.name})`);
10105
+ }
10139
10106
  /** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
10140
10107
  #hooksActive() {
10141
10108
  return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
@@ -10293,7 +10260,7 @@ var init_collection = __esm({
10293
10260
  }
10294
10261
  const version2 = existingVersion + 1;
10295
10262
  const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
10296
- const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
10263
+ const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2, options?.source, options?.sourceTs);
10297
10264
  await this.adapter.put(this.vault, this.name, id, envelope2);
10298
10265
  const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
10299
10266
  const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
@@ -10372,7 +10339,7 @@ var init_collection = __esm({
10372
10339
  });
10373
10340
  }
10374
10341
  }
10375
- const envelope = await this.encryptRecord(record, version, cek);
10342
+ const envelope = await this.encryptRecord(record, version, cek, options?.source, options?.sourceTs);
10376
10343
  await this.adapter.put(this.vault, this.name, id, envelope);
10377
10344
  if (this.ledger) {
10378
10345
  const appendInput = {
@@ -10665,7 +10632,7 @@ var init_collection = __esm({
10665
10632
  priorEnvelope
10666
10633
  });
10667
10634
  }
10668
- await outputCollection.put(entry.key, entry.value);
10635
+ await outputCollection.put(entry.key, entry.value, { source: "derived" });
10669
10636
  }
10670
10637
  await saveFanoutSidecar2(this.adapter, this.vault, {
10671
10638
  source: spec.source,
@@ -10698,7 +10665,7 @@ var init_collection = __esm({
10698
10665
  priorEnvelope: prior
10699
10666
  });
10700
10667
  }
10701
- await outputCollection.put(run.runId, patched);
10668
+ await outputCollection.put(run.runId, patched, { source: "derived" });
10702
10669
  continue;
10703
10670
  }
10704
10671
  if (txCtx !== null) {
@@ -10713,7 +10680,7 @@ var init_collection = __esm({
10713
10680
  priorEnvelope: prior
10714
10681
  });
10715
10682
  }
10716
- await outputCollection.put(run.runId, out.value);
10683
+ await outputCollection.put(run.runId, out.value, { source: "derived" });
10717
10684
  }
10718
10685
  }
10719
10686
  }
@@ -12376,7 +12343,7 @@ var init_collection = __esm({
12376
12343
  * (see {@link encryptRecord}). Rejects `_`-prefixed record fields, which
12377
12344
  * would collide with the reserved metadata namespace.
12378
12345
  */
12379
- buildDebugEnvelope(record, version) {
12346
+ buildDebugEnvelope(record, version, source, sourceTs) {
12380
12347
  const rec = record;
12381
12348
  for (const key of Object.keys(rec)) {
12382
12349
  if (key.startsWith("_")) throw new DebugReservedFieldError(this.name, key);
@@ -12389,11 +12356,13 @@ var init_collection = __esm({
12389
12356
  _data: "",
12390
12357
  _by: this.keyring.userId,
12391
12358
  _debug: NOYDB_FORMAT_VERSION,
12359
+ ...this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {},
12392
12360
  ...rec
12393
12361
  };
12394
12362
  }
12395
- async encryptJsonString(json, version, cek) {
12363
+ async encryptJsonString(json, version, cek, source, sourceTs) {
12396
12364
  const by = this.keyring.userId;
12365
+ const provenanceFields = this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {};
12397
12366
  if (!this.encrypted) {
12398
12367
  return {
12399
12368
  _noydb: NOYDB_FORMAT_VERSION,
@@ -12401,7 +12370,8 @@ var init_collection = __esm({
12401
12370
  _ts: (/* @__PURE__ */ new Date()).toISOString(),
12402
12371
  _iv: "",
12403
12372
  _data: json,
12404
- _by: by
12373
+ _by: by,
12374
+ ...provenanceFields
12405
12375
  };
12406
12376
  }
12407
12377
  const dek = await this.getDEK(this.name);
@@ -12415,7 +12385,8 @@ var init_collection = __esm({
12415
12385
  _iv: iv2,
12416
12386
  _data: data2,
12417
12387
  _by: by,
12418
- _cek: wrapped
12388
+ _cek: wrapped,
12389
+ ...provenanceFields
12419
12390
  };
12420
12391
  }
12421
12392
  const { iv, data } = await encrypt(json, dek);
@@ -12425,14 +12396,15 @@ var init_collection = __esm({
12425
12396
  _ts: (/* @__PURE__ */ new Date()).toISOString(),
12426
12397
  _iv: iv,
12427
12398
  _data: data,
12428
- _by: by
12399
+ _by: by,
12400
+ ...provenanceFields
12429
12401
  };
12430
12402
  }
12431
- async encryptRecord(record, version, cek) {
12403
+ async encryptRecord(record, version, cek, source, sourceTs) {
12432
12404
  if (!this.encrypted && this.keyring.debugPlaintext === true && !this.name.startsWith("_")) {
12433
- return this.buildDebugEnvelope(record, version);
12405
+ return this.buildDebugEnvelope(record, version, source, sourceTs);
12434
12406
  }
12435
- const base = await this.encryptJsonString(JSON.stringify(record), version, cek);
12407
+ const base = await this.encryptJsonString(JSON.stringify(record), version, cek, source, sourceTs);
12436
12408
  if (!this.deterministicFields || !this.encrypted) return base;
12437
12409
  const dek = await this.getDEK(this.name);
12438
12410
  const rec = record;
@@ -12566,7 +12538,8 @@ var init_collection = __esm({
12566
12538
  _iv: iv,
12567
12539
  _data: data,
12568
12540
  _by: this.keyring.userId,
12569
- ...tier > 0 && { _tier: tier }
12541
+ ...tier > 0 && { _tier: tier },
12542
+ ...this.provenance && opts?.source !== void 0 ? { _source: opts.source, _sourceTs: opts.sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {}
12570
12543
  };
12571
12544
  await this.adapter.put(this.vault, this.name, id, envelope);
12572
12545
  if (tier > 0) {
@@ -12873,43 +12846,49 @@ function randomId() {
12873
12846
  const b = globalThis.crypto.getRandomValues(new Uint8Array(12));
12874
12847
  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
12875
12848
  }
12876
- async function freezeAndDeleteClosure(vault, collections, opts) {
12849
+ async function freezeSnapshotOnly(vault, collections, opts) {
12877
12850
  const { name: vaultName, adapter } = vault._introspectState();
12878
12851
  const closure = [];
12879
12852
  for (const c of collections) {
12880
12853
  for (const id of await adapter.list(vaultName, c)) closure.push({ collection: c, id });
12881
12854
  }
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
- }
12855
+ const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
12856
+ const snap = {};
12911
12857
  for (const { collection, id } of closure) {
12912
- await vault.collection(collection).delete(id);
12858
+ const env = await adapter.get(vaultName, collection, id);
12859
+ if (env) (snap[collection] ??= {})[id] = env;
12860
+ }
12861
+ const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
12862
+ const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
12863
+ const sha = await sha256Hex2(ENC.encode(body));
12864
+ await adapter.put(
12865
+ vaultName,
12866
+ FROZEN_SNAPSHOTS_COLLECTION,
12867
+ withdrawalId,
12868
+ { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
12869
+ 0
12870
+ );
12871
+ await vault._getLedgerOrNull()?.append({
12872
+ op: "lifecycle",
12873
+ collection: "",
12874
+ id: "",
12875
+ version: 0,
12876
+ actor: opts.actorUserId,
12877
+ payloadHash: "",
12878
+ reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
12879
+ });
12880
+ return { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
12881
+ }
12882
+ async function freezeAndDeleteClosure(vault, collections, opts) {
12883
+ const snapshot = opts.disposition === "freeze" ? await freezeSnapshotOnly(vault, collections, {
12884
+ actorUserId: opts.actorUserId,
12885
+ ...opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}
12886
+ }) : void 0;
12887
+ const { name: vaultName, adapter } = vault._introspectState();
12888
+ for (const c of collections) {
12889
+ for (const id of await adapter.list(vaultName, c)) {
12890
+ await vault.collection(c).delete(id);
12891
+ }
12913
12892
  }
12914
12893
  return snapshot;
12915
12894
  }
@@ -12921,6 +12900,11 @@ async function withdrawAccessibleData(vault, opts) {
12921
12900
  "unilateralWithdrawal is the scoped self-service path; an owner/admin should use extractPartition"
12922
12901
  );
12923
12902
  }
12903
+ if (keyring.role === "custodian") {
12904
+ throw new ReadOnlyError(
12905
+ "a custodian cannot destructively withdraw/sever; use vault.custody.liberate for an audited ownership claim"
12906
+ );
12907
+ }
12924
12908
  if (keyring.role === "client" || keyring.role === "viewer") {
12925
12909
  throw new ReadOnlyError(
12926
12910
  "read-only role cannot self-serve a destructive withdrawal \u2014 use requestWithdrawal (two-party)"
@@ -14819,6 +14803,157 @@ var init_api = __esm({
14819
14803
  }
14820
14804
  });
14821
14805
 
14806
+ // src/custody/index.ts
14807
+ var CustodyApi;
14808
+ var init_custody = __esm({
14809
+ "src/custody/index.ts"() {
14810
+ "use strict";
14811
+ CustodyApi = class {
14812
+ constructor(_grantCustodian, _revokeCustodian, _liberate) {
14813
+ this._grantCustodian = _grantCustodian;
14814
+ this._revokeCustodian = _revokeCustodian;
14815
+ this._liberate = _liberate;
14816
+ }
14817
+ _grantCustodian;
14818
+ _revokeCustodian;
14819
+ _liberate;
14820
+ /**
14821
+ * Owner-only: grant the FR-6 `custodian` role. The custodian operates every
14822
+ * collection (rw + access) but is provably unable to grant / revoke / rotate /
14823
+ * extract-and-sever. Defended in depth (gate + owner-only role check) inside
14824
+ * the injected `Noydb.grantCustodian`.
14825
+ */
14826
+ async grantCustodian(options, factors) {
14827
+ return this._grantCustodian(options, factors);
14828
+ }
14829
+ /** Owner-only: revoke a custodian. */
14830
+ async revokeCustodian(options, factors) {
14831
+ return this._revokeCustodian(options, factors);
14832
+ }
14833
+ /**
14834
+ * Custodian-only: the audited claim of ownership over a sealed-owner (Deed)
14835
+ * vault. Mints a DISTINCT new owner re-wrapping the incumbent DEKs under a
14836
+ * fresh KEK (the latent owner is never impersonated), ledger-audited. See
14837
+ * {@link liberateVault}.
14838
+ */
14839
+ async liberate(opts) {
14840
+ return this._liberate(opts);
14841
+ }
14842
+ };
14843
+ }
14844
+ });
14845
+
14846
+ // src/team/deed.ts
14847
+ async function loadDeedMarker(store, vault) {
14848
+ const envelope = await store.get(vault, "_meta", DEED_RECORD_ID);
14849
+ if (!envelope) return null;
14850
+ let payload;
14851
+ try {
14852
+ payload = JSON.parse(envelope._data);
14853
+ } catch {
14854
+ return null;
14855
+ }
14856
+ if (typeof payload !== "object" || payload === null) return null;
14857
+ const r = payload;
14858
+ if (r._noydb_deed !== 1) return null;
14859
+ if (typeof r.ownerUserId !== "string" || typeof r.sealedUnder !== "string" || r.latent !== true || typeof r.issuedAt !== "string") {
14860
+ return null;
14861
+ }
14862
+ const marker = {
14863
+ ownerUserId: r.ownerUserId,
14864
+ sealedUnder: r.sealedUnder,
14865
+ latent: true,
14866
+ issuedAt: r.issuedAt,
14867
+ ...typeof r.liberatedAt === "string" ? { liberatedAt: r.liberatedAt } : {}
14868
+ };
14869
+ return marker;
14870
+ }
14871
+ async function saveDeedMarker(store, vault, marker) {
14872
+ const persisted = { _noydb_deed: 1, ...marker };
14873
+ const prior = await store.get(vault, "_meta", DEED_RECORD_ID);
14874
+ const env = {
14875
+ _noydb: NOYDB_FORMAT_VERSION,
14876
+ _v: (prior?._v ?? 0) + 1,
14877
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
14878
+ // AES-GCM bypassed — the marker is plaintext audit metadata.
14879
+ _iv: "",
14880
+ _data: JSON.stringify(persisted)
14881
+ };
14882
+ await store.put(vault, "_meta", DEED_RECORD_ID, env);
14883
+ }
14884
+ var DEED_RECORD_ID;
14885
+ var init_deed = __esm({
14886
+ "src/team/deed.ts"() {
14887
+ "use strict";
14888
+ init_types();
14889
+ DEED_RECORD_ID = "deed";
14890
+ }
14891
+ });
14892
+
14893
+ // src/custody/liberate.ts
14894
+ async function liberateVault(vault, opts) {
14895
+ await vault.noydb.checkGate(vault.name, "liberate-vault", opts.factors);
14896
+ const { name: vaultName, adapter, keyring } = vault._introspectState();
14897
+ if (keyring.role !== "custodian") {
14898
+ throw new PermissionDeniedError(
14899
+ "liberation is claimed only by the custodian (the de-facto authority holding the DEKs)"
14900
+ );
14901
+ }
14902
+ const existing = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
14903
+ if (existing) {
14904
+ throw new PermissionDeniedError(
14905
+ `liberateVault: newOwnerId "${opts.newOwnerId}" already exists as a principal; choose a fresh id (liberation mints a distinct owner, it never overwrites an existing keyring)`
14906
+ );
14907
+ }
14908
+ const collections = await listOperationalCollections(vault);
14909
+ const snapshot = await freezeSnapshotOnly(vault, collections, { actorUserId: keyring.userId });
14910
+ const newOwner = await createOwnerKeyring(adapter, vaultName, opts.newOwnerId, opts.newOwnerPassphrase);
14911
+ if (!newOwner.kek) {
14912
+ throw new PermissionDeniedError(
14913
+ `new owner keyring for "${opts.newOwnerId}" has no KEK to re-wrap the incumbent DEKs under`
14914
+ );
14915
+ }
14916
+ const env = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
14917
+ if (!env) {
14918
+ throw new PermissionDeniedError(`new owner keyring for "${opts.newOwnerId}" did not persist`);
14919
+ }
14920
+ const keyringFile = JSON.parse(env._data);
14921
+ const mergedDeks = { ...keyringFile.deks };
14922
+ for (const [collection, dek] of keyring.deks) {
14923
+ mergedDeks[collection] = await wrapKey(dek, newOwner.kek);
14924
+ }
14925
+ const mergedFile = { ...keyringFile, deks: mergedDeks };
14926
+ await adapter.put(vaultName, "_keyring", opts.newOwnerId, { ...env, _data: JSON.stringify(mergedFile) });
14927
+ await vault._getLedgerOrNull()?.append({
14928
+ op: "lifecycle",
14929
+ collection: "",
14930
+ id: "",
14931
+ version: 0,
14932
+ actor: opts.newOwnerId,
14933
+ payloadHash: "",
14934
+ reason: `liberation-claimed:${opts.newOwnerId}:${opts.legalBasis}`
14935
+ });
14936
+ const marker = await loadDeedMarker(adapter, vaultName);
14937
+ if (marker) {
14938
+ await saveDeedMarker(adapter, vaultName, { ...marker, liberatedAt: (/* @__PURE__ */ new Date()).toISOString() });
14939
+ }
14940
+ return { snapshot };
14941
+ }
14942
+ async function listOperationalCollections(vault) {
14943
+ const { keyring } = vault._introspectState();
14944
+ return [...keyring.deks.keys()].filter((c) => !c.startsWith("_"));
14945
+ }
14946
+ var init_liberate = __esm({
14947
+ "src/custody/liberate.ts"() {
14948
+ "use strict";
14949
+ init_errors();
14950
+ init_crypto();
14951
+ init_keyring();
14952
+ init_withdraw_accessible();
14953
+ init_deed();
14954
+ }
14955
+ });
14956
+
14822
14957
  // src/persisted-schemas/canonicalize.ts
14823
14958
  function canonicalize(value) {
14824
14959
  if (value === null || typeof value !== "object") {
@@ -14868,8 +15003,8 @@ async function derivePersistedSchema(validator) {
14868
15003
  if (kind === "Zod") {
14869
15004
  const convert = await loadZodConverter();
14870
15005
  const jsonSchema = convert(validator);
14871
- const canonical2 = canonicalize(jsonSchema);
14872
- const hash = await sha256Hex2(new TextEncoder().encode(canonical2));
15006
+ const canonical = canonicalize(jsonSchema);
15007
+ const hash = await sha256Hex2(new TextEncoder().encode(canonical));
14873
15008
  return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt };
14874
15009
  }
14875
15010
  return {
@@ -15912,7 +16047,7 @@ var init_read_only_facade = __esm({
15912
16047
 
15913
16048
  // src/derivations/strategy-hash.ts
15914
16049
  async function computeStrategyHash(source, outputKeys, derive, sources) {
15915
- const canonical2 = JSON.stringify({
16050
+ const canonical = JSON.stringify({
15916
16051
  source,
15917
16052
  outputs: [...outputKeys].sort(),
15918
16053
  derive: derive.toString(),
@@ -15921,7 +16056,7 @@ async function computeStrategyHash(source, outputKeys, derive, sources) {
15921
16056
  // so strategies without siblings keep their existing hash.
15922
16057
  ...sources?.length ? { sources: [...sources].sort() } : {}
15923
16058
  });
15924
- const bytes = new TextEncoder().encode(canonical2);
16059
+ const bytes = new TextEncoder().encode(canonical);
15925
16060
  const digest = await crypto.subtle.digest("SHA-256", bytes);
15926
16061
  return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
15927
16062
  }
@@ -16254,6 +16389,8 @@ var init_vault = __esm({
16254
16389
  init_blob_compaction();
16255
16390
  init_magic_link_grant();
16256
16391
  init_api();
16392
+ init_custody();
16393
+ init_liberate();
16257
16394
  init_register();
16258
16395
  init_gate();
16259
16396
  init_fence_controller();
@@ -16361,6 +16498,18 @@ var init_vault = __esm({
16361
16498
  * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
16362
16499
  */
16363
16500
  user;
16501
+ /**
16502
+ * FR-6 custody API — the sovereign-custody surface, mirroring `vault.user.*`.
16503
+ *
16504
+ * - `grantCustodian(opts)` / `revokeCustodian(opts)` — owner-only: mint /
16505
+ * remove a `custodian` who operates the vault fully but can never grant /
16506
+ * rotate / sever / extract.
16507
+ * - `liberate(opts)` — custodian-only: the audited claim of ownership over a
16508
+ * sealed-owner (Deed) vault (mints a DISTINCT new owner; ledger-audited).
16509
+ *
16510
+ * @see docs/superpowers/specs/2026-06-17-fr6-deed-custodian-liberate-design.md
16511
+ */
16512
+ custody;
16364
16513
  /**
16365
16514
  * Optional callback that re-derives an UnlockedKeyring from the
16366
16515
  * adapter using the active user's passphrase. Called by `load()`
@@ -16571,6 +16720,11 @@ var init_vault = __esm({
16571
16720
  (requestId, opts2) => approveWithdrawal(this, requestId, opts2),
16572
16721
  (requestId, opts2) => rejectWithdrawal(this, requestId, opts2)
16573
16722
  );
16723
+ this.custody = new CustodyApi(
16724
+ (options, factors) => this.noydb.grantCustodian(this.name, options, factors),
16725
+ (options, factors) => this.noydb.revokeCustodian(this.name, options, factors),
16726
+ (opts2) => liberateVault(this, opts2)
16727
+ );
16574
16728
  }
16575
16729
  /**
16576
16730
  * Construct (or reconstruct) the lazy DEK resolver. Captures the
@@ -16798,6 +16952,7 @@ var init_vault = __esm({
16798
16952
  }
16799
16953
  collOpts.perRecordKeys = true;
16800
16954
  }
16955
+ if (options?.provenance !== void 0) collOpts.provenance = options.provenance;
16801
16956
  if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
16802
16957
  if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
16803
16958
  collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
@@ -18620,7 +18775,7 @@ var init_vault = __esm({
18620
18775
  if (this.activeElevation) {
18621
18776
  throw new AlreadyElevatedError(this.activeElevation.tier);
18622
18777
  }
18623
- if (this.keyring.role !== "owner" && this.keyring.role !== "admin") {
18778
+ if (this.keyring.role !== "owner" && this.keyring.role !== "admin" && this.keyring.role !== "custodian") {
18624
18779
  const suffix = `#${tier}`;
18625
18780
  let found = false;
18626
18781
  for (const k of this.keyring.deks.keys()) {
@@ -20822,997 +20977,6 @@ var init_executor3 = __esm({
20822
20977
  }
20823
20978
  });
20824
20979
 
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
20980
  // src/noydb.ts
21817
20981
  var noydb_exports = {};
21818
20982
  __export(noydb_exports, {
@@ -21873,7 +21037,6 @@ var init_noydb = __esm({
21873
21037
  "src/noydb.ts"() {
21874
21038
  "use strict";
21875
21039
  init_errors();
21876
- init_constants2();
21877
21040
  init_storage3();
21878
21041
  init_rotate_recover();
21879
21042
  init_peer_recover();
@@ -21907,6 +21070,12 @@ var init_noydb = __esm({
21907
21070
  client: 1,
21908
21071
  viewer: 2,
21909
21072
  operator: 3,
21073
+ // FR-6: custodian is operationally admin-rank (rw + access on every
21074
+ // collection) — it ranks alongside admin for "how much can this
21075
+ // principal see/operate." It is NOT above admin, and explicitly below
21076
+ // owner: a custodian can never grant/revoke/rotate/sever (those are
21077
+ // owner meta-capabilities), so it must not outrank or equal the owner.
21078
+ custodian: 4,
21910
21079
  admin: 4,
21911
21080
  owner: 5
21912
21081
  };
@@ -21955,7 +21124,6 @@ var init_noydb = __esm({
21955
21124
  writeRelay;
21956
21125
  /** Per-vault policy enforcers. */
21957
21126
  policyEnforcers = /* @__PURE__ */ new Map();
21958
- vaultTemplates = /* @__PURE__ */ new Map();
21959
21127
  txStrategy;
21960
21128
  forgetStrategy;
21961
21129
  sessionStrategy;
@@ -22405,6 +21573,37 @@ var init_noydb = __esm({
22405
21573
  const keyring = await this.getKeyringInternal(vault);
22406
21574
  await revoke(this.options.store, vault, keyring, options);
22407
21575
  }
21576
+ /**
21577
+ * Grant the FR-6 `custodian` role to a user (owner-only custody API).
21578
+ *
21579
+ * A custodian operates every collection (rw + access) but is provably
21580
+ * unable to grant / revoke / rotate / extract-and-sever. Only the Deed
21581
+ * owner may mint one. Defended in depth: the `grant-custodian` gate
21582
+ * (fail-closed) AND an explicit `keyring.role !== 'owner'` check — the
21583
+ * gate enforces host policy, the role check enforces the cryptographic
21584
+ * owner-only invariant even if a host mis-configures the gate.
21585
+ */
21586
+ async grantCustodian(vault, options, factors) {
21587
+ this.checkPolicyOperation(vault, "grant");
21588
+ await this.checkGate(vault, "grant-custodian", factors);
21589
+ const keyring = await this.getKeyringInternal(vault);
21590
+ if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can grant a custodian");
21591
+ await grant(this.options.store, vault, keyring, { ...options, role: "custodian" });
21592
+ }
21593
+ /**
21594
+ * Revoke a custodian (owner-only custody API).
21595
+ *
21596
+ * Mirrors {@link revoke} but pins the caller to the Deed owner: defended
21597
+ * in depth by the `revoke-user` gate AND an explicit `keyring.role !==
21598
+ * 'owner'` check, so an admin cannot unwind a custodianship.
21599
+ */
21600
+ async revokeCustodian(vault, options, factors) {
21601
+ this.checkPolicyOperation(vault, "revoke");
21602
+ await this.checkGate(vault, "revoke-user", factors);
21603
+ const keyring = await this.getKeyringInternal(vault);
21604
+ if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can revoke a custodian");
21605
+ await revoke(this.options.store, vault, keyring, options);
21606
+ }
22408
21607
  /**
22409
21608
  * Mutate post-grant identity fields on an existing keyring — `role`,
22410
21609
  * `displayName`, and/or `permissions`. Pure plaintext-header rewrite:
@@ -22674,52 +21873,12 @@ var init_noydb = __esm({
22674
21873
  return results;
22675
21874
  }
22676
21875
  /**
22677
- * Register a shard schema blueprint. `createShard` / `openVaultGroup`
22678
- * stamp shards from the named template. See the MVF design spec.
22679
- */
22680
- withVaultTemplate(name, template) {
22681
- this.vaultTemplates.set(name, template);
22682
- }
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;
22713
- }
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.
21876
+ * @internal True once `close()` has been called. Read by outward
21877
+ * orchestration frameworks whose entry points can't see the private
21878
+ * `closed` field.
22718
21879
  */
22719
- 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);
21880
+ get isClosed() {
21881
+ return this.closed;
22723
21882
  }
22724
21883
  /**
22725
21884
  * @internal — true when an encrypted shard vault is provisioned
@@ -24215,6 +23374,7 @@ __export(bundle_exports, {
24215
23374
  TransferSealError: () => TransferSealError,
24216
23375
  adoptPartition: () => adoptPartition,
24217
23376
  createOwnerOnAdoptedPartition: () => createOwnerOnAdoptedPartition,
23377
+ decryptExtractedPartition: () => decryptExtractedPartition,
24218
23378
  describeExtraction: () => describeExtraction,
24219
23379
  encodeBundleHeader: () => encodeBundleHeader,
24220
23380
  extractPartition: () => extractPartition,
@@ -24388,7 +23548,7 @@ init_constants();
24388
23548
  init_entry();
24389
23549
  init_hash();
24390
23550
  init_bundle();
24391
- async function reKeyClosure(vault, closure) {
23551
+ async function reKeyClosure(vault, closure, fieldProjection) {
24392
23552
  const { name: vaultName, adapter, getDEK } = vault._introspectState();
24393
23553
  const collections = {};
24394
23554
  const deks = /* @__PURE__ */ new Map();
@@ -24397,29 +23557,40 @@ async function reKeyClosure(vault, closure) {
24397
23557
  const destDek = await generateDEK();
24398
23558
  deks.set(collectionName, destDek);
24399
23559
  const out = {};
23560
+ const projList = fieldProjection?.[collectionName];
23561
+ const proj = projList ? new Set(projList) : void 0;
23562
+ const project = (plaintext) => {
23563
+ if (!proj) return plaintext;
23564
+ const rec = JSON.parse(plaintext);
23565
+ const kept = {};
23566
+ if ("id" in rec) kept["id"] = rec["id"];
23567
+ for (const f of proj) if (f in rec) kept[f] = rec[f];
23568
+ return JSON.stringify(kept);
23569
+ };
24400
23570
  for (const id of ids) {
24401
23571
  const env = await adapter.get(vaultName, collectionName, id);
24402
23572
  if (!env) continue;
24403
23573
  if (env._cek !== void 0) {
24404
23574
  const cek = await unwrapCek(env._cek, srcDek);
24405
23575
  const plaintext2 = await decrypt(env._iv, env._data, cek);
24406
- const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
23576
+ const { iv: iv2, data: data2 } = await encrypt(project(plaintext2), cek);
24407
23577
  const wrapped = await wrapCek(cek, destDek);
24408
23578
  out[id] = { ...env, _iv: iv2, _data: data2, _cek: wrapped };
24409
23579
  continue;
24410
23580
  }
24411
23581
  const plaintext = await decrypt(env._iv, env._data, srcDek);
24412
- const { iv, data } = await encrypt(plaintext, destDek);
23582
+ const { iv, data } = await encrypt(project(plaintext), destDek);
24413
23583
  out[id] = { ...env, _iv: iv, _data: data };
24414
23584
  }
24415
23585
  collections[collectionName] = out;
24416
23586
  }
24417
23587
  return { collections, deks };
24418
23588
  }
24419
- async function reKeySchemas(vault, closure, destDeks) {
23589
+ async function reKeySchemas(vault, closure, destDeks, fieldProjection) {
24420
23590
  const { name: vaultName, adapter, getDEK } = vault._introspectState();
24421
23591
  const out = {};
24422
23592
  for (const collectionName of closure.keys()) {
23593
+ if (fieldProjection?.[collectionName]) continue;
24423
23594
  const env = await adapter.get(vaultName, SCHEMAS_COLLECTION, collectionName);
24424
23595
  if (!env) continue;
24425
23596
  const destDek = destDeks.get(collectionName);
@@ -24511,6 +23682,11 @@ async function sealDeks(deks) {
24511
23682
  };
24512
23683
  }
24513
23684
  async function extractPartition(vault, opts) {
23685
+ if (vault.role === "custodian") {
23686
+ throw new PartitionExtractionError(
23687
+ "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)."
23688
+ );
23689
+ }
24514
23690
  if (vault.role !== "owner") {
24515
23691
  throw new PartitionExtractionError(
24516
23692
  `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 +23694,7 @@ async function extractPartition(vault, opts) {
24518
23694
  }
24519
23695
  if (opts.carrySchemas) await vault._drainPendingSchemaWrites();
24520
23696
  const { closure } = await walkClosure(vault, opts);
24521
- const { collections, deks } = await reKeyClosure(vault, closure);
23697
+ const { collections, deks } = await reKeyClosure(vault, closure, opts.fieldProjection);
24522
23698
  let ledgerHead;
24523
23699
  let ledgerEntries;
24524
23700
  if (opts.carryLedger && vault._getLedgerOrNull() !== null) {
@@ -24530,7 +23706,7 @@ async function extractPartition(vault, opts) {
24530
23706
  deks.set(LEDGER_COLLECTION, ledgerDek);
24531
23707
  }
24532
23708
  }
24533
- const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks) : {};
23709
+ const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks, opts.fieldProjection) : {};
24534
23710
  const internal = {};
24535
23711
  if (Object.keys(internalSchemas).length > 0) internal[SCHEMAS_COLLECTION] = internalSchemas;
24536
23712
  if (ledgerEntries) internal[LEDGER_COLLECTION] = ledgerEntries;
@@ -24745,6 +23921,41 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
24745
23921
  return { vaultName, userId };
24746
23922
  }
24747
23923
 
23924
+ // src/bundle/decrypt-partition.ts
23925
+ init_crypto();
23926
+ init_record_keys();
23927
+ init_bundle();
23928
+ async function decryptExtractedPartition(bundleBytes, transferKey) {
23929
+ const header = readNoydbBundleHeader(bundleBytes);
23930
+ if (header.bundleKind !== "extracted-partition" || header.transferSeal === void 0) {
23931
+ throw new Error("decryptExtractedPartition: bundle is not an extracted-partition.");
23932
+ }
23933
+ const { dumpJson } = await readNoydbBundle(bundleBytes);
23934
+ const { dump, seal } = parseExtractedPartitionBody(dumpJson);
23935
+ const deks = await unsealDeks(seal, transferKey);
23936
+ const backup = JSON.parse(dump);
23937
+ const out = {};
23938
+ for (const [collection, byId] of Object.entries(backup.collections)) {
23939
+ const dek = deks.get(collection);
23940
+ if (dek === void 0) continue;
23941
+ const recs = [];
23942
+ for (const [id, env] of Object.entries(byId)) {
23943
+ const plaintext = env._cek !== void 0 ? await decrypt(env._iv, env._data, await unwrapCek(env._cek, dek)) : await decrypt(env._iv, env._data, dek);
23944
+ const body = JSON.parse(plaintext);
23945
+ recs.push({
23946
+ id,
23947
+ record: { ...body, id },
23948
+ ts: env._ts,
23949
+ version: env._v,
23950
+ ...env._source !== void 0 ? { source: env._source } : {},
23951
+ ...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
23952
+ });
23953
+ }
23954
+ out[collection] = recs;
23955
+ }
23956
+ return out;
23957
+ }
23958
+
24748
23959
  // src/bundle/index.ts
24749
23960
  init_errors();
24750
23961
  init_errors();
@@ -24768,6 +23979,7 @@ init_errors();
24768
23979
  TransferSealError,
24769
23980
  adoptPartition,
24770
23981
  createOwnerOnAdoptedPartition,
23982
+ decryptExtractedPartition,
24771
23983
  describeExtraction,
24772
23984
  encodeBundleHeader,
24773
23985
  extractPartition,