@noy-db/hub 0.6.0-pre.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/CHANGELOG.md +7079 -0
  2. package/codemods/0.6.0-pre.json +211 -0
  3. package/dist/api-6X2VLZ7J.js +20 -0
  4. package/dist/attestation/index.js +19 -19
  5. package/dist/{backup-KULFZ2T4.js → backup-VL4GRIEW.js} +36 -18
  6. package/dist/backup-VL4GRIEW.js.map +1 -0
  7. package/dist/blobs/index.js +22 -20
  8. package/dist/blobs/index.js.map +1 -1
  9. package/dist/broker/index.js +12 -12
  10. package/dist/cargo/index.js +35 -35
  11. package/dist/{chunk-KUDZVJKQ.js → chunk-22GR3O4J.js} +2 -2
  12. package/dist/{chunk-CCZJJ4I2.js → chunk-2CBFYXJX.js} +6 -6
  13. package/dist/{chunk-JJNOLSG5.js → chunk-2J5IT37K.js} +15 -16
  14. package/dist/chunk-2J5IT37K.js.map +1 -0
  15. package/dist/{chunk-PNKHAKQ7.js → chunk-2ZCWHCLF.js} +17 -10
  16. package/dist/chunk-2ZCWHCLF.js.map +1 -0
  17. package/dist/{chunk-2BMRJ6LQ.js → chunk-2ZT4INZ4.js} +8 -11
  18. package/dist/{chunk-2BMRJ6LQ.js.map → chunk-2ZT4INZ4.js.map} +1 -1
  19. package/dist/{chunk-FAXZQ5UF.js → chunk-323MN67Z.js} +2 -2
  20. package/dist/{chunk-VMCCGUQB.js → chunk-36CEG6CM.js} +2 -2
  21. package/dist/{chunk-PDLLCOGD.js → chunk-3OE2QNSM.js} +20 -17
  22. package/dist/chunk-3OE2QNSM.js.map +1 -0
  23. package/dist/{chunk-AHLJSVRL.js → chunk-3YGUHUJA.js} +5 -7
  24. package/dist/chunk-3YGUHUJA.js.map +1 -0
  25. package/dist/{chunk-ZJADGNYF.js → chunk-4AOERLTV.js} +1 -1
  26. package/dist/chunk-4AOERLTV.js.map +1 -0
  27. package/dist/{chunk-BRVW2GS5.js → chunk-4ATF4BLF.js} +87 -34
  28. package/dist/chunk-4ATF4BLF.js.map +1 -0
  29. package/dist/{chunk-747LRSOO.js → chunk-4K6BIMLF.js} +2 -2
  30. package/dist/{chunk-5O5HRRTV.js → chunk-5ATC6UT4.js} +7 -7
  31. package/dist/{chunk-5O5HRRTV.js.map → chunk-5ATC6UT4.js.map} +1 -1
  32. package/dist/{chunk-4GZ6XOVW.js → chunk-5F7BZAGV.js} +44 -42
  33. package/dist/chunk-5F7BZAGV.js.map +1 -0
  34. package/dist/{chunk-FNSMZQS4.js → chunk-5IHGJHWC.js} +2 -2
  35. package/dist/{chunk-4I72F5DF.js → chunk-5LZZQMIC.js} +11 -14
  36. package/dist/chunk-5LZZQMIC.js.map +1 -0
  37. package/dist/{chunk-HPLWFNG4.js → chunk-5O5ZSW7N.js} +2 -2
  38. package/dist/{chunk-CEJWMBXO.js → chunk-5OT4G5N5.js} +6 -2
  39. package/dist/chunk-5OT4G5N5.js.map +1 -0
  40. package/dist/{chunk-GAG7QOVO.js → chunk-64WHKBMP.js} +53 -18
  41. package/dist/chunk-64WHKBMP.js.map +1 -0
  42. package/dist/{chunk-7TSYBD3E.js → chunk-65JXA63J.js} +2 -2
  43. package/dist/{chunk-Z45YRGFO.js → chunk-6NR36UNB.js} +2 -2
  44. package/dist/{chunk-W77KZHCW.js → chunk-6RH7PRTL.js} +16 -17
  45. package/dist/chunk-6RH7PRTL.js.map +1 -0
  46. package/dist/{chunk-3JVXVJ7L.js → chunk-7OBIBPCV.js} +10 -10
  47. package/dist/{chunk-AR2JDFAE.js → chunk-7Q3RHOA5.js} +2 -2
  48. package/dist/chunk-7ZGPQOFV.js +190 -0
  49. package/dist/chunk-7ZGPQOFV.js.map +1 -0
  50. package/dist/{chunk-QULVCV3M.js → chunk-AJGGBXWA.js} +4 -4
  51. package/dist/{chunk-CADUYBQD.js → chunk-ATL3ZVXT.js} +2 -2
  52. package/dist/{chunk-DMQCLZ4Z.js → chunk-ATY3ZASK.js} +2 -2
  53. package/dist/{chunk-4HDYQL3J.js → chunk-B4CRUZPR.js} +2 -2
  54. package/dist/{chunk-RV3ARMPN.js → chunk-BMU2DS3S.js} +30 -21
  55. package/dist/chunk-BMU2DS3S.js.map +1 -0
  56. package/dist/{chunk-6SAFL5YE.js → chunk-BZ3WMOYX.js} +3 -3
  57. package/dist/{chunk-UHXCLPGC.js → chunk-C3XWJCG7.js} +2 -2
  58. package/dist/chunk-C3XWJCG7.js.map +1 -0
  59. package/dist/{chunk-HXGUIPQB.js → chunk-CKKN25P2.js} +3 -3
  60. package/dist/{chunk-3URQC7EH.js → chunk-CXPOG3ZY.js} +355 -132
  61. package/dist/chunk-CXPOG3ZY.js.map +1 -0
  62. package/dist/{chunk-3FSX6LDG.js → chunk-D2YFQ7XG.js} +18 -3
  63. package/dist/{chunk-3FSX6LDG.js.map → chunk-D2YFQ7XG.js.map} +1 -1
  64. package/dist/{chunk-52JPHN5N.js → chunk-D6SXRMZZ.js} +27 -149
  65. package/dist/chunk-D6SXRMZZ.js.map +1 -0
  66. package/dist/{chunk-2DHSSLOD.js → chunk-DECNV4QK.js} +13 -15
  67. package/dist/chunk-DECNV4QK.js.map +1 -0
  68. package/dist/{chunk-6BU3L4UE.js → chunk-DNKVKO7K.js} +7 -10
  69. package/dist/chunk-DNKVKO7K.js.map +1 -0
  70. package/dist/{chunk-6HFM7EIT.js → chunk-EHRIDNZX.js} +2 -2
  71. package/dist/{chunk-3737A6EA.js → chunk-EHW7NLGD.js} +127 -31
  72. package/dist/chunk-EHW7NLGD.js.map +1 -0
  73. package/dist/{chunk-BEFVYAR5.js → chunk-EQOTJL76.js} +4 -4
  74. package/dist/chunk-EQOTJL76.js.map +1 -0
  75. package/dist/{chunk-E6427JU2.js → chunk-EV2TDHQZ.js} +19 -3
  76. package/dist/chunk-EV2TDHQZ.js.map +1 -0
  77. package/dist/{chunk-WFUIE565.js → chunk-FNYCT27B.js} +17 -14
  78. package/dist/chunk-FNYCT27B.js.map +1 -0
  79. package/dist/{chunk-RTAMLDWP.js → chunk-FU5VYEYK.js} +2 -2
  80. package/dist/chunk-FX3UIDCS.js +66 -0
  81. package/dist/chunk-FX3UIDCS.js.map +1 -0
  82. package/dist/{chunk-7F4DBNSP.js → chunk-GANFIM2M.js} +135 -18
  83. package/dist/chunk-GANFIM2M.js.map +1 -0
  84. package/dist/{chunk-4VUPLMZ6.js → chunk-GCU42IG7.js} +66 -71
  85. package/dist/chunk-GCU42IG7.js.map +1 -0
  86. package/dist/{chunk-RXYYFSBB.js → chunk-GKTW3KLV.js} +4 -4
  87. package/dist/chunk-HK67PX4J.js +27 -0
  88. package/dist/chunk-HK67PX4J.js.map +1 -0
  89. package/dist/{chunk-OF3L2SD6.js → chunk-HOPNIR5R.js} +2 -2
  90. package/dist/{chunk-WLZ47GSL.js → chunk-HSJBJIB2.js} +12 -10
  91. package/dist/{chunk-WLZ47GSL.js.map → chunk-HSJBJIB2.js.map} +1 -1
  92. package/dist/{chunk-ADW22QSB.js → chunk-HWI7GHJZ.js} +4 -4
  93. package/dist/{chunk-GQ3BX5EA.js → chunk-I3ISCYT7.js} +2 -2
  94. package/dist/{chunk-YJ6IONGA.js → chunk-IWKCJCLP.js} +11 -3
  95. package/dist/{chunk-YJ6IONGA.js.map → chunk-IWKCJCLP.js.map} +1 -1
  96. package/dist/{chunk-NJDQ4YSV.js → chunk-IWQPYT6S.js} +4 -4
  97. package/dist/{chunk-JRKZTEZZ.js → chunk-J6I5VR6P.js} +444 -73
  98. package/dist/chunk-J6I5VR6P.js.map +1 -0
  99. package/dist/{chunk-2Z3NCLNX.js → chunk-JHYKSMVX.js} +7 -7
  100. package/dist/{chunk-6FJWSWFT.js → chunk-JIHWBKMQ.js} +3 -3
  101. package/dist/{chunk-YAWK6AWF.js → chunk-JKATP6OE.js} +2 -2
  102. package/dist/{chunk-PL2GHSCM.js → chunk-JMJSCI7U.js} +157 -90
  103. package/dist/chunk-JMJSCI7U.js.map +1 -0
  104. package/dist/{chunk-Q6TEJXBX.js → chunk-JXHNUYQO.js} +31 -49
  105. package/dist/chunk-JXHNUYQO.js.map +1 -0
  106. package/dist/{chunk-2GAJGUNM.js → chunk-JYPRZWCZ.js} +343 -229
  107. package/dist/chunk-JYPRZWCZ.js.map +1 -0
  108. package/dist/{chunk-5ZKS6ZE2.js → chunk-K6CPIK3R.js} +4 -4
  109. package/dist/chunk-K6CPIK3R.js.map +1 -0
  110. package/dist/{chunk-CDB2HVQW.js → chunk-KM6ZXG7U.js} +14 -15
  111. package/dist/chunk-KM6ZXG7U.js.map +1 -0
  112. package/dist/{chunk-DG27IUVI.js → chunk-LC6NE4IQ.js} +2 -2
  113. package/dist/{chunk-WASHZV34.js → chunk-LIR2DJWP.js} +63 -25
  114. package/dist/chunk-LIR2DJWP.js.map +1 -0
  115. package/dist/{chunk-VZ3XFUQ7.js → chunk-LUYPYCHY.js} +16 -22
  116. package/dist/chunk-LUYPYCHY.js.map +1 -0
  117. package/dist/{chunk-G6S5VVHP.js → chunk-M7WBE3OR.js} +10 -10
  118. package/dist/chunk-M7WBE3OR.js.map +1 -0
  119. package/dist/{chunk-VJFMXYKP.js → chunk-MPKP6L5E.js} +2 -2
  120. package/dist/{chunk-IGJCYUT5.js → chunk-NSITFOCJ.js} +1 -1
  121. package/dist/chunk-NSITFOCJ.js.map +1 -0
  122. package/dist/{chunk-FUHKW252.js → chunk-OCJ3JA4O.js} +2 -2
  123. package/dist/{chunk-VCETL77O.js → chunk-ONPVHX2Y.js} +36 -7
  124. package/dist/chunk-ONPVHX2Y.js.map +1 -0
  125. package/dist/{chunk-WLYIROCJ.js → chunk-OQMCFHUX.js} +2 -2
  126. package/dist/{chunk-JRCYZEHU.js → chunk-OR2FYRYR.js} +2 -2
  127. package/dist/{chunk-HNHCQ556.js → chunk-PFWE7FJG.js} +2 -2
  128. package/dist/{chunk-NPP5R7FF.js → chunk-PJX72ICF.js} +22 -18
  129. package/dist/chunk-PJX72ICF.js.map +1 -0
  130. package/dist/{chunk-U5O6GIWZ.js → chunk-POFYIGQ2.js} +3 -3
  131. package/dist/{chunk-35EBKWQU.js → chunk-QH47SQLD.js} +4 -4
  132. package/dist/{chunk-FZ6NYJ7L.js → chunk-QQRRKO7W.js} +11 -8
  133. package/dist/chunk-QQRRKO7W.js.map +1 -0
  134. package/dist/{chunk-HVQGCKY4.js → chunk-QVXZFJBU.js} +2 -2
  135. package/dist/{chunk-FMQJZH4R.js → chunk-RDB66EKD.js} +6 -6
  136. package/dist/{chunk-X56RTUK3.js → chunk-RJZQ2PVD.js} +3 -3
  137. package/dist/{chunk-SUFG5E73.js → chunk-RPRASGEY.js} +2 -2
  138. package/dist/{chunk-XWX7GL56.js → chunk-S2I5MYSA.js} +7 -10
  139. package/dist/chunk-S2I5MYSA.js.map +1 -0
  140. package/dist/{chunk-FFDNKRCY.js → chunk-S6PJ6ZKE.js} +2 -2
  141. package/dist/chunk-S6PJ6ZKE.js.map +1 -0
  142. package/dist/{chunk-JIUVJ4M7.js → chunk-S7PM7Z4K.js} +3 -3
  143. package/dist/{chunk-55LKVALY.js → chunk-T7IR4N2S.js} +1 -3
  144. package/dist/chunk-T7IR4N2S.js.map +1 -0
  145. package/dist/{chunk-N35NC4JH.js → chunk-TKBB62DN.js} +2 -2
  146. package/dist/{chunk-YPYHVOXP.js → chunk-UJ4Z67UQ.js} +2 -2
  147. package/dist/chunk-UJ4Z67UQ.js.map +1 -0
  148. package/dist/{chunk-7MNBICB5.js → chunk-W3HAIN6U.js} +16 -14
  149. package/dist/chunk-W3HAIN6U.js.map +1 -0
  150. package/dist/{chunk-4UVDI4XE.js → chunk-WPZ5FVAS.js} +4 -4
  151. package/dist/{chunk-MSWYQLMQ.js → chunk-XINT4VJA.js} +2 -2
  152. package/dist/{chunk-7G6VDWXB.js → chunk-XPEPMK4Y.js} +3 -3
  153. package/dist/chunk-XPEPMK4Y.js.map +1 -0
  154. package/dist/{chunk-W3TVWF6N.js → chunk-YCGLAANM.js} +2 -2
  155. package/dist/{chunk-O2ERS4UJ.js → chunk-YTUFV7U7.js} +80 -51
  156. package/dist/chunk-YTUFV7U7.js.map +1 -0
  157. package/dist/{chunk-FDX7OWWI.js → chunk-YYV53EHO.js} +2 -2
  158. package/dist/{chunk-HNGSV2YC.js → chunk-Z3BZXPRO.js} +67 -10
  159. package/dist/chunk-Z3BZXPRO.js.map +1 -0
  160. package/dist/{chunk-6NVPFKN3.js → chunk-ZIMQDXTQ.js} +92 -35
  161. package/dist/chunk-ZIMQDXTQ.js.map +1 -0
  162. package/dist/{chunk-5JY5TSVY.js → chunk-ZJUZOJVW.js} +25 -31
  163. package/dist/chunk-ZJUZOJVW.js.map +1 -0
  164. package/dist/{chunk-3IXIU6FP.js → chunk-ZY6P7M52.js} +7 -10
  165. package/dist/chunk-ZY6P7M52.js.map +1 -0
  166. package/dist/{chunk-BWVKEOPE.js → chunk-ZYZJLYNS.js} +7 -11
  167. package/dist/chunk-ZYZJLYNS.js.map +1 -0
  168. package/dist/classified/index.js +5 -5
  169. package/dist/{classified-marker-2KJAXRRO.js → classified-marker-AZDSDG6V.js} +3 -3
  170. package/dist/collection-facade-WSUAYFJ4.js +45 -0
  171. package/dist/{computed-6G5M3UPH.js → computed-VG2PCM4L.js} +3 -3
  172. package/dist/consent/index.js +10 -10
  173. package/dist/cover/index.js +10 -3
  174. package/dist/custody/index.js +12 -11
  175. package/dist/{dead-filter-DCB62VWW.js → dead-filter-JNQNB3TA.js} +2 -2
  176. package/dist/debug/index.js +1 -1
  177. package/dist/{delegation-MO7YZYJS.js → delegation-5F36DRDH.js} +24 -23
  178. package/dist/delegation-5F36DRDH.js.map +1 -0
  179. package/dist/derivations/index.js +12 -12
  180. package/dist/derive-MPOGZ4NR.js +22 -0
  181. package/dist/directory/index.js +10 -10
  182. package/dist/{dispatch-MJ6SHB3Y.js → dispatch-3ZA733EY.js} +18 -18
  183. package/dist/{dispatch-FZEAWKVM.js → dispatch-TNM5CRLZ.js} +9 -6
  184. package/dist/dispatch-TNM5CRLZ.js.map +1 -0
  185. package/dist/{enclave-JU4YIEPB.js → enclave-Y2ALNDRD.js} +31 -9
  186. package/dist/executor-BBEEUFO3.js +9 -0
  187. package/dist/executor-G6G3YEWR.js +9 -0
  188. package/dist/executor-ZUTUMTPA.js +29 -0
  189. package/dist/export-accessible-S3KEB763.js +23 -0
  190. package/dist/extract-partition-KVCTSUVE.js +38 -0
  191. package/dist/{fanout-sidecar-NNAJQXMX.js → fanout-sidecar-3QZU7ZZX.js} +18 -27
  192. package/dist/fanout-sidecar-3QZU7ZZX.js.map +1 -0
  193. package/dist/find-V4FN5ITT.js +11 -0
  194. package/dist/forget/index.js +9 -9
  195. package/dist/guards/index.js +7 -7
  196. package/dist/history/index.js +23 -20
  197. package/dist/history/index.js.map +1 -1
  198. package/dist/i18n/index.js +14 -14
  199. package/dist/index.d.ts +30 -13
  200. package/dist/index.js +180 -188
  201. package/dist/index.js.map +1 -1
  202. package/dist/indexing/index.js +4 -4
  203. package/dist/introspection/index.js +11 -11
  204. package/dist/issue-5CFBWBRX.js +19 -0
  205. package/dist/kernel/collection.d.ts +8 -2
  206. package/dist/kernel/constants.d.ts +36 -0
  207. package/dist/kernel/enclave/crypto.d.ts +52 -4
  208. package/dist/kernel/enclave/index.d.ts +10 -3
  209. package/dist/kernel/enclave/record-aad.d.ts +131 -0
  210. package/dist/kernel/enclave/record-envelope.d.ts +76 -0
  211. package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
  212. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
  213. package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
  214. package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +88 -0
  215. package/dist/kernel/enclave/record-keys/rekey.d.ts +91 -0
  216. package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -2
  217. package/dist/kernel/errors.d.ts +132 -12
  218. package/dist/kernel/merge-authority.d.ts +25 -0
  219. package/dist/kernel/noydb.d.ts +27 -4
  220. package/dist/kernel/query/builder.d.ts +51 -4
  221. package/dist/kernel/query/join.d.ts +27 -0
  222. package/dist/kernel/query/live.d.ts +31 -9
  223. package/dist/kernel/query/predicate.d.ts +9 -0
  224. package/dist/kernel/refs.d.ts +3 -1
  225. package/dist/kernel/sync-policy.d.ts +0 -2
  226. package/dist/kernel/tier-visibility.d.ts +34 -0
  227. package/dist/kernel/types.d.ts +111 -13
  228. package/dist/kernel/vault.d.ts +6 -6
  229. package/dist/kernel/via/index.d.ts +11 -2
  230. package/dist/kernel/via/reconcile.d.ts +12 -0
  231. package/dist/{ledger-AETNF4OM.js → ledger-MQIIOTZB.js} +11 -11
  232. package/dist/liberate-LW7OKVBH.js +25 -0
  233. package/dist/{link-set-E5HNY67I.js → link-set-5PJBBR63.js} +11 -11
  234. package/dist/{managed-secret-CWQ6PND5.js → managed-secret-ETDG6AAE.js} +11 -3
  235. package/dist/materialized-views/index.js +24 -24
  236. package/dist/migrate-cek-V3VDRV24.js +19 -0
  237. package/dist/migrate-cek-V3VDRV24.js.map +1 -0
  238. package/dist/money/index.js +5 -5
  239. package/dist/noydb-LFDRMYZ5.js +85 -0
  240. package/dist/overlay-views/index.js +4 -4
  241. package/dist/periods/index.js +12 -12
  242. package/dist/pod/index.js +79 -89
  243. package/dist/pod-handle-AZ3CXY5Y.js +41 -0
  244. package/dist/pod-handle-AZ3CXY5Y.js.map +1 -0
  245. package/dist/policy/index.js +11 -4
  246. package/dist/port/to/index.d.ts +4 -4
  247. package/dist/port/to/locator.d.ts +54 -8
  248. package/dist/port/with/i18n-strategy.d.ts +4 -1
  249. package/dist/port/with/lookup-strategy.d.ts +4 -1
  250. package/dist/port/with/merge-authority.d.ts +52 -0
  251. package/dist/port/with/service-bus.d.ts +0 -4
  252. package/dist/port/with/strategies.d.ts +2 -0
  253. package/dist/port/with/team-strategy.d.ts +5 -1
  254. package/dist/portability/index.js +8 -8
  255. package/dist/{post-register-2XSSMGEQ.js → post-register-QCJYRMIK.js} +6 -6
  256. package/dist/query/index.js +6 -6
  257. package/dist/reduce/index.js +6 -6
  258. package/dist/register-JBOEHTVX.js +32 -0
  259. package/dist/registry-5DYHQFJS.js +9 -0
  260. package/dist/registry-EANV44W2.js +9 -0
  261. package/dist/registry-J25H6VB4.js +19 -0
  262. package/dist/registry-R23SQUQH.js +18 -0
  263. package/dist/request-withdrawal-KQ4W4UT5.js +31 -0
  264. package/dist/{reveal-BW36KCOQ.js → reveal-W7BKZSUP.js} +6 -6
  265. package/dist/revoke-H3VSTAIE.js +24 -0
  266. package/dist/satellites/index.js +1 -1
  267. package/dist/schema-update/index.js +2 -2
  268. package/dist/sealed-record/index.js +20 -13
  269. package/dist/sealed-record/index.js.map +1 -1
  270. package/dist/search/index.js +4 -4
  271. package/dist/{seed-GDV5PT4R.js → seed-55FPOMYP.js} +20 -22
  272. package/dist/seed-55FPOMYP.js.map +1 -0
  273. package/dist/sequence/index.js +9 -9
  274. package/dist/session/index.js +10 -10
  275. package/dist/shadow/index.js +2 -2
  276. package/dist/share-link/index.js +1 -1
  277. package/dist/signer-53AQ3BU2.js +25 -0
  278. package/dist/snapshots/index.js +10 -10
  279. package/dist/snapshots/index.js.map +1 -1
  280. package/dist/{stale-ARHFVL5U.js → stale-TH7SSVRV.js} +10 -10
  281. package/dist/storage-WSF7ZKTL.js +23 -0
  282. package/dist/{store-coordination-provider-GFCUOBR6.js → store-coordination-provider-R27VRD6K.js} +18 -12
  283. package/dist/store-coordination-provider-R27VRD6K.js.map +1 -0
  284. package/dist/sync/index.js +11 -11
  285. package/dist/team/index.js +15 -14
  286. package/dist/tiers/index.js +11 -10
  287. package/dist/to/index.js +17 -9
  288. package/dist/to/index.js.map +1 -1
  289. package/dist/transactions/index.js +4 -4
  290. package/dist/{ulid-PTAIMDG4.js → ulid-VQW6ERQN.js} +2 -2
  291. package/dist/util/index.js +1 -1
  292. package/dist/vault-head/index.js +115 -0
  293. package/dist/vault-head/index.js.map +1 -0
  294. package/dist/{verify-RO5RTZOJ.js → verify-M5LQH43C.js} +5 -5
  295. package/dist/via/blob/index.d.ts +3 -0
  296. package/dist/via/lookup/handle.d.ts +4 -1
  297. package/dist/walk-NABCHDZN.js +21 -0
  298. package/dist/with-audit/periods/periods.d.ts +1 -1
  299. package/dist/with-audit/periods/strategy.d.ts +1 -1
  300. package/dist/with-audit/sealed-record/index.d.ts +13 -0
  301. package/dist/with-audit/tiers/index.d.ts +17 -2
  302. package/dist/with-cargo/adopt-partition.d.ts +1 -1
  303. package/dist/with-cargo/extract-partition.d.ts +1 -1
  304. package/dist/with-cargo/index.d.ts +1 -1
  305. package/dist/with-commit/history/history.d.ts +26 -0
  306. package/dist/with-commit/history/index.d.ts +2 -1
  307. package/dist/with-commit/history/ledger/store.d.ts +7 -1
  308. package/dist/with-commit/history/strategy.d.ts +11 -1
  309. package/dist/with-commit/numbering/index.d.ts +0 -5
  310. package/dist/with-commit/vault-head/active.d.ts +2 -0
  311. package/dist/with-commit/vault-head/head.d.ts +28 -0
  312. package/dist/with-commit/vault-head/index.d.ts +10 -0
  313. package/dist/with-commit/vault-head/strategy.d.ts +127 -0
  314. package/dist/with-commit/vault-head/verify.d.ts +90 -0
  315. package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -0
  316. package/dist/with-formula/materialized-views/projection-legs.d.ts +59 -0
  317. package/dist/with-formula/materialized-views/registry.d.ts +50 -0
  318. package/dist/with-formula/materialized-views/types.d.ts +68 -10
  319. package/dist/with-party/directory/cover/storage.d.ts +0 -8
  320. package/dist/with-party/directory/storage.d.ts +0 -11
  321. package/dist/with-party/directory/visibility.d.ts +0 -21
  322. package/dist/with-party/policy/storage.d.ts +0 -10
  323. package/dist/with-party/team/index.d.ts +1 -1
  324. package/dist/with-party/team/keyring.d.ts +139 -2
  325. package/dist/with-party/team/noydb-facade.d.ts +5 -1
  326. package/dist/with-party/team/roster-tag.d.ts +101 -0
  327. package/dist/with-party/team/tiers.d.ts +2 -1
  328. package/dist/with-pod/format.d.ts +10 -12
  329. package/dist/with-pod/index.d.ts +10 -10
  330. package/dist/with-pod/open.d.ts +2 -2
  331. package/dist/with-pod/pod-handle.d.ts +19 -0
  332. package/dist/with-pod/pod-store.d.ts +0 -8
  333. package/dist/with-pod/{bundle.d.ts → pod.d.ts} +11 -19
  334. package/dist/with-pod/ulid.d.ts +1 -1
  335. package/dist/with-shape/blobs/blob-compaction.d.ts +13 -0
  336. package/dist/with-shape/blobs/blob-set.d.ts +83 -11
  337. package/dist/with-shape/blobs/orphan-report.d.ts +62 -0
  338. package/dist/with-sync/engine.d.ts +54 -5
  339. package/dist/with-sync/strategy.d.ts +3 -0
  340. package/dist/withdraw-accessible-VCINIG6V.js +28 -0
  341. package/package.json +10 -4
  342. package/dist/api-RXUMEKT3.js +0 -20
  343. package/dist/backup-KULFZ2T4.js.map +0 -1
  344. package/dist/bundle-handle-GFNHT52M.js +0 -36
  345. package/dist/bundle-handle-GFNHT52M.js.map +0 -1
  346. package/dist/chunk-2DHSSLOD.js.map +0 -1
  347. package/dist/chunk-2GAJGUNM.js.map +0 -1
  348. package/dist/chunk-3737A6EA.js.map +0 -1
  349. package/dist/chunk-3IXIU6FP.js.map +0 -1
  350. package/dist/chunk-3URQC7EH.js.map +0 -1
  351. package/dist/chunk-4GZ6XOVW.js.map +0 -1
  352. package/dist/chunk-4I72F5DF.js.map +0 -1
  353. package/dist/chunk-4VUPLMZ6.js.map +0 -1
  354. package/dist/chunk-52JPHN5N.js.map +0 -1
  355. package/dist/chunk-55LKVALY.js.map +0 -1
  356. package/dist/chunk-5JY5TSVY.js.map +0 -1
  357. package/dist/chunk-5ZKS6ZE2.js.map +0 -1
  358. package/dist/chunk-6BU3L4UE.js.map +0 -1
  359. package/dist/chunk-6NVPFKN3.js.map +0 -1
  360. package/dist/chunk-7F4DBNSP.js.map +0 -1
  361. package/dist/chunk-7G6VDWXB.js.map +0 -1
  362. package/dist/chunk-7H73RIE3.js +0 -45
  363. package/dist/chunk-7H73RIE3.js.map +0 -1
  364. package/dist/chunk-7MNBICB5.js.map +0 -1
  365. package/dist/chunk-AFHQATY4.js +0 -47
  366. package/dist/chunk-AFHQATY4.js.map +0 -1
  367. package/dist/chunk-AHLJSVRL.js.map +0 -1
  368. package/dist/chunk-BEFVYAR5.js.map +0 -1
  369. package/dist/chunk-BRVW2GS5.js.map +0 -1
  370. package/dist/chunk-BWVKEOPE.js.map +0 -1
  371. package/dist/chunk-CDB2HVQW.js.map +0 -1
  372. package/dist/chunk-CEJWMBXO.js.map +0 -1
  373. package/dist/chunk-E6427JU2.js.map +0 -1
  374. package/dist/chunk-FFDNKRCY.js.map +0 -1
  375. package/dist/chunk-FZ6NYJ7L.js.map +0 -1
  376. package/dist/chunk-G6S5VVHP.js.map +0 -1
  377. package/dist/chunk-GAG7QOVO.js.map +0 -1
  378. package/dist/chunk-HNGSV2YC.js.map +0 -1
  379. package/dist/chunk-IGJCYUT5.js.map +0 -1
  380. package/dist/chunk-JJNOLSG5.js.map +0 -1
  381. package/dist/chunk-JRKZTEZZ.js.map +0 -1
  382. package/dist/chunk-NPP5R7FF.js.map +0 -1
  383. package/dist/chunk-O2ERS4UJ.js.map +0 -1
  384. package/dist/chunk-PDLLCOGD.js.map +0 -1
  385. package/dist/chunk-PL2GHSCM.js.map +0 -1
  386. package/dist/chunk-PNKHAKQ7.js.map +0 -1
  387. package/dist/chunk-Q6TEJXBX.js.map +0 -1
  388. package/dist/chunk-RV3ARMPN.js.map +0 -1
  389. package/dist/chunk-UHXCLPGC.js.map +0 -1
  390. package/dist/chunk-VCETL77O.js.map +0 -1
  391. package/dist/chunk-VZ3XFUQ7.js.map +0 -1
  392. package/dist/chunk-W77KZHCW.js.map +0 -1
  393. package/dist/chunk-WASHZV34.js.map +0 -1
  394. package/dist/chunk-WFUIE565.js.map +0 -1
  395. package/dist/chunk-XWX7GL56.js.map +0 -1
  396. package/dist/chunk-YPYHVOXP.js.map +0 -1
  397. package/dist/chunk-ZJADGNYF.js.map +0 -1
  398. package/dist/collection-facade-QEJIM6BC.js +0 -45
  399. package/dist/delegation-MO7YZYJS.js.map +0 -1
  400. package/dist/derive-IUA6JFLB.js +0 -22
  401. package/dist/dispatch-FZEAWKVM.js.map +0 -1
  402. package/dist/executor-CYJQLKKI.js +0 -9
  403. package/dist/executor-S4QIKEHW.js +0 -9
  404. package/dist/executor-YIQB2S6N.js +0 -29
  405. package/dist/export-accessible-4S36PQNR.js +0 -23
  406. package/dist/extract-partition-IO7FDZ2P.js +0 -38
  407. package/dist/fanout-sidecar-NNAJQXMX.js.map +0 -1
  408. package/dist/find-RBBYKXI7.js +0 -11
  409. package/dist/issue-RIVHSY2S.js +0 -19
  410. package/dist/liberate-GFN4C7RR.js +0 -24
  411. package/dist/noydb-ASEXNJS6.js +0 -83
  412. package/dist/register-4EFJCVYD.js +0 -32
  413. package/dist/registry-4SDHHOJ7.js +0 -18
  414. package/dist/registry-CZVINVQH.js +0 -9
  415. package/dist/registry-IKUR2MXD.js +0 -9
  416. package/dist/registry-OYTFGWVK.js +0 -19
  417. package/dist/request-withdrawal-KGXGDPVQ.js +0 -31
  418. package/dist/revoke-JFN773KF.js +0 -24
  419. package/dist/seed-GDV5PT4R.js.map +0 -1
  420. package/dist/signer-TA55QB6R.js +0 -25
  421. package/dist/storage-V7MTFAQJ.js +0 -23
  422. package/dist/store-coordination-provider-GFCUOBR6.js.map +0 -1
  423. package/dist/walk-3ZECBFDA.js +0 -21
  424. package/dist/with-pod/bundle-handle.d.ts +0 -9
  425. package/dist/withdraw-accessible-7PG4DYQK.js +0 -28
  426. /package/dist/{api-RXUMEKT3.js.map → api-6X2VLZ7J.js.map} +0 -0
  427. /package/dist/{chunk-KUDZVJKQ.js.map → chunk-22GR3O4J.js.map} +0 -0
  428. /package/dist/{chunk-CCZJJ4I2.js.map → chunk-2CBFYXJX.js.map} +0 -0
  429. /package/dist/{chunk-FAXZQ5UF.js.map → chunk-323MN67Z.js.map} +0 -0
  430. /package/dist/{chunk-VMCCGUQB.js.map → chunk-36CEG6CM.js.map} +0 -0
  431. /package/dist/{chunk-747LRSOO.js.map → chunk-4K6BIMLF.js.map} +0 -0
  432. /package/dist/{chunk-FNSMZQS4.js.map → chunk-5IHGJHWC.js.map} +0 -0
  433. /package/dist/{chunk-HPLWFNG4.js.map → chunk-5O5ZSW7N.js.map} +0 -0
  434. /package/dist/{chunk-7TSYBD3E.js.map → chunk-65JXA63J.js.map} +0 -0
  435. /package/dist/{chunk-Z45YRGFO.js.map → chunk-6NR36UNB.js.map} +0 -0
  436. /package/dist/{chunk-3JVXVJ7L.js.map → chunk-7OBIBPCV.js.map} +0 -0
  437. /package/dist/{chunk-AR2JDFAE.js.map → chunk-7Q3RHOA5.js.map} +0 -0
  438. /package/dist/{chunk-QULVCV3M.js.map → chunk-AJGGBXWA.js.map} +0 -0
  439. /package/dist/{chunk-CADUYBQD.js.map → chunk-ATL3ZVXT.js.map} +0 -0
  440. /package/dist/{chunk-DMQCLZ4Z.js.map → chunk-ATY3ZASK.js.map} +0 -0
  441. /package/dist/{chunk-4HDYQL3J.js.map → chunk-B4CRUZPR.js.map} +0 -0
  442. /package/dist/{chunk-6SAFL5YE.js.map → chunk-BZ3WMOYX.js.map} +0 -0
  443. /package/dist/{chunk-HXGUIPQB.js.map → chunk-CKKN25P2.js.map} +0 -0
  444. /package/dist/{chunk-6HFM7EIT.js.map → chunk-EHRIDNZX.js.map} +0 -0
  445. /package/dist/{chunk-RTAMLDWP.js.map → chunk-FU5VYEYK.js.map} +0 -0
  446. /package/dist/{chunk-RXYYFSBB.js.map → chunk-GKTW3KLV.js.map} +0 -0
  447. /package/dist/{chunk-OF3L2SD6.js.map → chunk-HOPNIR5R.js.map} +0 -0
  448. /package/dist/{chunk-ADW22QSB.js.map → chunk-HWI7GHJZ.js.map} +0 -0
  449. /package/dist/{chunk-GQ3BX5EA.js.map → chunk-I3ISCYT7.js.map} +0 -0
  450. /package/dist/{chunk-NJDQ4YSV.js.map → chunk-IWQPYT6S.js.map} +0 -0
  451. /package/dist/{chunk-2Z3NCLNX.js.map → chunk-JHYKSMVX.js.map} +0 -0
  452. /package/dist/{chunk-6FJWSWFT.js.map → chunk-JIHWBKMQ.js.map} +0 -0
  453. /package/dist/{chunk-YAWK6AWF.js.map → chunk-JKATP6OE.js.map} +0 -0
  454. /package/dist/{chunk-DG27IUVI.js.map → chunk-LC6NE4IQ.js.map} +0 -0
  455. /package/dist/{chunk-VJFMXYKP.js.map → chunk-MPKP6L5E.js.map} +0 -0
  456. /package/dist/{chunk-FUHKW252.js.map → chunk-OCJ3JA4O.js.map} +0 -0
  457. /package/dist/{chunk-WLYIROCJ.js.map → chunk-OQMCFHUX.js.map} +0 -0
  458. /package/dist/{chunk-JRCYZEHU.js.map → chunk-OR2FYRYR.js.map} +0 -0
  459. /package/dist/{chunk-HNHCQ556.js.map → chunk-PFWE7FJG.js.map} +0 -0
  460. /package/dist/{chunk-U5O6GIWZ.js.map → chunk-POFYIGQ2.js.map} +0 -0
  461. /package/dist/{chunk-35EBKWQU.js.map → chunk-QH47SQLD.js.map} +0 -0
  462. /package/dist/{chunk-HVQGCKY4.js.map → chunk-QVXZFJBU.js.map} +0 -0
  463. /package/dist/{chunk-FMQJZH4R.js.map → chunk-RDB66EKD.js.map} +0 -0
  464. /package/dist/{chunk-X56RTUK3.js.map → chunk-RJZQ2PVD.js.map} +0 -0
  465. /package/dist/{chunk-SUFG5E73.js.map → chunk-RPRASGEY.js.map} +0 -0
  466. /package/dist/{chunk-JIUVJ4M7.js.map → chunk-S7PM7Z4K.js.map} +0 -0
  467. /package/dist/{chunk-N35NC4JH.js.map → chunk-TKBB62DN.js.map} +0 -0
  468. /package/dist/{chunk-4UVDI4XE.js.map → chunk-WPZ5FVAS.js.map} +0 -0
  469. /package/dist/{chunk-MSWYQLMQ.js.map → chunk-XINT4VJA.js.map} +0 -0
  470. /package/dist/{chunk-W3TVWF6N.js.map → chunk-YCGLAANM.js.map} +0 -0
  471. /package/dist/{chunk-FDX7OWWI.js.map → chunk-YYV53EHO.js.map} +0 -0
  472. /package/dist/{classified-marker-2KJAXRRO.js.map → classified-marker-AZDSDG6V.js.map} +0 -0
  473. /package/dist/{collection-facade-QEJIM6BC.js.map → collection-facade-WSUAYFJ4.js.map} +0 -0
  474. /package/dist/{computed-6G5M3UPH.js.map → computed-VG2PCM4L.js.map} +0 -0
  475. /package/dist/{dead-filter-DCB62VWW.js.map → dead-filter-JNQNB3TA.js.map} +0 -0
  476. /package/dist/{derive-IUA6JFLB.js.map → derive-MPOGZ4NR.js.map} +0 -0
  477. /package/dist/{dispatch-MJ6SHB3Y.js.map → dispatch-3ZA733EY.js.map} +0 -0
  478. /package/dist/{enclave-JU4YIEPB.js.map → enclave-Y2ALNDRD.js.map} +0 -0
  479. /package/dist/{executor-CYJQLKKI.js.map → executor-BBEEUFO3.js.map} +0 -0
  480. /package/dist/{executor-S4QIKEHW.js.map → executor-G6G3YEWR.js.map} +0 -0
  481. /package/dist/{executor-YIQB2S6N.js.map → executor-ZUTUMTPA.js.map} +0 -0
  482. /package/dist/{export-accessible-4S36PQNR.js.map → export-accessible-S3KEB763.js.map} +0 -0
  483. /package/dist/{extract-partition-IO7FDZ2P.js.map → extract-partition-KVCTSUVE.js.map} +0 -0
  484. /package/dist/{find-RBBYKXI7.js.map → find-V4FN5ITT.js.map} +0 -0
  485. /package/dist/{issue-RIVHSY2S.js.map → issue-5CFBWBRX.js.map} +0 -0
  486. /package/dist/{ledger-AETNF4OM.js.map → ledger-MQIIOTZB.js.map} +0 -0
  487. /package/dist/{liberate-GFN4C7RR.js.map → liberate-LW7OKVBH.js.map} +0 -0
  488. /package/dist/{link-set-E5HNY67I.js.map → link-set-5PJBBR63.js.map} +0 -0
  489. /package/dist/{managed-secret-CWQ6PND5.js.map → managed-secret-ETDG6AAE.js.map} +0 -0
  490. /package/dist/{noydb-ASEXNJS6.js.map → noydb-LFDRMYZ5.js.map} +0 -0
  491. /package/dist/{post-register-2XSSMGEQ.js.map → post-register-QCJYRMIK.js.map} +0 -0
  492. /package/dist/{register-4EFJCVYD.js.map → register-JBOEHTVX.js.map} +0 -0
  493. /package/dist/{registry-4SDHHOJ7.js.map → registry-5DYHQFJS.js.map} +0 -0
  494. /package/dist/{registry-CZVINVQH.js.map → registry-EANV44W2.js.map} +0 -0
  495. /package/dist/{registry-IKUR2MXD.js.map → registry-J25H6VB4.js.map} +0 -0
  496. /package/dist/{registry-OYTFGWVK.js.map → registry-R23SQUQH.js.map} +0 -0
  497. /package/dist/{request-withdrawal-KGXGDPVQ.js.map → request-withdrawal-KQ4W4UT5.js.map} +0 -0
  498. /package/dist/{reveal-BW36KCOQ.js.map → reveal-W7BKZSUP.js.map} +0 -0
  499. /package/dist/{revoke-JFN773KF.js.map → revoke-H3VSTAIE.js.map} +0 -0
  500. /package/dist/{signer-TA55QB6R.js.map → signer-53AQ3BU2.js.map} +0 -0
  501. /package/dist/{stale-ARHFVL5U.js.map → stale-TH7SSVRV.js.map} +0 -0
  502. /package/dist/{storage-V7MTFAQJ.js.map → storage-WSF7ZKTL.js.map} +0 -0
  503. /package/dist/{ulid-PTAIMDG4.js.map → ulid-VQW6ERQN.js.map} +0 -0
  504. /package/dist/{verify-RO5RTZOJ.js.map → verify-M5LQH43C.js.map} +0 -0
  505. /package/dist/{walk-3ZECBFDA.js.map → walk-NABCHDZN.js.map} +0 -0
  506. /package/dist/{withdraw-accessible-7PG4DYQK.js.map → withdraw-accessible-VCINIG6V.js.map} +0 -0
@@ -1,29 +1,34 @@
1
1
  import {
2
- assertTierAccess,
2
+ assertTierAccess
3
+ } from "./chunk-3YGUHUJA.js";
4
+ import {
3
5
  dekKey
4
- } from "./chunk-AHLJSVRL.js";
6
+ } from "./chunk-D2YFQ7XG.js";
5
7
  import {
6
8
  applyRewrappedBody,
9
+ buildRecordAad,
10
+ recordAadFor,
7
11
  rewrapBodyToDek
8
- } from "./chunk-3URQC7EH.js";
12
+ } from "./chunk-CXPOG3ZY.js";
9
13
  import {
14
+ buildRecordEnvelope,
10
15
  isDeleteMarker,
11
16
  isTombstoneShape
12
- } from "./chunk-7H73RIE3.js";
17
+ } from "./chunk-FX3UIDCS.js";
13
18
  import {
14
19
  decrypt,
15
20
  encrypt,
16
21
  unwrapCek
17
- } from "./chunk-VCETL77O.js";
22
+ } from "./chunk-ONPVHX2Y.js";
18
23
  import {
19
24
  NOYDB_FORMAT_VERSION
20
- } from "./chunk-YPYHVOXP.js";
25
+ } from "./chunk-UJ4Z67UQ.js";
21
26
  import {
22
27
  PersistedIndexCompensationError,
23
28
  TierDemoteDeniedError,
24
29
  TiersNotEnabledError,
25
30
  UnsupportedTierCompositionError
26
- } from "./chunk-HNGSV2YC.js";
31
+ } from "./chunk-Z3BZXPRO.js";
27
32
 
28
33
  // src/with-audit/tiers/active.ts
29
34
  function withTiers() {
@@ -124,22 +129,33 @@ async function putAtTier(ctx, id, record, tier, opts) {
124
129
  const fromKey = dekKey(ctx.name, existing?._tier ?? 0);
125
130
  const fromDek = await ctx.getDEK(fromKey);
126
131
  if (existing && ctx.historyEnabled) {
127
- const body = await rewrapBodyToDek(existing, fromDek, dek);
128
- const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...preCarried } = existing;
129
- await ctx.saveHistorySnapshot(id, applyRewrappedBody(preCarried, body));
132
+ const preFrom = {
133
+ // The PRE-move version this identity opens what is already stored.
134
+ collection: ctx.name,
135
+ id,
136
+ version: existing._v,
137
+ ...existing._tier !== void 0 ? { tier: existing._tier } : {},
138
+ ...existing._by !== void 0 ? { by: existing._by } : {}
139
+ };
140
+ await ctx.saveHistorySnapshot(id, existing._v, async (address) => {
141
+ const to = { ...address, ...existing._by !== void 0 ? { by: existing._by } : {} };
142
+ const body = await rewrapBodyToDek(preFrom, to, existing, fromDek, dek);
143
+ const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...preCarried } = existing;
144
+ return applyRewrappedBody(preCarried, body);
145
+ });
130
146
  }
131
147
  const json = JSON.stringify(record);
132
- const { iv, data } = await encrypt(json, dek);
133
- const envelope = {
134
- _noydb: NOYDB_FORMAT_VERSION,
135
- _v: version,
136
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
137
- _iv: iv,
138
- _data: data,
139
- _by: ctx.keyring.userId,
140
- ...tier > 0 && { _tier: tier },
141
- ...ctx.provenance && opts?.source !== void 0 ? { _source: opts.source, _sourceTs: opts.sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {}
142
- };
148
+ const identity = { collection: ctx.name, id, version, by: ctx.keyring.userId, ...tier > 0 ? { tier } : {} };
149
+ const { iv, data } = await encrypt(json, dek, buildRecordAad(identity));
150
+ const envelope = buildRecordEnvelope(
151
+ identity,
152
+ {
153
+ iv,
154
+ data,
155
+ // `_tier` is stamped from `identity.tier` above — the single source.
156
+ ...ctx.provenance && opts?.source !== void 0 ? { provenance: { source: opts.source, sourceTs: opts.sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } } : {}
157
+ }
158
+ );
143
159
  await ctx.adapter.put(ctx.vault, ctx.name, id, envelope);
144
160
  await ctx.addSubjectRef(id, record);
145
161
  if (tier > 0) {
@@ -148,11 +164,11 @@ async function putAtTier(ctx, id, record, tier, opts) {
148
164
  await ctx.syncLedger(id);
149
165
  await ctx.syncDerived(
150
166
  id,
151
- ctx.hasDerivedOutputs && existing ? await ctx.codec.decryptRecordAtDek(existing, await ctx.getDEK(dekKey(ctx.name, existing._tier ?? 0)), id) : null,
167
+ ctx.hasDerivedOutputs && existing ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, existing, await ctx.getDEK(dekKey(ctx.name, existing._tier ?? 0))) : null,
152
168
  true
153
169
  );
154
170
  } else {
155
- const rec = await ctx.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
171
+ const rec = await ctx.codec.decryptRecord({ collection: ctx.name, id }, envelope, { sealedAsHandles: true });
156
172
  await ctx.syncIndexes(id, rec, envelope._v, existing ?? void 0);
157
173
  ctx.syncCache(id, rec !== null ? { record: rec, version: envelope._v } : null);
158
174
  await ctx.syncDerived(id, rec, false, envelope._v);
@@ -185,7 +201,7 @@ async function getAtTier(ctx, id) {
185
201
  if (!envelope) return null;
186
202
  const tier = envelope._tier ?? 0;
187
203
  if (tier === 0) {
188
- return ctx.codec.decryptRecord(envelope, { id });
204
+ return ctx.codec.decryptRecord({ collection: ctx.name, id }, envelope);
189
205
  }
190
206
  const key = dekKey(ctx.name, tier);
191
207
  if (!ctx.keyring.deks.has(key)) {
@@ -195,14 +211,15 @@ async function getAtTier(ctx, id) {
195
211
  return null;
196
212
  }
197
213
  const dek = await ctx.getDEK(key);
214
+ const aad = recordAadFor({ collection: ctx.name, id }, envelope);
198
215
  let plaintext;
199
216
  let cek;
200
217
  if (envelope._cek !== void 0) {
201
218
  cek = await unwrapCek(envelope._cek, dek);
202
219
  ctx.cekCache?.set(id, cek, 1);
203
- plaintext = await decrypt(envelope._iv, envelope._data, cek);
220
+ plaintext = await decrypt(envelope._iv, envelope._data, cek, aad);
204
221
  } else {
205
- plaintext = await decrypt(envelope._iv, envelope._data, dek);
222
+ plaintext = await decrypt(envelope._iv, envelope._data, dek, aad);
206
223
  }
207
224
  let record = JSON.parse(plaintext);
208
225
  if (envelope._sealed !== void 0) {
@@ -261,10 +278,30 @@ async function elevate(ctx, id, toTier) {
261
278
  const fromDek = await ctx.getDEK(fromKey);
262
279
  const toDek = await ctx.getDEK(toKey);
263
280
  const now = (/* @__PURE__ */ new Date()).toISOString();
264
- const body = await rewrapBodyToDek(envelope, fromDek, toDek);
281
+ const moveFrom = {
282
+ // Opens at the version currently stored; the live write below re-seals at
283
+ // `_v + 1`, so the two ends of a move are NOT the same identity (#1093).
284
+ collection: ctx.name,
285
+ id,
286
+ version: envelope._v,
287
+ ...fromTier > 0 ? { tier: fromTier } : {},
288
+ ...envelope._by !== void 0 ? { by: envelope._by } : {}
289
+ };
290
+ const moveTo = {
291
+ collection: ctx.name,
292
+ id,
293
+ version: envelope._v + 1,
294
+ ...toTier > 0 ? { tier: toTier } : {},
295
+ by: ctx.keyring.userId
296
+ };
297
+ const body = await rewrapBodyToDek(moveFrom, moveTo, envelope, fromDek, toDek);
265
298
  if (body.cek) ctx.cekCache?.set(id, body.cek, 1);
266
- const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...snapshot } = applyRewrappedBody(envelope, body);
267
- await ctx.saveHistorySnapshot(id, snapshot);
299
+ await ctx.saveHistorySnapshot(id, envelope._v, async (address) => {
300
+ const to = { ...address, ...envelope._by !== void 0 ? { by: envelope._by } : {} };
301
+ const histBody = await rewrapBodyToDek(moveFrom, to, envelope, fromDek, toDek);
302
+ const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...snapshot } = applyRewrappedBody(envelope, histBody);
303
+ return snapshot;
304
+ });
268
305
  const next = {
269
306
  ...envelope,
270
307
  _noydb: NOYDB_FORMAT_VERSION,
@@ -282,7 +319,7 @@ async function elevate(ctx, id, toTier) {
282
319
  ctx.syncCache(id, null);
283
320
  const searchResidue = await syncSearchResilient(ctx, id, null);
284
321
  await ctx.syncLedger(id);
285
- await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true);
322
+ await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, envelope, fromDek) : null, true);
286
323
  ctx.emitCrossTierEvent({
287
324
  actor: ctx.keyring.userId,
288
325
  collection: ctx.name,
@@ -318,10 +355,30 @@ async function demote(ctx, id, toTier) {
318
355
  const fromDek = await ctx.getDEK(dekKey(ctx.name, fromTier));
319
356
  const toDek = await ctx.getDEK(dekKey(ctx.name, toTier));
320
357
  const now = (/* @__PURE__ */ new Date()).toISOString();
321
- const body = await rewrapBodyToDek(envelope, fromDek, toDek);
358
+ const moveFrom = {
359
+ // Opens at the version currently stored; the live write below re-seals at
360
+ // `_v + 1`, so the two ends of a move are NOT the same identity (#1093).
361
+ collection: ctx.name,
362
+ id,
363
+ version: envelope._v,
364
+ ...fromTier > 0 ? { tier: fromTier } : {},
365
+ ...envelope._by !== void 0 ? { by: envelope._by } : {}
366
+ };
367
+ const moveTo = {
368
+ collection: ctx.name,
369
+ id,
370
+ version: envelope._v + 1,
371
+ ...toTier > 0 ? { tier: toTier } : {},
372
+ by: ctx.keyring.userId
373
+ };
374
+ const body = await rewrapBodyToDek(moveFrom, moveTo, envelope, fromDek, toDek);
322
375
  if (body.cek) ctx.cekCache?.set(id, body.cek, 1);
323
376
  const { _elevatedBy, _tier: _priorTier, ...carried } = envelope;
324
- await ctx.saveHistorySnapshot(id, applyRewrappedBody(carried, body));
377
+ await ctx.saveHistorySnapshot(id, envelope._v, async (address) => {
378
+ const to = { ...address, ...envelope._by !== void 0 ? { by: envelope._by } : {} };
379
+ const histBody = await rewrapBodyToDek(moveFrom, to, envelope, fromDek, toDek);
380
+ return applyRewrappedBody(carried, histBody);
381
+ });
325
382
  const next = {
326
383
  ...carried,
327
384
  _noydb: NOYDB_FORMAT_VERSION,
@@ -336,7 +393,7 @@ async function demote(ctx, id, toTier) {
336
393
  await ctx.adapter.put(ctx.vault, ctx.name, id, next);
337
394
  let searchResidue;
338
395
  if (toTier === 0) {
339
- const rec = await ctx.codec.decryptRecord(next, { id, sealedAsHandles: true });
396
+ const rec = await ctx.codec.decryptRecord({ collection: ctx.name, id }, next, { sealedAsHandles: true });
340
397
  await ctx.syncIndexes(id, rec, next._v, envelope);
341
398
  ctx.syncCache(id, rec !== null ? { record: rec, version: next._v } : null);
342
399
  searchResidue = await syncSearchResilient(ctx, id, rec, next._v);
@@ -345,7 +402,7 @@ async function demote(ctx, id, toTier) {
345
402
  await ctx.syncIndexes(id, null, next._v);
346
403
  ctx.syncCache(id, null);
347
404
  searchResidue = await syncSearchResilient(ctx, id, null);
348
- await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true);
405
+ await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, envelope, fromDek) : null, true);
349
406
  }
350
407
  ctx.emitCrossTierEvent({
351
408
  actor: ctx.keyring.userId,
@@ -378,4 +435,4 @@ export {
378
435
  demote,
379
436
  classifySealedShred
380
437
  };
381
- //# sourceMappingURL=chunk-6NVPFKN3.js.map
438
+ //# sourceMappingURL=chunk-ZIMQDXTQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-audit/tiers/active.ts","../src/with-audit/tiers/strategy.ts","../src/with-audit/tiers/index.ts"],"sourcesContent":["/**\n * Enable the hierarchical-tiers capability.\n * Pass to `createNoydb({ tiersStrategy: withTiers() })` to make a collection's\n * `putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote` methods live.\n * The tier read/write/re-key engine is dynamically imported here, so it stays\n * out of the floor bundle until opted in.\n */\nimport type { TiersStrategy } from './strategy.js'\n\nexport function withTiers(): TiersStrategy {\n return {\n async putAtTier(ctx, id, record, tier, opts) {\n const { putAtTier } = await import('./index.js')\n return putAtTier(ctx, id, record, tier, opts)\n },\n async getAtTier(ctx, id) {\n const { getAtTier } = await import('./index.js')\n return getAtTier(ctx, id)\n },\n async listAtTier(ctx) {\n const { listAtTier } = await import('./index.js')\n return listAtTier(ctx)\n },\n async elevate(ctx, id, toTier) {\n const { elevate } = await import('./index.js')\n return elevate(ctx, id, toTier)\n },\n async demote(ctx, id, toTier) {\n const { demote } = await import('./index.js')\n return demote(ctx, id, toTier)\n },\n }\n}\n","/**\n * Tiers capability strategy — the five on-demand collection-level tier\n * operations the `Collection` routes through. The active engine\n * ({@link withTiers}) dynamically imports the tier cores (keeping them out of\n * the floor bundle); {@link NO_TIERS} throws. Collection always builds its\n * per-call {@link TiersContext} and delegates here, so an un-opted-in caller\n * hits `NO_TIERS`'s throw.\n * @internal\n */\nimport type { GhostRecord } from '../../kernel/types.js'\nimport type { TiersContext, TierMoveResult } from './index.js'\nimport { TiersNotEnabledError } from '../../kernel/errors.js'\n\n// #779 — re-exported so kernel-spine callers (e.g. `Vault._elevatedPut`) can reference the\n// result shape via this grandfathered strategy.js seam instead of statically importing\n// tiers/index.js directly (the port-layering check only grandfathers this file).\nexport type { TierMoveResult }\n\nexport interface TiersStrategy {\n putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n ): Promise<TierMoveResult>\n getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null>\n listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>>\n elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n}\n\n/**\n * No-op stub — the floor default. Every capability method throws\n * {@link TiersNotEnabledError}; opt in with `tiersStrategy: withTiers()` in\n * createNoydb. @internal\n */\nexport const NO_TIERS: TiersStrategy = {\n async putAtTier() { throw new TiersNotEnabledError() },\n async getAtTier() { throw new TiersNotEnabledError() },\n async listAtTier() { throw new TiersNotEnabledError() },\n async elevate() { throw new TiersNotEnabledError() },\n async demote() { throw new TiersNotEnabledError() },\n}\n","/**\n * Hierarchical access — the collection-level tier operations\n * (`putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote`).\n *\n * A collection opted into `{ tiers: [...] }` stamps `_tier: N` on each envelope\n * and encrypts its body under the tier-N DEK. These helpers are the read/write\n * surface for that scheme: write at a tier, read with tier-aware visibility\n * (invisibility vs. ghost), and move a record up (`elevate`) or down (`demote`)\n * the tier ladder by re-wrapping its body key.\n *\n * Each function takes a small {@link TiersContext} (the exact `this.*` the\n * moving methods touched) instead of `this`, mirroring the `record-keys/`\n * siblings. Behaviour is byte-identical to the inline code it replaced.\n *\n * The crux is `cekCache`: `elevate`/`demote`/`getAtTier` write the SAME `Lru`\n * reference `Collection` owns (never a copy) so a per-record CEK re-wrap stays\n * synchronous with the cache the kernel's write/read path also mutates. The\n * cross-tier event sink stays collection-resident and is reached via the\n * `emitCrossTierEvent` callback.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nexport { withTiers } from './active.js'\nexport { NO_TIERS, type TiersStrategy } from './strategy.js'\nexport { TiersNotEnabledError } from '../../kernel/errors.js'\nimport { recordAadFor, type RecordIdentity, buildRecordAad, buildRecordEnvelope, encrypt, decrypt, unwrapCek, rewrapBodyToDek, applyRewrappedBody, isDeleteMarker, isTombstoneShape, type RecordCodec, type EnclaveKey, type SealedShredSlot } from '../../kernel/enclave/index.js'\nimport { TierDemoteDeniedError, UnsupportedTierCompositionError, PersistedIndexCompensationError } from '../../kernel/errors.js'\nimport { dekKey, assertTierAccess } from '../../with-party/team/tiers.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport type { Lru } from '../../kernel/cache/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport {\n NOYDB_FORMAT_VERSION,\n type NoydbStore,\n type EncryptedEnvelope,\n type GhostRecord,\n type TierMode,\n type CrossTierAccessEvent,\n} from '../../kernel/types.js'\n\n/** Everything the moving tier methods touched on `this.*`, as a flat context. */\nexport interface TiersContext<T> {\n /** Collection name — the crypto AAD scope and tier-DEK key prefix. */\n readonly name: string\n /** Vault namespace the records live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The caller's unlocked keyring (tier-DEK holdings + role + userId). */\n readonly keyring: UnlockedKeyring\n /** The record codec — decrypts a tier-0 envelope to T. */\n readonly codec: RecordCodec<T>\n /**\n * The collection's per-record CEK cache (SHARED reference, not a copy).\n * `elevate`/`demote`/`getAtTier` re-wrap a record's CEK and `set` it here so\n * the move stays synchronous with the cache the kernel's read/write path\n * owns. `null` → no caching.\n */\n readonly cekCache: Lru<string, EnclaveKey> | null\n /**\n * Sync the collection's decoded-record cache after a tier move rewraps the\n * envelope (#691). The lazy LRU is evicted on EVERY call — it is never\n * re-seeded, since a lazy `#getRaw` miss refetches and decodes the fresh\n * envelope via its adapter fallback (a stale LRU entry after a tier-0\n * `putAtTier` overwrite was the #702 lazy-mode gap). `null` → also evict\n * the eager cache entry; an `entry` → set the eager cache to the given\n * decoded record/version (used by `demote()` landing back at tier 0 and\n * `putAtTier`'s tier-0 write, #702 — the record is tier-0, so it must stay\n * plain-`get()`-readable in-session).\n */\n syncCache(id: string, entry: { record: T; version: number } | null): void\n /**\n * Sync the collection's indexes after a tier move rewraps the envelope\n * (#709). Persisted `_idx/<field>/<recordId>` side-cars hold the indexed\n * field's PLAINTEXT value and are always encrypted under the tier-0 DEK,\n * whatever the record's own tier — the same leak class `forget()` fixed\n * via `purgePersistedIndexes` (\"forget() crypto-shreds the body but keeps\n * the collection DEK, under which these side-cars are encrypted — so\n * without this they leave the indexed field VALUES readable\"). `null` →\n * the record just left tier 0: purge its persisted side-cars and drop its\n * in-memory index entries. A record → it is tier-0 again: (re)build its\n * entries from that record. Call this BEFORE {@link syncCache} — the\n * implementation reads the pre-write cached value as \"previous\" to clean\n * up stale index buckets, so it must run while that entry is still live.\n * `version` stamps a rebuilt side-car's own envelope version — every\n * caller has one in scope (the envelope it just wrote), including the\n * purge (`null`) branches, which pass it along even though it's ignored\n * there (#720: keeps the parameter honestly required, no dead default).\n * `priorEnvelope` (#720) — the RAW envelope read BEFORE this call's own\n * overwrite (already in scope as `existing`/`envelope` at every record-\n * branch call site), so a lazy same-tier value change (a `putAtTier(0)`\n * dropping an indexed field, a `demote(0)` off an elevated record) can\n * still be tier-gate-decoded as \"previous\" once the live envelope itself\n * has already moved past it. The `null`-record branches never pass one —\n * see the implementation's doc comment for why they don't need to.\n */\n syncIndexes(id: string, record: T | null, version: number, priorEnvelope?: EncryptedEnvelope): Promise<void>\n /** Sync the collection's SEARCH artifacts after a tier move (#721). Both the\n * lexical `_ftindex` blob and the `_vec/<id>` embedding are encrypted under\n * the tier-0 DEK and hold the record's derived plaintext (full field text /\n * a text-invertible vector), so leaving them means elevation never hid what\n * the record was searchable by — the `forget()` precedent, unapplied to\n * elevate. `null` → the record left tier 0: purge its `_vec`, and\n * invalidate the `_ftindex` blob (the cache-driven rebuild then excludes\n * it). A record → it is tier-0 again: re-embed it, and invalidate\n * `_ftindex` (rebuild includes it). No-op fast when the collection has no\n * search. */\n syncSearch(id: string, record: T | null, version?: number): Promise<void>\n /**\n * Sync a record's `_history` snapshots after a tier move rewraps the live\n * envelope (#712, at-rest hardening). Each snapshot carries its own key\n * material (a direct DEK wrap, or a per-record `_cek` itself wrapped under\n * the tier DEK) — `elevate()`'s live-body rewrap moves only the live\n * envelope, leaving prior versions decryptable at rest under the tier the\n * record has moved away from (the same leak class `forget()`'s\n * `tombstoneHistory` closed for erasure). `fromDek`/`toDek` are the SAME\n * DEKs the caller already resolved for the live rewrap — reuse them, don't\n * recompute. No-op when the strategy is `NO_HISTORY`.\n */\n syncHistory(id: string, fromDek: EnclaveKey, toDek: EnclaveKey): Promise<void>\n /**\n * Save a raw pre-move envelope as a `_history` snapshot (#728). Tier moves\n * (`putAtTier`/`elevate`/`demote`) bump `_v` and overwrite the live\n * envelope without ever snapshotting the version that existed just before\n * the move — so `history()` silently lost it. The caller (`tiers/index.ts`)\n * builds `envelope` by reusing the SAME `rewrapBodyToDek(envelope, fromDek,\n * toDek)` result it already computed for the live write, so the snapshot\n * lands wrapped under the DESTINATION tier's DEK — never `ctx.codec.\n * encryptRecord`, which always resolves the tier-0 DEK and would leak the\n * pre-move body whenever `fromTier > 0`. No-op when history is disabled\n * (folds the `historyConfig.enabled` gate so `tiers/index.ts` stays simple).\n */\n /**\n * Persist a `_history` snapshot of `version`, sealed against the address it\n * will be STORED at (#1041).\n *\n * Takes a **sealer**, not a finished envelope, because the destination\n * identity is not knowable here: only the history strategy owns the\n * `_history` layout. It hands the address in; the caller seals to it.\n *\n * ⚠️ The identity passed to `rewrapBodyToDek` must be what a READER will\n * recompute — `recordAadFor(address, finalEnvelope)`. A tier-move snapshot\n * strips `_tier`/`_elevatedBy` but KEEPS `_by`, so the destination is\n * `{...address, by: envelope._by}` with no tier. Sealing against the bare\n * address would produce history nothing can open, and the failure would\n * surface only when someone reads it.\n */\n saveHistorySnapshot(\n id: string,\n version: number,\n seal: (address: RecordIdentity) => Promise<EncryptedEnvelope>,\n ): Promise<void>\n /**\n * `true` iff a real history strategy is wired (not `NO_HISTORY`) AND not\n * explicitly disabled via `historyConfig.enabled`. `putAtTier` checks this\n * BEFORE decrypting `existing` to build a snapshot (#728, #737 regression\n * fix) — unlike elevate/demote, which reuse a `body` rewrap their live\n * write needs unconditionally, putAtTier's live write never decrypts the\n * prior envelope, so building a snapshot is the ONLY reason it would\n * touch (and potentially throw decrypting) `existing`'s ciphertext. A\n * derivation-free, history-disabled collection must not pay that decode —\n * or risk it, on a corrupted/inaccessible prior body — same law\n * `hasDerivedOutputs` already enforces for the derived-outputs decode.\n */\n readonly historyEnabled: boolean\n /**\n * Rehome a record's blob attachments after a tier move (#724 Arc 10 Task\n * 2, at-rest hardening). A blob's home tier is its owning record's tier —\n * the `_blob` DEK is tier-scoped (`dekKey('_blob', tier)`), so a solo-\n * owned blob's content CEK must move with the record or it stays\n * tier-0-unwrappable at rest even once the live body has moved (the same\n * leak class `syncHistory` closes for `_history` snapshots). Delegates to\n * `BlobSet.rehomeForTier`; no-op fast when the collection has no blob\n * fields. Order-independent with the other sync hooks — touches only\n * blob side structures, not this collection's own cache/indexes.\n */\n syncBlobs(id: string, fromTier: number, toTier: number): Promise<void>\n /**\n * Purge a record's tier-0-era plaintext ledger deltas after a tier move\n * lands it above tier 0 (#729). The audit ledger is a flat, vault-wide,\n * hash-chained log — a record's reverse-JSON-Patch deltas in\n * `_ledger_deltas` are encrypted under one collection-wide `_ledger` DEK\n * (not the record's own tier DEK), so `elevate()`/`putAtTier()`'s live-body\n * rewrap never moves them: they stay readable at rest to any tier-0\n * caller, the same leak class `syncHistory` closed for `_history`\n * snapshots. Chain-safe: `verify()` reads only `_ledger` entry fields\n * (including `deltaHash`, which lives on the entry, not the deleted delta\n * row), never `_ledger_deltas`, so a purge cannot break it.\n * **Irreversible** — the deleted plaintext cannot be restored by\n * `demote()`. **Metadata-retained** — the `_ledger` entries (that the\n * record was mutated, at which version/timestamp/actor) are untouched;\n * only the delta *content* is purged. No-op when the collection has no\n * ledger (`withHistory()` not enabled).\n */\n syncLedger(id: string): Promise<void>\n /**\n * Sync a record's derived outputs (materialized-view rows, rollup\n * contributions, `withDerivation` outputs) after a tier move (#722).\n * Those outputs are computed from this record and written to OTHER\n * (output) collections via a plain `put` at tier 0 — `elevate()`'s live-\n * body rewrap moves only the source envelope, so the output rows keep\n * holding the source's tier-0-era plaintext, the same leak class\n * `syncSearch`/`syncIndexes` closed for THIS collection's own artifacts.\n * `elevated` (landing tier > 0) reuses the SAME onDelete fanout\n * `forgetDerivedFanout` drives for `forget()` (`kernel/via/dispatch.ts`)\n * — minus its `'ref'` cascade edge (elevate/demote never erase a\n * *different* record) — to recompute this record's derived outputs as\n * REMOVED. Recompute is tier-safe: the fanout's own source scan reads\n * the elevated-excluding cache (#701/#709/#712), so it naturally omits\n * the now-elevated record instead of re-embedding its plaintext. Landing\n * back at tier 0 (`elevated === false`) is the reverse (Task 2, #722):\n * `syncDerivedOutputs` instead runs the ordinary local-write add-\n * dispatchers (`dispatchDerivations`/`dispatchMaterializedViews`, the\n * same ones a plain `put()` fires) to restore the contribution —\n * reversible, since the source's plaintext survives the elevate/demote\n * rewrap round-trip. `record` is the decoded source record (PRE-move on\n * the remove side, POST-move on the add side); only the rollup edge\n * consumes it on remove, both add-dispatchers require it on add; `null`\n * (tombstone/delete-marker) skips both. `version` stamps the add-\n * dispatchers' `_derivedFrom.sourceVersion` metadata — reuses the version\n * the tier op already has, no re-read; unused on the remove side. No-op\n * fast when the collection has no MV/derivation source — each dispatcher\n * below checks its own source before doing any work.\n */\n syncDerived(id: string, record: T | null, elevated: boolean, version?: number): Promise<void>\n /**\n * `true` iff the collection has a materialized-view or derivation source\n * attached. Gates the {@link syncDerived} pre-move decode on the remove\n * direction (`elevate()`, `putAtTier(tier>0)`, `demote()`'s intermediate-\n * tier branch): those sites decode the record SOLELY to feed\n * `syncDerived(..., true)`, whose dispatchers no-op immediately when the\n * collection has no MV/derivation source — so a no-derivation collection\n * would otherwise pay a full record-body decrypt on every tier move for\n * nothing (#722 perf regression). `false` short-circuits the decode to\n * `null`, which `syncDerivedOutputs` already treats safely.\n */\n readonly hasDerivedOutputs: boolean\n /** Emit `_source`/`_sourceTs` provenance fields when a source is supplied. */\n readonly provenance: boolean\n /** Declared tiers, or null when the feature is off. */\n readonly tiers: ReadonlySet<number> | null\n /** Above-tier read visibility mode (`'invisibility'` | `'ghost'`). */\n readonly tierMode: TierMode\n /** Resolve a tier DEK by its `dekKey(name, tier)`. */\n getDEK(key: string): Promise<EnclaveKey>\n /** Fire a cross-tier access event (sink stays collection-resident). */\n emitCrossTierEvent(event: CrossTierAccessEvent): void\n /**\n * Register this record's ref in the encrypted `_subject_index` (#766).\n * `putAtTier`'s raw `ctx.adapter.put` bypasses `Collection.put()`'s write-\n * hook pipeline — the `onAfterWrite` hook that normally does this — so a\n * record whose FIRST persistence is `putAtTier` (sensitive from birth, a\n * documented legitimate use) would otherwise never enter the index and\n * stay unreachable by `vault.forget()`. Idempotent (safe on every call,\n * including a repeat write of an already-registered record) and a no-op\n * when the collection declares no forget-subject field.\n */\n addSubjectRef(id: string, record: T): Promise<void>\n}\n\nexport function assertTiersEnabled<T>(ctx: TiersContext<T>): void {\n if (!ctx.tiers) {\n throw new Error(\n `Collection \"${ctx.name}\": hierarchical tiers are not enabled. ` +\n `Pass { tiers: [0, 1, 2, …] } to vault.collection() to opt in.`,\n )\n }\n}\n\nexport function assertDeclaredTier<T>(ctx: TiersContext<T>, tier: number): void {\n if (tier < 0 || !Number.isInteger(tier)) {\n throw new Error(`Collection \"${ctx.name}\": tier must be a non-negative integer, got ${tier}`)\n }\n if (tier === 0) return\n if (!ctx.tiers || !ctx.tiers.has(tier)) {\n throw new Error(\n `Collection \"${ctx.name}\": tier ${tier} is not declared in { tiers: [...] }`,\n )\n }\n}\n\n/**\n * Tier-composition guard (#724 / Arc 7 of the tier-invisibility campaign).\n *\n * Refuses `tiers` declared together with a derived-artifact feature whose\n * crypto has not yet been made tier-aware — i.e. a feature `elevate()` /\n * `demote()` does not re-key when a record moves tiers, so an elevated\n * record's data for that feature would stay readable at tier 0.\n *\n * **Status:** the original call site (`Collection` constructor, beside\n * `buildUniqueConstraintSet`) was removed by Arc 10 Task 1 (#724) — the\n * `tiers + blobFields` refusal below was superseded by a runtime read gate\n * on `collection.blob(id)` (`with-shape/blobs/blob-set.ts`), so this\n * function currently has no caller. Relocated here from\n * `with-lookup/indexing/unique-constraints.ts` (#733) so the tier-domain\n * guard lives in the tier domain, rather than an indexing file it only\n * borrowed for a grandfathered import specifier.\n *\n * ## #724 verified: `tiers + blobFields` leaks\n *\n * `collection.blob(id)` (`Collection.blob()`) never checks the live\n * record's tier before returning a `BlobSet` handle, and `BlobSet`'s crypto\n * is entirely orthogonal to the tier ladder:\n * - the slot map (`_blob_slots_{collection}/{id}`) is encrypted under the\n * collection's TIER-0 DEK (`getDEK(name)` ≡ `dekKey(name, 0)` — see\n * `dekKey` in `with-party/team/tiers.ts`), never a tier-N DEK;\n * - chunk content (`_blob_index` / `_blob_chunks`) is encrypted under the\n * vault-shared `_blob` DEK (`BLOB_COLLECTION`), which has no tier\n * dimension at all.\n * `elevate()`/`demote()` (this file) rewrap only the live record body,\n * `_history` snapshots (#712), and index side-cars — blob slots and chunks\n * are untouched. A caller whose keyring never held the record's elevated\n * tier DEK can still open `collection.blob(id).get(slot)` and read full\n * plaintext, even though `collection.get(id)` correctly reports the same\n * record as invisible. See `__tests__/tier-composition-guard.test.ts` for\n * the reproduction.\n *\n * ## Safe today — no check needed here\n *\n * Field indexes, search (`textIndexes`/`embeddings`), `withHistory`\n * (snapshot keys are rewrapped by `elevate()`/`demote()`, #712), and the\n * decoded-record cache (evicted on tier moves) are already tier-safe. They\n * are not enumerated below; only known-unsafe features are refused, so any\n * combination not named here passes silently by default.\n *\n * ## Deliberately NOT handled here\n *\n * - `ledger` — `withHistory` threads a ledger writer onto every tiered\n * collection by default in shipped usage; refusing `tiers + ledger` would\n * break that default. A ledger-specific tier-rekey handler (rewrap ledger\n * entries, or scope ledger visibility to tier) is a separate arc.\n * - materialized-view (MV) output — an MV's fanout spec lives on the\n * SOURCE collection, not on the MV collection's own config, so it is not\n * visible at `vault.collection()` registration time and this guard cannot\n * detect it structurally.\n */\nexport function assertTierComposition(\n collectionName: string,\n cfg: { readonly tiers: boolean; readonly blobFields: BlobFieldsConfig | undefined },\n): void {\n if (!cfg.tiers) return\n if (cfg.blobFields !== undefined && Object.keys(cfg.blobFields).length > 0) {\n throw new UnsupportedTierCompositionError(\n 'blobs',\n `Collection \"${collectionName}\": blobFields are not supported together with tiers (#724) — ` +\n `blob chunk content is encrypted under a vault-shared DEK that elevate()/demote() do not ` +\n `re-key, so an elevated record's blob attachments would remain readable at tier 0. Use a ` +\n `non-tiered collection for blob-bearing records until a blob tier-rekey handler ships.`,\n )\n }\n}\n\nfunction isElevatorOrOwner<T>(ctx: TiersContext<T>): boolean {\n return ctx.keyring.role === 'owner' || ctx.keyring.role === 'admin'\n}\n\n/**\n * Structural surface {@link syncDerivedOutputs} needs from the owning\n * collection — the SAME onDelete dispatchers `forgetDerivedFanout` already\n * drives (`kernel/via/dispatch.ts`), reused as-is (#722). Narrower than\n * `Collection<T>` (avoids an import cycle: collection.ts constructs\n * `TiersContext` FROM `this`, which already satisfies this shape\n * structurally — no cast needed at the `tiersContext()` call site).\n */\nexport interface DerivedOutputsHost<T> {\n dispatchMaterializedViewsOnDelete(id: string): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[] }>\n dispatchArrayDerivationsOnDelete(id: string, eraseRecordShapeToo?: boolean): Promise<number>\n dispatchRollupsOnDelete(id: string, deleted: T): Promise<unknown>\n /** Task 2 (#722) add-direction: the same local-write dispatchers an ordinary `put()` fires. */\n dispatchMaterializedViews(id: string, record: T): Promise<void>\n dispatchDerivations(id: string, record: T, version: number): Promise<void>\n}\n\n/**\n * The {@link TiersContext.syncDerived} implementation collection.ts's\n * `tiersContext()` binds `this` into (#722). See that field's doc comment\n * for the design; `host` is `this` — every dispatcher below is a public\n * Collection method, already self-guarding (no-op) when the collection has\n * no materialized-view/derivation source, so no separate fast-path check is\n * needed here.\n */\nexport async function syncDerivedOutputs<T>(\n host: DerivedOutputsHost<T>,\n id: string,\n record: T | null,\n elevated: boolean,\n version?: number,\n): Promise<void> {\n if (elevated) {\n await host.dispatchMaterializedViewsOnDelete(id)\n await host.dispatchArrayDerivationsOnDelete(id, true)\n if (record !== null) await host.dispatchRollupsOnDelete(id, record)\n return\n }\n // Task 2 (#722) recompute-as-add: the record rejoined tier 0 (demote(→0) /\n // putAtTier(0)) — restore its contribution via the SAME local-write\n // dispatchers an ordinary `put()` fires (`collection.ts`'s\n // `_onRecordMutated('local-write')`), same order. `record === null` only\n // when a caller demotes a tombstone/delete-marker to 0 — nothing to add.\n if (record === null) return\n await host.dispatchDerivations(id, record, version ?? 0)\n await host.dispatchMaterializedViews(id, record)\n}\n\n/**\n * tier-aware put. Encrypts the record with the collection's tier-N DEK and\n * stamps `_tier: N` on the envelope. The caller's keyring must hold the tier-N\n * DEK (directly, by delegation, or by virtue of being the grantor); otherwise\n * throws `TierNotGrantedError`.\n *\n * accepts an optional `elevation` context. When present, the emitted cross-tier\n * event is stamped with `authorization: 'elevation'`, the elevation's reason,\n * and the caller's pre-elevation tier. `vault.elevate(...).collection().put`\n * threads this through; direct `putAtTier` calls leave it undefined and fall\n * back to the inherent-write event shape.\n */\nexport async function putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, tier)\n assertTierAccess(ctx.keyring, ctx.name, tier)\n\n const key = dekKey(ctx.name, tier)\n const dek = await ctx.getDEK(key)\n\n const existing = await ctx.adapter.get(ctx.vault, ctx.name, id)\n // #712/whole-branch-fix-1: putAtTier moves the record OFF its existing\n // tier, same as elevate/demote — so the caller must also be cleared for\n // that existing tier, not just the target. Without this, a member holding\n // only the target tier's DEK could putAtTier over a record parked at a\n // tier they've never been granted; the from-tier `getDEK` below would then\n // silently MINT a fresh DEK for that tier into their keyring (a\n // non-cleared caller creating tier key material inside the trust\n // boundary — see `assertTierAccess`'s doc comment). Gate here, BEFORE any\n // from-tier getDEK/syncHistory call, so the mint never happens; owner/\n // admin/custodian still bypass (they may mint, same as elevate/demote).\n assertTierAccess(ctx.keyring, ctx.name, existing?._tier ?? 0)\n const version = existing ? existing._v + 1 : 1\n\n // #728 review-fix-2: resolve the from-tier DEK and snapshot the PRE-move\n // version BEFORE the live write below, mirroring elevate()/demote() —\n // originally this ran much later (after syncIndexes/syncLedger/syncDerived/\n // syncSearchResilient), so a throw in any of those left the live record\n // already moved with `existing` — its sole pre-move copy — gone, silently\n // losing the version #728 exists to preserve. `fromKey`/`fromDek` are\n // resolved ONCE here and reused below for the trailing syncHistory rewrap.\n const fromKey = dekKey(ctx.name, existing?._tier ?? 0)\n const fromDek = await ctx.getDEK(fromKey)\n if (existing && ctx.historyEnabled) {\n // Snapshot the version this write is about to overwrite — same law as\n // Collection.put()'s history save, extended to putAtTier (#728). Rewrap\n // under the DESTINATION `dek` via `applyRewrappedBody` (never\n // `ctx.codec.encryptRecord`, which always resolves the tier-0 DEK and\n // would leak this body at rest whenever the prior tier was > 0).\n // Untagged (`_tier`/`_elevatedBy` stripped), same as elevate()/\n // demote()'s snapshots. `_v` stays the PRE-move version (unbumped), as\n // `preCarried` already carries it.\n const preFrom: RecordIdentity = {\n // The PRE-move version — this identity opens what is already stored.\n collection: ctx.name, id, version: existing._v,\n ...(existing._tier !== undefined ? { tier: existing._tier } : {}),\n ...(existing._by !== undefined ? { by: existing._by } : {}),\n }\n await ctx.saveHistorySnapshot(id, existing._v, async (address) => {\n // The snapshot is UNTAGGED (`_tier` stripped) and keeps `_by`, so that is\n // the identity a reader will recompute from it (#1041).\n const to: RecordIdentity = { ...address, ...(existing._by !== undefined ? { by: existing._by } : {}) }\n const body = await rewrapBodyToDek(preFrom, to, existing, fromDek, dek)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...preCarried\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...preCarried } = existing\n return applyRewrappedBody(preCarried, body)\n })\n }\n\n const json = JSON.stringify(record)\n const identity = { collection: ctx.name, id, version, by: ctx.keyring.userId, ...(tier > 0 ? { tier } : {}) }\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n const envelope = buildRecordEnvelope(\n identity,\n {\n iv,\n data,\n // `_tier` is stamped from `identity.tier` above — the single source.\n ...(ctx.provenance && opts?.source !== undefined\n ? { provenance: { source: opts.source, sourceTs: opts.sourceTs ?? new Date().toISOString() } }\n : {}),\n },\n )\n\n await ctx.adapter.put(ctx.vault, ctx.name, id, envelope)\n\n // #766: putAtTier bypasses Collection.put()'s onAfterWrite hook — register\n // the subject ref directly so a sensitive-from-birth first write stays\n // reachable by vault.forget(). No-op when no forget-subject field is declared.\n await ctx.addSubjectRef(id, record)\n\n // #702/#709: keep the record cache AND indexes coherent with the raw\n // write — same law as elevate/demote (#691): tier > 0 → invisible on\n // tier-0 surfaces (evict + purge); tier 0 → an ordinary write (re-seed +\n // reindex). syncIndexes runs first — it needs the pre-write cache entry\n // as \"previous\", which syncCache is about to overwrite/evict.\n if (tier > 0) {\n await ctx.syncIndexes(id, null, version)\n ctx.syncCache(id, null)\n // #729: the record lands above tier 0 — purge its tier-0-era plaintext\n // ledger deltas (irreversible; a tier-0 putAtTier(0) has nothing to purge).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0\n // — same law as elevate() below. `existing` is the PRE-write envelope;\n // decode it only here (the rollup edge is the only consumer). Gated by\n // `hasDerivedOutputs` — no-derivation collections skip this decrypt\n // entirely (perf regression review finding, Arc 9 #722). Whole-branch\n // review: `existing` may sit at ANY prior tier (including a\n // `putAtTier`-origin tier>0 record with no `_cek` ever minted) — decode\n // it under ITS OWN tier's DEK via `codec.decryptRecordAtDek`, not\n // `ctx.codec.decryptRecord`'s tier-unaware default, which threw\n // `TamperedError` here.\n await ctx.syncDerived(\n id,\n ctx.hasDerivedOutputs && existing\n ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, existing, await ctx.getDEK(dekKey(ctx.name, existing._tier ?? 0)))\n : null,\n true,\n )\n } else {\n const rec = await ctx.codec.decryptRecord({ collection: ctx.name, id }, envelope, { sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, envelope._v, existing ?? undefined)\n ctx.syncCache(id, rec !== null ? { record: rec, version: envelope._v } : null)\n // #722 Task 2: the record is written at tier 0 — restore its\n // contribution to every derived output (reuse the decode above, no\n // double-decrypt).\n await ctx.syncDerived(id, rec, false, envelope._v)\n }\n // #721: search artifacts follow the same tier > 0 → purge / tier 0 →\n // re-embed law as syncIndexes above. No ordering dependency on syncCache —\n // the _ftindex rebuild is deferred to the next retrieve() and _vec re-embed\n // reads `record` directly, not the cache. #774: a permanently stuck\n // compensation must not abort the write — the record put above already\n // landed — so it is caught and surfaced as residue instead, same as\n // elevate()/demote() (#764).\n const searchResidue = await syncSearchResilient(ctx, id, tier > 0 ? null : record, envelope._v)\n\n if (tier > 0) {\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: opts?.elevation ? 'elevation' : 'inherent',\n op: 'put',\n ts: envelope._ts,\n ...(opts?.elevation && {\n reason: opts.elevation.reason,\n elevatedFrom: opts.elevation.fromTier,\n }),\n })\n }\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch (and the cross-tier emit) have already landed. Its\n // own throw (adapter I/O, the Fix-1 assertion, the rewrap) then strands\n // only the `_history` artifact instead of leaving indexes/cache/search\n // unsynced behind an already-moved live record (would conditionally\n // reopen #709/#721/#723 on the error path). A direct putAtTier also moves\n // the record's tier — its history snapshots (if any exist from an earlier\n // tier-0 put()) must follow, same as elevate/demote. Skip when the record\n // was already at this tier (no move, nothing to rewrap). `fromKey`/\n // `fromDek` were already resolved above (review-fix-2), before the live\n // write, for the pre-move snapshot — reused here rather than recomputed.\n if (fromKey !== key) {\n await ctx.syncHistory(id, fromDek, dek)\n // #724: this putAtTier moved the record's tier — rehome its blobs too.\n await ctx.syncBlobs(id, existing?._tier ?? 0, tier)\n }\n return { searchResidue }\n}\n\n/**\n * tier-aware get. When the stored record is at a tier the caller cannot\n * decrypt:\n * - `'invisibility'` mode (default) → returns `null`.\n * - `'ghost'` mode → returns a `GhostRecord` placeholder with the tier and\n * the record id (the record exists but contents are withheld).\n *\n * Fully-cleared reads return the plaintext record and fire a cross-tier event\n * when `_tier > 0`.\n */\nexport async function getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null> {\n assertTiersEnabled(ctx)\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) return null\n const tier = envelope._tier ?? 0\n if (tier === 0) {\n return ctx.codec.decryptRecord({ collection: ctx.name, id }, envelope)\n }\n\n const key = dekKey(ctx.name, tier)\n if (!ctx.keyring.deks.has(key)) {\n if (ctx.tierMode === 'ghost') {\n return { _ghost: true, _tier: tier } as GhostRecord\n }\n return null\n }\n\n const dek = await ctx.getDEK(key)\n // A tiered record may carry a per-record CEK (e.g. a CEK record\n // elevated via `elevate()`): the CEK is wrapped under the TIER DEK, so\n // unwrap under the tier DEK then decrypt the body under the CEK. Legacy\n // tiered records decrypt directly under the tier DEK.\n // This leg decrypts manually rather than through `decryptRecord` (#635), so\n // it must build the AAD itself — from the address it fetched from plus the\n // envelope's own `_tier`/`_by`, exactly as `recordAadFor` does everywhere\n // else (#1041).\n const aad = recordAadFor({ collection: ctx.name, id }, envelope)\n let plaintext: string\n let cek: EnclaveKey | undefined\n if (envelope._cek !== undefined) {\n cek = await unwrapCek(envelope._cek, dek)\n ctx.cekCache?.set(id, cek, 1)\n plaintext = await decrypt(envelope._iv, envelope._data, cek, aad)\n } else {\n plaintext = await decrypt(envelope._iv, envelope._data, dek, aad)\n }\n let record = JSON.parse(plaintext) as T\n\n // #635: this manual-decrypt leg never went through `decryptRecord`, so it\n // never processed `_sealed` slots — an elevated classified record read\n // back through here would return the body WITHOUT its sealed fields\n // (silent omission). `applySealedSlots` is the same post-processing\n // `decryptRecord`'s tier-0 branch above gets for free, extracted so it can\n // take OUR already-unwrapped `cek` (unwrapped under the TIER dek above)\n // instead of resolving one itself under the collection DEK the way\n // `decryptRecord`/`resolveEnvelopeCek` would — which would be the wrong\n // key for a `_cek` wrapped under a tier DEK. `sealedAsHandles` is omitted\n // (default false) to match this function's OWN tier-0 branch above\n // (`ctx.codec.decryptRecord({ collection: ctx.name, id }, envelope)`, no `sealedAsHandles`) —\n // both tiers return sealed fields inline-decrypted, not as handles.\n if (envelope._sealed !== undefined) {\n record = await ctx.codec.applySealedSlots(record, envelope._sealed, cek, { id })\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: isElevatorOrOwner(ctx) ? 'inherent' : 'delegation',\n op: 'get',\n ts: new Date().toISOString(),\n })\n\n return record\n}\n\n/**\n * list ids grouped by the caller's readability. Returns only ids whose tier the\n * caller can read. Above-tier ids are omitted in `'invisibility'` mode and\n * included (with tier metadata) in `'ghost'` mode.\n */\nexport async function listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>> {\n assertTiersEnabled(ctx)\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n const out: Array<{ id: string; tier: number; readable: boolean }> = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env) continue\n const tier = env._tier ?? 0\n const readable = tier === 0 || ctx.keyring.deks.has(dekKey(ctx.name, tier))\n if (!readable && ctx.tierMode === 'invisibility') continue\n out.push({ id, tier, readable })\n }\n return out\n}\n\n/**\n * Result of a completed `elevate()`/`demote()` (#764). The record move\n * (put + indexes/cache/ledger/derived/history/blobs sync) always completes —\n * `searchResidue: true` means only the search-index side (`_vec`/`_ftindex`)\n * was left in a needs-retry state (a stuck `PersistedIndexCompensationError`),\n * mirroring `forget()`'s `indexResidue` posture rather than aborting the move.\n */\nexport interface TierMoveResult {\n readonly searchResidue: boolean\n}\n\n/**\n * Run {@link TiersContext.syncSearch} with #764 resilience: a permanently\n * stuck compensation (`PersistedIndexCompensationError`, thrown by\n * `PersistedIndexStore` when its compensating `remove()` of a stale\n * `_ftindex` blob keeps failing) must not abort `elevate()`/`demote()` — the\n * record's tier-move `put` has already landed, and aborting here would leave\n * `syncLedger`/`syncDerived`/`syncHistory`/`syncBlobs` unsynced behind it (the\n * partial-completion hazard #764 names). Same posture `forget()` takes for\n * `_purgeSearchIndex` (`vault.ts`'s `indexResidue`) — everything else (a\n * genuinely unexpected search-hook error) still aborts, undisturbed.\n */\nasync function syncSearchResilient<T>(ctx: TiersContext<T>, id: string, record: T | null, version?: number): Promise<boolean> {\n try {\n await ctx.syncSearch(id, record, version)\n return false\n } catch (e) {\n if (e instanceof PersistedIndexCompensationError) return true\n throw e\n }\n}\n\n/**\n * elevate a record to a higher tier. Re-encrypts with the target tier's DEK.\n * The caller must hold DEKs for both the current tier (to decrypt) and the\n * target tier (to re-encrypt). Stamps `_elevatedBy` with the caller id so\n * `demote()` can check the reverse operation.\n */\nexport async function elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, toTier)\n assertTierAccess(ctx.keyring, ctx.name, toTier)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier < fromTier) {\n throw new Error(`Use demote() to lower the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n // Caller must have access at the existing tier to decrypt.\n if (fromTier > 0) assertTierAccess(ctx.keyring, ctx.name, fromTier)\n\n const fromKey = dekKey(ctx.name, fromTier)\n const toKey = dekKey(ctx.name, toTier)\n const fromDek = await ctx.getDEK(fromKey)\n const toDek = await ctx.getDEK(toKey)\n\n // Per-record CEK composes with tiers: the body key is unchanged (history\n // chain identity preserved); only the wrapping key moves with the tier.\n // Legacy (no `_cek`) records take the direct-DEK path unchanged.\n const now = new Date().toISOString()\n const moveFrom: RecordIdentity = {\n // Opens at the version currently stored; the live write below re-seals at\n // `_v + 1`, so the two ends of a move are NOT the same identity (#1093).\n collection: ctx.name, id, version: envelope._v,\n ...(fromTier > 0 ? { tier: fromTier } : {}),\n ...(envelope._by !== undefined ? { by: envelope._by } : {}),\n }\n const moveTo: RecordIdentity = {\n collection: ctx.name, id, version: envelope._v + 1,\n ...(toTier > 0 ? { tier: toTier } : {}),\n by: ctx.keyring.userId,\n }\n const body = await rewrapBodyToDek(moveFrom, moveTo, envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #728: snapshot the PRE-move version into `_history` before it's\n // overwritten below — reuses `body` (already fromDek→toDek) via\n // `applyRewrappedBody` (never `ctx.codec.encryptRecord`, which always\n // resolves the tier-0 DEK and would leak this body at rest whenever\n // `fromTier > 0`). Untagged (`_tier`/`_elevatedBy` stripped) so the\n // history read-gate doesn't hide it PERMANENTLY once the record demotes\n // back — at-rest protection comes from the ciphertext being under\n // `toDek`, not from the tag. `_v` stays the PRE-move version (unbumped),\n // exactly as `envelope` already carries it.\n //\n // A SECOND rewrap, not a reuse of `body` above. The live write and the\n // snapshot now land at different identities, so they need different AAD and\n // therefore different ciphertext — sharing one rewrap would produce bytes\n // that open at one address and not the other (#1041).\n await ctx.saveHistorySnapshot(id, envelope._v, async (address) => {\n const to: RecordIdentity = { ...address, ...(envelope._by !== undefined ? { by: envelope._by } : {}) }\n const histBody = await rewrapBodyToDek(moveFrom, to, envelope, fromDek, toDek)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...snapshot\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...snapshot } = applyRewrappedBody(envelope, histBody)\n return snapshot\n })\n // #662: spread every slot the source carries (_sealed/_det/_vdig/_bidx/\n // _source/_sourceTs/_debug) through UNCHANGED, then override only\n // the fields a tier move manages. rewrapBodyToDek preserves the CEK, so no\n // passenger slot is re-keyed by the move. The old field-literal dropped every\n // unlisted slot.\n // NOT a `buildRecordEnvelope` site, deliberately (#1051). This is an envelope\n // TRANSFORM, not a construction: the spread is load-bearing (#662) — it carries\n // every passenger slot (`_sealed`/`_det`/`_vdig`/`_bidx`/`_source`/`_debug`)\n // through unchanged, which a fresh literal would drop. That drop is exactly the\n // defect #1074 found in `rotateKeys`. Its body already goes through the enclave\n // (`rewrapBodyToDek`), which is where a tier move will supply AAD.\n const next: EncryptedEnvelope = {\n ...envelope,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n _tier: toTier,\n _elevatedBy: ctx.keyring.userId,\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n // Evict/purge only once the write landed — a throwing put must not blind\n // the eager cache or leave a readable index behind for a still-valid\n // tier-0 record (same ordering as demote). #709: syncIndexes runs first —\n // it needs the pre-elevation cache entry as \"previous\" to clean the\n // eager index bucket; the persisted side-car purge is content-free.\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: same purge law as syncIndexes above — the record left tier 0, so\n // its _vec sidecar is purged and _ftindex is invalidated. #764: a\n // permanently stuck compensation must not abort the move — the record put\n // above already landed — so it is caught and surfaced as residue instead.\n const searchResidue = await syncSearchResilient(ctx, id, null)\n // #729: elevate always lands the record at tier > 0 — purge its\n // tier-0-era plaintext ledger deltas (irreversible; entry metadata stays).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0 —\n // same law as putAtTier(tier>0) above. `envelope` is the PRE-move envelope\n // (captured before the rewrap), decoded only here (the rollup edge is the\n // only consumer of the record content). Gated by `hasDerivedOutputs` — no-\n // derivation collections skip this decrypt entirely (perf regression\n // review finding, Arc 9 #722). Whole-branch review: decode under `fromDek`\n // via `codec.decryptRecordAtDek` — `fromTier` may be > 0 here (a prior\n // elevate), and `ctx.codec.decryptRecord`'s tier-unaware CEK resolution\n // threw `TamperedError` whenever this op's own rewrap hadn't just primed\n // the cekCache (non-`perRecordKeys` collections; `_cek`-absent bodies).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, envelope, fromDek) : null, true)\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: toTier,\n authorization: 'elevation',\n op: 'elevate',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps prior-version history snapshots to the SAME toTier DEK —\n // otherwise they stay decryptable at rest under fromDek forever.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments to the target tier's\n // `_blob` DEK — same at-rest law as syncHistory above.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * demote a record to a lower tier. Allowed only for the user who performed the\n * last elevation or an owner.\n */\nexport async function demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n if (toTier < 0) throw new Error(`Cannot demote to negative tier ${toTier}`)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier > fromTier) {\n throw new Error(`Use elevate() to raise the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n const isOwner = ctx.keyring.role === 'owner'\n const isOriginalElevator = envelope._elevatedBy === ctx.keyring.userId\n if (!isOwner && !isOriginalElevator) {\n throw new TierDemoteDeniedError(id, fromTier)\n }\n // Caller must still hold the DEK of the current tier to decrypt.\n assertTierAccess(ctx.keyring, ctx.name, fromTier)\n if (toTier > 0) assertDeclaredTier(ctx, toTier)\n\n const fromDek = await ctx.getDEK(dekKey(ctx.name, fromTier))\n const toDek = await ctx.getDEK(dekKey(ctx.name, toTier))\n\n // CEK re-wrap on demote — same body key, moved from the source tier\n // DEK to the target tier DEK. Legacy records take the direct-DEK path.\n const now = new Date().toISOString()\n const moveFrom: RecordIdentity = {\n // Opens at the version currently stored; the live write below re-seals at\n // `_v + 1`, so the two ends of a move are NOT the same identity (#1093).\n collection: ctx.name, id, version: envelope._v,\n ...(fromTier > 0 ? { tier: fromTier } : {}),\n ...(envelope._by !== undefined ? { by: envelope._by } : {}),\n }\n const moveTo: RecordIdentity = {\n collection: ctx.name, id, version: envelope._v + 1,\n ...(toTier > 0 ? { tier: toTier } : {}),\n by: ctx.keyring.userId,\n }\n const body = await rewrapBodyToDek(moveFrom, moveTo, envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #662: same passenger carry-through as elevate(). demote additionally CLEARS\n // `_elevatedBy` (the demote right is consumed) and OMITS `_tier` at tier 0, so\n // both are destructured out of the spread rather than carried.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...carried\n const { _elevatedBy, _tier: _priorTier, ...carried } = envelope\n // #728: snapshot the PRE-move version into `_history` — reuses `body`\n // (already fromDek→toDek) and `carried` (envelope minus `_tier`/\n // `_elevatedBy`, computed above for the live write) via\n // `applyRewrappedBody`, so a tier-0-landing demote's snapshot is untagged\n // just like an ordinary put() entry, same law as elevate()'s snapshot\n // above. `_v` stays the PRE-move version (unbumped), as `carried` already\n // carries it.\n // A SECOND rewrap — see elevate()'s note. The snapshot's destination\n // identity differs from the live write's, so the bytes must differ too.\n await ctx.saveHistorySnapshot(id, envelope._v, async (address) => {\n const to: RecordIdentity = { ...address, ...(envelope._by !== undefined ? { by: envelope._by } : {}) }\n const histBody = await rewrapBodyToDek(moveFrom, to, envelope, fromDek, toDek)\n return applyRewrappedBody(carried, histBody)\n })\n // Transform, not construction — see the note on elevate()'s `next` above.\n const next: EncryptedEnvelope = {\n ...carried,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n ...(toTier > 0 ? { _tier: toTier } : {}),\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n\n // #691/#709: landing back at tier 0 makes this a plain tier-0 record\n // again — it must stay plain-get()-readable AND indexed in the same\n // session, so re-seed the eager cache and rebuild its index entries from\n // the just-written (now tier-0-keyed) envelope through the canonical\n // codec path, the same decode collection.ts's own eager hydration / lazy\n // cache-miss paths use — reused for both, no double-decrypt. A demote\n // that lands on an intermediate elevated tier (toTier > 0) still evicts\n // + purges: the record stays above tier 0 and must remain invisible on\n // tier-0 surfaces, unindexed. syncIndexes runs first in both branches —\n // it needs the pre-demote cache entry as \"previous\".\n // #764: a permanently stuck search-index compensation must not abort the\n // move — the record put above already landed — so it is caught in both\n // branches and surfaced as residue on the return value instead.\n let searchResidue: boolean\n if (toTier === 0) {\n const rec = await ctx.codec.decryptRecord({ collection: ctx.name, id }, next, { sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, next._v, envelope)\n ctx.syncCache(id, rec !== null ? { record: rec, version: next._v } : null)\n // #721: reuse the decode above — no double-decrypt. The record is tier-0\n // again, so re-embed its _vec and invalidate _ftindex to include it.\n searchResidue = await syncSearchResilient(ctx, id, rec, next._v)\n // #722 Task 2: the record rejoined tier 0 — restore its contribution to\n // every derived output (reuse the decode above, no double-decrypt).\n await ctx.syncDerived(id, rec, false, next._v)\n } else {\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: still above tier 0 — purge _vec, invalidate _ftindex.\n searchResidue = await syncSearchResilient(ctx, id, null)\n // #722 Task 2: still elevated (an intermediate tier) — the source's\n // derived outputs stay recompute-as-removed, same law as elevate()\n // above. `envelope` is the PRE-move envelope captured at function\n // entry; decoded only here (the rollup edge is the only consumer).\n // Gated by `hasDerivedOutputs` — no-derivation collections skip this\n // decrypt entirely (perf regression review finding, Arc 9 #722).\n // Whole-branch review: decode under `fromDek` via\n // `codec.decryptRecordAtDek` — this branch's `fromTier` is always > 0 (a\n // demote FROM tier `fromTier` TO an intermediate `toTier` > 0), so\n // `ctx.codec.decryptRecord`'s tier-unaware CEK resolution threw\n // `TamperedError` here exactly as it did in elevate() above\n // (code-identical bug, same fix).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek({ collection: ctx.name, id }, envelope, fromDek) : null, true)\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: fromTier,\n authorization: 'elevation',\n op: 'demote',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps history snapshots the same direction as the live body —\n // demote restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments the same direction as the\n // live body — restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * Classify a live envelope's `_sealed` slots for crypto-shred completeness\n * (#M-1). Thin delegate to {@link RecordCodec.classifySealedShred}; surfaced\n * here because `vault.ts` forget() reaches in via `collection._classifySealedShred`.\n */\nexport function classifySealedShred<T>(\n ctx: TiersContext<T>,\n live: EncryptedEnvelope,\n): Promise<{ readonly slots: readonly SealedShredSlot[] }> {\n return ctx.codec.classifySealedShred(live)\n}\n\n// #837 — classifySealedShred's slot type must be nameable from this entry.\nexport type { SealedShredSlot } from '../../kernel/enclave/index.js'\n"],"mappings":"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","names":["putAtTier","getAtTier","listAtTier","elevate","demote"]}
@@ -1,18 +1,19 @@
1
1
  import {
2
+ buildRecordAad,
2
3
  openEnvelopeJson
3
- } from "./chunk-3URQC7EH.js";
4
+ } from "./chunk-CXPOG3ZY.js";
4
5
  import {
5
- encrypt
6
- } from "./chunk-VCETL77O.js";
6
+ buildRecordEnvelope
7
+ } from "./chunk-FX3UIDCS.js";
7
8
  import {
8
- NOYDB_FORMAT_VERSION
9
- } from "./chunk-YPYHVOXP.js";
9
+ encrypt
10
+ } from "./chunk-ONPVHX2Y.js";
10
11
  import {
11
12
  ConflictError,
12
13
  USER_ENVELOPE_COLLECTION,
13
14
  USER_ENVELOPE_MAX_BYTES,
14
15
  UserEnvelopeOversizedError
15
- } from "./chunk-HNGSV2YC.js";
16
+ } from "./chunk-Z3BZXPRO.js";
16
17
 
17
18
  // src/with-party/directory/storage.ts
18
19
  var META_COLLECTION = "_meta";
@@ -29,13 +30,10 @@ async function readDirectoryConfig(store, vault) {
29
30
  }
30
31
  }
31
32
  async function persistDirectoryConfig(store, vault, config) {
32
- const envelope = {
33
- _noydb: NOYDB_FORMAT_VERSION,
34
- _v: 1,
35
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
36
- _iv: "",
37
- _data: JSON.stringify({ enabled: config.enabled })
38
- };
33
+ const envelope = buildRecordEnvelope(
34
+ { collection: META_COLLECTION, id: DIRECTORY_RECORD_ID, version: 1 },
35
+ { iv: "", data: JSON.stringify({ enabled: config.enabled }) }
36
+ );
39
37
  await store.put(vault, META_COLLECTION, DIRECTORY_RECORD_ID, envelope);
40
38
  }
41
39
  function isDirectoryConfig(x) {
@@ -61,14 +59,12 @@ async function readUserVisibility(store, vault, keyringId) {
61
59
  }
62
60
  }
63
61
  async function persistUserVisibility(store, vault, keyringId, visibility) {
64
- const envelope = {
65
- _noydb: NOYDB_FORMAT_VERSION,
66
- _v: 1,
67
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
68
- _iv: "",
69
- _data: JSON.stringify({ hidden: visibility.hidden })
70
- };
71
- await store.put(vault, META_COLLECTION, visibilityRecordId(keyringId), envelope);
62
+ const recordId = visibilityRecordId(keyringId);
63
+ const envelope = buildRecordEnvelope(
64
+ { collection: META_COLLECTION, id: recordId, version: 1 },
65
+ { iv: "", data: JSON.stringify({ hidden: visibility.hidden }) }
66
+ );
67
+ await store.put(vault, META_COLLECTION, recordId, envelope);
72
68
  }
73
69
  async function deleteUserVisibility(store, vault, keyringId) {
74
70
  await store.delete(vault, META_COLLECTION, visibilityRecordId(keyringId));
@@ -83,7 +79,7 @@ function isUserVisibility(x) {
83
79
  async function loadUserEnvelope(store, vault, keyringId, dek) {
84
80
  const envelope = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId);
85
81
  if (!envelope) return null;
86
- const plaintext = await openEnvelopeJson(envelope, dek);
82
+ const plaintext = await openEnvelopeJson({ collection: USER_ENVELOPE_COLLECTION, id: keyringId }, envelope, dek);
87
83
  const data = JSON.parse(plaintext);
88
84
  return {
89
85
  keyringId,
@@ -110,14 +106,12 @@ async function saveUserEnvelope(store, vault, keyringId, payload, dek, expectedV
110
106
  }
111
107
  const nextVersion = (prior?._v ?? 0) + 1;
112
108
  const ts = (/* @__PURE__ */ new Date()).toISOString();
113
- const { iv, data } = await encrypt(json, dek);
114
- const envelope = {
115
- _noydb: NOYDB_FORMAT_VERSION,
116
- _v: nextVersion,
117
- _ts: ts,
118
- _iv: iv,
119
- _data: data
120
- };
109
+ const identity = { collection: USER_ENVELOPE_COLLECTION, id: keyringId, version: nextVersion };
110
+ const { iv, data } = await encrypt(json, dek, buildRecordAad(identity));
111
+ const envelope = buildRecordEnvelope(
112
+ identity,
113
+ { ts, iv, data }
114
+ );
121
115
  await store.put(vault, USER_ENVELOPE_COLLECTION, keyringId, envelope);
122
116
  return {
123
117
  keyringId,
@@ -148,4 +142,4 @@ export {
148
142
  deleteUserEnvelope,
149
143
  listUserEnvelopeIds
150
144
  };
151
- //# sourceMappingURL=chunk-5JY5TSVY.js.map
145
+ //# sourceMappingURL=chunk-ZJUZOJVW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-party/directory/storage.ts","../src/with-party/directory/visibility.ts","../src/with-party/directory/user-envelope/storage.ts"],"sourcesContent":["/**\n * Persistence helpers for the vault-level user-directory toggle\n * (`_meta/directory`). Mirrors the bypass-AES pattern used by\n * `_meta/policy` — the directory document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/user-envelope.md → Directory visibility\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { DirectoryConfig } from './types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level directory document. */\nexport const DIRECTORY_RECORD_ID = 'directory'\n\n/**\n * Read the directory toggle from `_meta/directory`. Returns `undefined`\n * when no document has been persisted — callers treat that as the\n * default-on case (`{ enabled: true }`).\n *\n * Tolerates corrupted documents the same way `_meta/policy` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so a\n * bad write never permanently breaks team enumeration.\n */\nexport async function readDirectoryConfig(\n store: NoydbStore,\n vault: string,\n): Promise<DirectoryConfig | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, DIRECTORY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isDirectoryConfig(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the directory toggle at `_meta/directory`. Idempotent — call\n * on every `db.setDirectoryEnabled()` invocation. Owner-only at the\n * caller site; this primitive does not check roles.\n */\nexport async function persistDirectoryConfig(\n store: NoydbStore,\n vault: string,\n config: DirectoryConfig,\n): Promise<void> {\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: DIRECTORY_RECORD_ID, version: 1 },\n { iv: '', data: JSON.stringify({ enabled: config.enabled }) },\n )\n await store.put(vault, META_COLLECTION, DIRECTORY_RECORD_ID, envelope)\n}\n\nfunction isDirectoryConfig(x: unknown): x is DirectoryConfig {\n if (x === null || typeof x !== 'object') return false\n if (!('enabled' in x)) return false\n return typeof (x as { enabled: unknown }).enabled === 'boolean'\n}\n","/**\n * Persistence helpers for the per-user visibility flag\n * (`_meta/visibility/<keyringId>`). Mirrors the bypass-AES pattern used\n * by `_meta/policy` — the visibility document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * Stored alongside the keyring file rather than inside the encrypted\n * user envelope (`_users/<keyringId>`) because:\n *\n * - `UserEnvelope<T>.data` is opaque-to-hub by contract — hub does not\n * introspect or reserve any keys inside it. Adding `hidden` there\n * would violate that contract.\n * - `listUsersWithEnvelopes` filters by the flag, and the filter must\n * work even when decryption fails (legacy keyrings predating the\n * envelope feature, or a corrupted envelope).\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/user-envelope.md → Directory visibility\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { UserVisibility } from './types.js'\nimport { META_COLLECTION } from './storage.js'\n\n/** Prefix for per-user visibility records inside `_meta`. */\nexport const VISIBILITY_RECORD_PREFIX = 'visibility/'\n\n/** Compose the `_meta` record id for a keyring's visibility doc. */\nexport function visibilityRecordId(keyringId: string): string {\n return VISIBILITY_RECORD_PREFIX + keyringId\n}\n\n/**\n * Read the visibility flag for `keyringId`. Returns `undefined` when no\n * document has been persisted — callers treat that as the default-visible\n * case (`{ hidden: false }`).\n */\nexport async function readUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<UserVisibility | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, visibilityRecordId(keyringId))\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isUserVisibility(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the visibility flag for `keyringId` at\n * `_meta/visibility/<keyringId>`. Idempotent — call on every\n * `vault.user.setMyVisibility()` invocation. Own-only at the caller\n * site; this primitive does not enforce keyring ownership.\n */\nexport async function persistUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n visibility: UserVisibility,\n): Promise<void> {\n const recordId = visibilityRecordId(keyringId)\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: recordId, version: 1 },\n { iv: '', data: JSON.stringify({ hidden: visibility.hidden }) },\n )\n await store.put(vault, META_COLLECTION, recordId, envelope)\n}\n\n/**\n * Delete the visibility flag for `keyringId`. Called from `revoke()`\n * alongside `deleteUserEnvelope` so the sidecar does not leak to a\n * re-granted principal with the same `userId`. Idempotent — the store's\n * `delete()` is already a no-op when the record is absent.\n */\nexport async function deleteUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, META_COLLECTION, visibilityRecordId(keyringId))\n}\n\nfunction isUserVisibility(x: unknown): x is UserVisibility {\n if (x === null || typeof x !== 'object') return false\n if (!('hidden' in x)) return false\n return typeof (x as { hidden: unknown }).hidden === 'boolean'\n}\n","/**\n * Persistence helpers for per-principal user envelopes stored at\n * `_users/<keyringId>` (logically: `_meta/user/<keyringId>`).\n *\n * Unlike `_meta/policy` and `_meta/handle` which are plaintext, user\n * envelopes carry user data and are encrypted with a dedicated\n * {@link USER_ENVELOPE_COLLECTION} DEK (provisioned at vault open and\n * propagated to every keyring via the system-collection DEK path in\n * `team/keyring.ts`).\n *\n * This module is the **storage primitive** layer. The public API\n * (`vault.user.*`) sits on top of this; permission gates, own-only\n * write enforcement, and presence-channel propagation live there.\n *\n * @see design-history/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport type { NoydbStore, UserEnvelope } from '../../../kernel/types.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, openEnvelopeJson, type EnclaveKey } from '../../../kernel/enclave/index.js'\nimport { ConflictError, UserEnvelopeOversizedError } from '../../../kernel/errors.js'\nimport {\n USER_ENVELOPE_COLLECTION,\n USER_ENVELOPE_MAX_BYTES,\n} from '../../../kernel/constants.js'\n\n/**\n * Read and decrypt the user envelope for `keyringId`. Returns `null`\n * when no envelope has been persisted (either the principal has never\n * called `updateMe`, or the keyring predates this feature).\n *\n * Decryption errors propagate — a tampered or wrong-keyed envelope\n * surfaces as the underlying crypto error rather than masquerading as\n * \"not found\".\n */\nexport async function loadUserEnvelope<T = unknown>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n dek: EnclaveKey,\n): Promise<UserEnvelope<T> | null> {\n const envelope = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (!envelope) return null\n const plaintext = await openEnvelopeJson({ collection: USER_ENVELOPE_COLLECTION, id: keyringId }, envelope, dek)\n const data = JSON.parse(plaintext) as T\n return {\n keyringId,\n data,\n _v: envelope._v,\n _ts: envelope._ts,\n }\n}\n\n/**\n * Encrypt and persist the user envelope for `keyringId`. The new\n * version is `(prior._v ?? 0) + 1`. Pass `expectedVersion` to enable\n * optimistic-concurrency checks: a mismatch with the stored version\n * throws {@link ConflictError} with the actual stored version.\n *\n * `expectedVersion: 0` means \"expect no prior envelope\"; the write\n * succeeds only if no envelope exists yet.\n *\n * Soft-caps the JSON-serialized payload at {@link USER_ENVELOPE_MAX_BYTES};\n * larger payloads throw {@link UserEnvelopeOversizedError}.\n */\nexport async function saveUserEnvelope<T>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n payload: T,\n dek: EnclaveKey,\n expectedVersion?: number,\n): Promise<UserEnvelope<T>> {\n const json = JSON.stringify(payload)\n // TextEncoder counts bytes correctly for multi-byte UTF-8 (Thai text,\n // emoji, etc.) — JSON.stringify().length would undercount.\n const bytes = new TextEncoder().encode(json).byteLength\n if (bytes > USER_ENVELOPE_MAX_BYTES) {\n throw new UserEnvelopeOversizedError(bytes)\n }\n\n const prior = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (expectedVersion !== undefined) {\n const priorVersion = prior?._v ?? 0\n if (priorVersion !== expectedVersion) {\n throw new ConflictError(\n priorVersion,\n `User envelope for \"${keyringId}\" expected version ${expectedVersion}, ` +\n `actual ${priorVersion}`,\n )\n }\n }\n\n const nextVersion = (prior?._v ?? 0) + 1\n const ts = new Date().toISOString()\n const identity = { collection: USER_ENVELOPE_COLLECTION, id: keyringId, version: nextVersion }\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n\n const envelope = buildRecordEnvelope(\n identity,\n { ts, iv, data },\n )\n await store.put(vault, USER_ENVELOPE_COLLECTION, keyringId, envelope)\n\n return {\n keyringId,\n data: payload,\n _v: nextVersion,\n _ts: ts,\n }\n}\n\n/**\n * Delete the user envelope for `keyringId`. Idempotent — no error if\n * the envelope is already absent. Called from the keyring revoke path\n * (cascade-delete) and is a no-op for keyrings that never wrote.\n */\nexport async function deleteUserEnvelope(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, USER_ENVELOPE_COLLECTION, keyringId)\n}\n\n/**\n * List the keyring ids that have a user envelope persisted in `vault`.\n * Order is store-defined — callers that need a stable order should sort.\n */\nexport async function listUserEnvelopeIds(\n store: NoydbStore,\n vault: string,\n): Promise<string[]> {\n return store.list(vault, USER_ENVELOPE_COLLECTION)\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
- NOYDB_FORMAT_VERSION
3
- } from "./chunk-YPYHVOXP.js";
2
+ buildRecordEnvelope
3
+ } from "./chunk-FX3UIDCS.js";
4
4
 
5
5
  // src/with-party/policy/storage.ts
6
6
  var META_COLLECTION = "_meta";
@@ -17,13 +17,10 @@ async function loadVaultPolicy(store, vault) {
17
17
  }
18
18
  }
19
19
  async function saveVaultPolicy(store, vault, policy) {
20
- const envelope = {
21
- _noydb: NOYDB_FORMAT_VERSION,
22
- _v: 1,
23
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
24
- _iv: "",
25
- _data: JSON.stringify(policy)
26
- };
20
+ const envelope = buildRecordEnvelope(
21
+ { collection: META_COLLECTION, id: POLICY_RECORD_ID, version: 1 },
22
+ { iv: "", data: JSON.stringify(policy) }
23
+ );
27
24
  await store.put(vault, META_COLLECTION, POLICY_RECORD_ID, envelope);
28
25
  }
29
26
  function isVaultPolicy(x) {
@@ -39,4 +36,4 @@ export {
39
36
  loadVaultPolicy,
40
37
  saveVaultPolicy
41
38
  };
42
- //# sourceMappingURL=chunk-3IXIU6FP.js.map
39
+ //# sourceMappingURL=chunk-ZY6P7M52.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-party/policy/storage.ts"],"sourcesContent":["/**\n * Persistence helpers for the vault-level policy document\n * (`_meta/policy`). Mirrors the bypass-AES pattern used by\n * `_meta/handle` — the policy document is plain JSON, the envelope's\n * `_iv` field is left empty.\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Storage location\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\nimport type { NoydbStore, VaultPolicy } from '../../kernel/types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level policy document. */\nexport const POLICY_RECORD_ID = 'policy'\n\n/**\n * Read the vault-level policy from `_meta/policy`. Returns `undefined`\n * when no policy has been persisted (fresh vault, or a vault written\n * before the policy module landed). The caller falls back to the\n * default preset.\n *\n * Tolerates corrupted documents the same way `_meta/handle` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so\n * a bad write never permanently locks a vault.\n */\nexport async function loadVaultPolicy(\n store: NoydbStore,\n vault: string,\n): Promise<VaultPolicy | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, POLICY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isVaultPolicy(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the vault-level policy at `_meta/policy`. Idempotent — call\n * once at vault creation and again on `db.updatePolicy()` invocations.\n */\nexport async function saveVaultPolicy(\n store: NoydbStore,\n vault: string,\n policy: VaultPolicy,\n): Promise<void> {\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: POLICY_RECORD_ID, version: 1 },\n { iv: '', data: JSON.stringify(policy) },\n )\n await store.put(vault, META_COLLECTION, POLICY_RECORD_ID, envelope)\n}\n\nfunction isVaultPolicy(x: unknown): x is VaultPolicy {\n if (x === null || typeof x !== 'object') return false\n if (!('gates' in x)) return false\n const gates = (x as { gates: unknown }).gates\n return gates !== null && typeof gates === 'object'\n}\n"],"mappings":";;;;;AAcO,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AAYhC,eAAsB,gBACpB,OACA,OACkC;AAClC,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,iBAAiB,gBAAgB;AACzE,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,SAAS,KAAK;AACxC,QAAI,CAAC,cAAc,MAAM,EAAG,QAAO;AACnC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,gBACpB,OACA,OACA,QACe;AACf,QAAM,WAAW;AAAA,IACf,EAAE,YAAY,iBAAiB,IAAI,kBAAkB,SAAS,EAAE;AAAA,IAChE,EAAE,IAAI,IAAI,MAAM,KAAK,UAAU,MAAM,EAAE;AAAA,EACzC;AACA,QAAM,MAAM,IAAI,OAAO,iBAAiB,kBAAkB,QAAQ;AACpE;AAEA,SAAS,cAAc,GAA8B;AACnD,MAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,MAAI,EAAE,WAAW,GAAI,QAAO;AAC5B,QAAM,QAAS,EAAyB;AACxC,SAAO,UAAU,QAAQ,OAAO,UAAU;AAC5C;","names":[]}
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  createNoydb
3
- } from "./chunk-2GAJGUNM.js";
3
+ } from "./chunk-JYPRZWCZ.js";
4
4
  import {
5
5
  deriveSchemaManifest
6
- } from "./chunk-2DHSSLOD.js";
6
+ } from "./chunk-DECNV4QK.js";
7
7
  import {
8
8
  loadFence
9
- } from "./chunk-6BU3L4UE.js";
9
+ } from "./chunk-DNKVKO7K.js";
10
10
  import {
11
11
  readPod,
12
12
  readPodRedirect,
13
13
  signRecord,
14
14
  verifyPodHeader,
15
15
  verifyRecord
16
- } from "./chunk-4VUPLMZ6.js";
16
+ } from "./chunk-GCU42IG7.js";
17
17
  import {
18
18
  loadKeyring
19
- } from "./chunk-JRKZTEZZ.js";
19
+ } from "./chunk-J6I5VR6P.js";
20
20
  import {
21
21
  ConflictError,
22
22
  MigrationRequiredError,
@@ -26,7 +26,7 @@ import {
26
26
  RedirectDepthExceededError,
27
27
  RedirectLoopError,
28
28
  RedirectUnreachableError
29
- } from "./chunk-HNGSV2YC.js";
29
+ } from "./chunk-Z3BZXPRO.js";
30
30
 
31
31
  // src/with-pod/pod-store.ts
32
32
  var BUNDLE_STORE_VERSION = 1;
@@ -192,8 +192,6 @@ function mergeSnapshots(remote, local) {
192
192
  function createPodStore(factory) {
193
193
  return factory;
194
194
  }
195
- var wrapBundleStore = wrapPodStore;
196
- var createBundleStore = createPodStore;
197
195
 
198
196
  // src/with-pod/redirect.ts
199
197
  async function signRedirect(signer, fields) {
@@ -283,11 +281,9 @@ async function open(podFileOrBytes, opts) {
283
281
  export {
284
282
  wrapPodStore,
285
283
  createPodStore,
286
- wrapBundleStore,
287
- createBundleStore,
288
284
  signRedirect,
289
285
  verifyRedirect,
290
286
  followRedirects,
291
287
  open
292
288
  };
293
- //# sourceMappingURL=chunk-BWVKEOPE.js.map
289
+ //# sourceMappingURL=chunk-ZYZJLYNS.js.map