@noy-db/hub 0.6.0-pre.17 → 0.6.0-pre.19

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 (426) hide show
  1. package/dist/api-Z5VHH24A.js +20 -0
  2. package/dist/attestation/index.js +18 -18
  3. package/dist/{backup-7X3PFTSI.js → backup-K62BFMYK.js} +16 -16
  4. package/dist/{backup-7X3PFTSI.js.map → backup-K62BFMYK.js.map} +1 -1
  5. package/dist/blobs/index.js +13 -13
  6. package/dist/broker/index.js +12 -12
  7. package/dist/cargo/index.js +32 -32
  8. package/dist/{chunk-5SK5AFYH.js → chunk-2E2MKBMI.js} +2 -2
  9. package/dist/{chunk-SDEHNGWW.js → chunk-2IK6AC6A.js} +7 -6
  10. package/dist/{chunk-SDEHNGWW.js.map → chunk-2IK6AC6A.js.map} +1 -1
  11. package/dist/{chunk-7TZTQS4H.js → chunk-32O2N2CH.js} +48 -21
  12. package/dist/chunk-32O2N2CH.js.map +1 -0
  13. package/dist/{chunk-QKRJUPAC.js → chunk-37P5VB4N.js} +13 -16
  14. package/dist/chunk-37P5VB4N.js.map +1 -0
  15. package/dist/{chunk-5UGEGEIJ.js → chunk-3Y4OFWQJ.js} +1 -1
  16. package/dist/chunk-3Y4OFWQJ.js.map +1 -0
  17. package/dist/{chunk-7LLKPODL.js → chunk-437FDIHO.js} +3 -3
  18. package/dist/{chunk-AMKCY454.js → chunk-47ZFULEZ.js} +8 -3
  19. package/dist/{chunk-AMKCY454.js.map → chunk-47ZFULEZ.js.map} +1 -1
  20. package/dist/{chunk-4GR422R5.js → chunk-4HVTRHSA.js} +16 -9
  21. package/dist/chunk-4HVTRHSA.js.map +1 -0
  22. package/dist/{chunk-VEA35MQP.js → chunk-5BS6IQZZ.js} +11 -14
  23. package/dist/chunk-5BS6IQZZ.js.map +1 -0
  24. package/dist/{chunk-QWNYUTI2.js → chunk-5EJQNA26.js} +2 -2
  25. package/dist/{chunk-C66T4W5P.js → chunk-5J5TTSGW.js} +3 -3
  26. package/dist/{chunk-JHYISAID.js → chunk-5JT4Y5UE.js} +10 -10
  27. package/dist/{chunk-VUQZYVYX.js → chunk-6S466CMQ.js} +92 -35
  28. package/dist/chunk-6S466CMQ.js.map +1 -0
  29. package/dist/{chunk-IRHOQFA7.js → chunk-7RCZ436O.js} +2 -2
  30. package/dist/{chunk-42IDLU5V.js → chunk-A3NTKHDB.js} +2 -2
  31. package/dist/{chunk-3JQVA527.js → chunk-A4K5GLE6.js} +4 -4
  32. package/dist/{chunk-K2MR7ROB.js → chunk-APV42WPR.js} +2 -2
  33. package/dist/{chunk-MVG67SMN.js → chunk-AQF7JSQR.js} +2 -2
  34. package/dist/{chunk-VTTRWLOR.js → chunk-BP4I7IBK.js} +2 -2
  35. package/dist/{chunk-T7AEAFEE.js → chunk-BTNKPDZM.js} +3 -3
  36. package/dist/{chunk-PCXAUC65.js → chunk-C7WJNVJG.js} +2 -2
  37. package/dist/{chunk-UNR6G3EJ.js → chunk-CV7WXCZD.js} +37 -33
  38. package/dist/chunk-CV7WXCZD.js.map +1 -0
  39. package/dist/{chunk-67XA44HH.js → chunk-CVYXIN5B.js} +3 -3
  40. package/dist/{chunk-BKYQNNO3.js → chunk-D5UDZANO.js} +203 -137
  41. package/dist/chunk-D5UDZANO.js.map +1 -0
  42. package/dist/{chunk-HVR74IBG.js → chunk-DJ23QZIN.js} +2 -2
  43. package/dist/{chunk-BMDF4TLU.js → chunk-DKYEVHZH.js} +7 -10
  44. package/dist/chunk-DKYEVHZH.js.map +1 -0
  45. package/dist/{chunk-N7HOHWKF.js → chunk-DYASJSKI.js} +8 -11
  46. package/dist/{chunk-N7HOHWKF.js.map → chunk-DYASJSKI.js.map} +1 -1
  47. package/dist/{chunk-OAWVM5L6.js → chunk-E6VDPBE3.js} +13 -15
  48. package/dist/chunk-E6VDPBE3.js.map +1 -0
  49. package/dist/{chunk-WBHQX6ME.js → chunk-E6WANBD6.js} +14 -15
  50. package/dist/chunk-E6WANBD6.js.map +1 -0
  51. package/dist/{chunk-VQQUOEON.js → chunk-EG6AQBYW.js} +10 -10
  52. package/dist/{chunk-GWI65NDQ.js → chunk-EJIVFOOQ.js} +6 -6
  53. package/dist/{chunk-YGJZLOSR.js → chunk-GDGTIWB4.js} +2 -2
  54. package/dist/{chunk-TQ7IRULX.js → chunk-GILI76SU.js} +2 -2
  55. package/dist/{chunk-4444HN72.js → chunk-GT72D4FO.js} +3 -3
  56. package/dist/{chunk-OXRPH6AR.js → chunk-GYUCRDAU.js} +15 -12
  57. package/dist/chunk-GYUCRDAU.js.map +1 -0
  58. package/dist/{chunk-JV2CNEX2.js → chunk-HGAR7H5M.js} +239 -188
  59. package/dist/chunk-HGAR7H5M.js.map +1 -0
  60. package/dist/{chunk-SGEI3IJK.js → chunk-HGJC7I5F.js} +2 -2
  61. package/dist/{chunk-C52CJDFO.js → chunk-HJRTFXAA.js} +7 -10
  62. package/dist/chunk-HJRTFXAA.js.map +1 -0
  63. package/dist/chunk-HK67PX4J.js +27 -0
  64. package/dist/chunk-HK67PX4J.js.map +1 -0
  65. package/dist/{chunk-V4M4BZNW.js → chunk-HTHRCGWP.js} +2 -2
  66. package/dist/{chunk-YVOW35HH.js → chunk-HTMQBBF3.js} +5 -7
  67. package/dist/chunk-HTMQBBF3.js.map +1 -0
  68. package/dist/{chunk-QC2HGYFX.js → chunk-HUYNPLTT.js} +4 -4
  69. package/dist/{chunk-APUP2J5M.js → chunk-I26TE27S.js} +7 -7
  70. package/dist/{chunk-DEP24EE3.js → chunk-ICCRAPWD.js} +4 -4
  71. package/dist/{chunk-ES36R3U2.js → chunk-IOURTXTM.js} +52 -65
  72. package/dist/chunk-IOURTXTM.js.map +1 -0
  73. package/dist/{chunk-JU3JKOY5.js → chunk-IPDS2TYW.js} +3 -3
  74. package/dist/{chunk-QJBXB4WU.js → chunk-JHO6FGZX.js} +25 -31
  75. package/dist/chunk-JHO6FGZX.js.map +1 -0
  76. package/dist/{chunk-YDJXGYYA.js → chunk-JIF3IBRK.js} +2 -2
  77. package/dist/{chunk-S6CP5FWF.js → chunk-KBOPY6W2.js} +2 -2
  78. package/dist/{chunk-RVTOCAHQ.js → chunk-KDX6VEYV.js} +2 -2
  79. package/dist/chunk-KDX6VEYV.js.map +1 -0
  80. package/dist/{chunk-H545OIWE.js → chunk-KM6YR5XF.js} +2 -2
  81. package/dist/{chunk-AFSMT3WI.js → chunk-KUXTWVAR.js} +3 -3
  82. package/dist/{chunk-466KGOQB.js → chunk-L2XQ776M.js} +2 -2
  83. package/dist/{chunk-QI2KXQOM.js → chunk-L6FSZFG7.js} +2 -2
  84. package/dist/{chunk-EEBMKMA7.js → chunk-LAFP5H5U.js} +35 -47
  85. package/dist/chunk-LAFP5H5U.js.map +1 -0
  86. package/dist/{chunk-I7JF5TNL.js → chunk-LBDTQM3R.js} +3 -3
  87. package/dist/{chunk-V3OLO4TK.js → chunk-M4IKEOJS.js} +20 -17
  88. package/dist/chunk-M4IKEOJS.js.map +1 -0
  89. package/dist/{chunk-5NKSNW25.js → chunk-MNMYHZVU.js} +2 -2
  90. package/dist/{chunk-IEA3TZJE.js → chunk-MNUTPZRQ.js} +31 -49
  91. package/dist/chunk-MNUTPZRQ.js.map +1 -0
  92. package/dist/{chunk-Z2OFE6IX.js → chunk-MQXYR65M.js} +15 -24
  93. package/dist/chunk-MQXYR65M.js.map +1 -0
  94. package/dist/{chunk-5VVBZ6E4.js → chunk-N2HKBESK.js} +4 -4
  95. package/dist/{chunk-HNOBRU24.js → chunk-N7YAQKAO.js} +2 -2
  96. package/dist/{chunk-6U7M7AX6.js → chunk-NH2MST6J.js} +2 -2
  97. package/dist/{chunk-JAHIL6WO.js → chunk-NRNTXBHT.js} +7 -10
  98. package/dist/chunk-NRNTXBHT.js.map +1 -0
  99. package/dist/{chunk-URVL4EKU.js → chunk-NYSD43OE.js} +15 -24
  100. package/dist/chunk-NYSD43OE.js.map +1 -0
  101. package/dist/{chunk-LUFZPROB.js → chunk-ODVIFBAQ.js} +16 -17
  102. package/dist/chunk-ODVIFBAQ.js.map +1 -0
  103. package/dist/{chunk-SJOK23XT.js → chunk-OIIAXFKM.js} +4 -4
  104. package/dist/chunk-OIIAXFKM.js.map +1 -0
  105. package/dist/{chunk-FSZPQCDR.js → chunk-OQLYMBJI.js} +3 -3
  106. package/dist/{chunk-EMFFWSAD.js → chunk-OWGEHDT2.js} +10 -10
  107. package/dist/chunk-OWGEHDT2.js.map +1 -0
  108. package/dist/{chunk-I64WQSN3.js → chunk-P7L3OQLL.js} +19 -2
  109. package/dist/chunk-P7L3OQLL.js.map +1 -0
  110. package/dist/{chunk-FZ2SSI5R.js → chunk-PG6PFMDC.js} +2 -2
  111. package/dist/{chunk-2RGQNFJB.js → chunk-PZSS7MUM.js} +11 -9
  112. package/dist/chunk-PZSS7MUM.js.map +1 -0
  113. package/dist/{chunk-UKKBKEUI.js → chunk-QPIUFRUC.js} +3 -3
  114. package/dist/{chunk-RJTGXZT6.js → chunk-REL4OLWW.js} +2 -2
  115. package/dist/{chunk-N4GAJSRQ.js → chunk-RNSC2ARR.js} +3 -3
  116. package/dist/{chunk-RWPNRCK2.js → chunk-RORE2GX2.js} +2 -2
  117. package/dist/{chunk-YH65A6WM.js → chunk-RSFG7XHM.js} +2 -2
  118. package/dist/{chunk-QLMUW7OC.js → chunk-S55DBWRC.js} +2 -2
  119. package/dist/{chunk-AKFNLRA2.js → chunk-SA77BQ7F.js} +27 -143
  120. package/dist/chunk-SA77BQ7F.js.map +1 -0
  121. package/dist/{chunk-RBTZLXZJ.js → chunk-SFD2LL7G.js} +2 -2
  122. package/dist/{chunk-XTC5EKST.js → chunk-SG3OQCRG.js} +3 -3
  123. package/dist/{chunk-JN64WH3U.js → chunk-T7IR4N2S.js} +1 -1
  124. package/dist/{chunk-JN64WH3U.js.map → chunk-T7IR4N2S.js.map} +1 -1
  125. package/dist/{chunk-P5ARVLNW.js → chunk-TC6MBEGH.js} +11 -8
  126. package/dist/chunk-TC6MBEGH.js.map +1 -0
  127. package/dist/{chunk-D6E5N2O3.js → chunk-TENLCGYD.js} +12 -10
  128. package/dist/{chunk-D6E5N2O3.js.map → chunk-TENLCGYD.js.map} +1 -1
  129. package/dist/{chunk-X2FXEEQT.js → chunk-VH2W6BOG.js} +7 -7
  130. package/dist/{chunk-FHFMWYGM.js → chunk-VHVMJWM6.js} +7 -7
  131. package/dist/{chunk-FHFMWYGM.js.map → chunk-VHVMJWM6.js.map} +1 -1
  132. package/dist/{chunk-QTDAPDDV.js → chunk-VJ4B3ZXY.js} +15 -3
  133. package/dist/chunk-VJ4B3ZXY.js.map +1 -0
  134. package/dist/{chunk-RDPZMUCX.js → chunk-W6NFONOV.js} +2 -2
  135. package/dist/{chunk-Z3U2YVNM.js → chunk-WBNXZU42.js} +4 -4
  136. package/dist/{chunk-7EIPPOEL.js → chunk-WKD5ESK4.js} +2 -2
  137. package/dist/{chunk-6VAM5THN.js → chunk-YEZW6UJA.js} +15 -15
  138. package/dist/{chunk-6VAM5THN.js.map → chunk-YEZW6UJA.js.map} +1 -1
  139. package/dist/chunk-YOT7D63X.js +190 -0
  140. package/dist/chunk-YOT7D63X.js.map +1 -0
  141. package/dist/{chunk-6JRIYG2C.js → chunk-YVTADFTC.js} +14 -15
  142. package/dist/chunk-YVTADFTC.js.map +1 -0
  143. package/dist/{chunk-C3YP5KKS.js → chunk-ZBDWKPBW.js} +15 -16
  144. package/dist/chunk-ZBDWKPBW.js.map +1 -0
  145. package/dist/{chunk-NWPR2V5W.js → chunk-ZPMXYPBR.js} +2 -2
  146. package/dist/{chunk-RGPB25DB.js → chunk-ZVAFJQZB.js} +25 -19
  147. package/dist/chunk-ZVAFJQZB.js.map +1 -0
  148. package/dist/{chunk-TQLYMYOK.js → chunk-ZWKF3CD3.js} +2 -2
  149. package/dist/classified/index.js +5 -5
  150. package/dist/{classified-marker-W2PUYC3K.js → classified-marker-PMDG3O36.js} +3 -3
  151. package/dist/collection-facade-ZJK5EB33.js +45 -0
  152. package/dist/{computed-5E2OM6EN.js → computed-UHCBRILW.js} +3 -3
  153. package/dist/consent/index.js +9 -9
  154. package/dist/cover/index.js +10 -3
  155. package/dist/custody/index.js +11 -11
  156. package/dist/{dead-filter-MVFQCLXY.js → dead-filter-777VWDER.js} +2 -2
  157. package/dist/debug/index.js +1 -1
  158. package/dist/{delegation-I7XXO5LL.js → delegation-4R5DU23X.js} +23 -24
  159. package/dist/delegation-4R5DU23X.js.map +1 -0
  160. package/dist/derivations/index.js +12 -12
  161. package/dist/derive-3WAS2G5T.js +22 -0
  162. package/dist/directory/index.js +10 -10
  163. package/dist/{dispatch-PY5RUHJF.js → dispatch-3EN6YCCS.js} +18 -18
  164. package/dist/{dispatch-K3MDLCXG.js → dispatch-ASL257KG.js} +5 -5
  165. package/dist/{enclave-77K7UDLS.js → enclave-ZJS5R5RH.js} +17 -9
  166. package/dist/executor-GPM6DNWV.js +9 -0
  167. package/dist/executor-LJSMZ4IB.js +9 -0
  168. package/dist/executor-SHUVRZUJ.js +29 -0
  169. package/dist/export-accessible-GNY6DC6U.js +23 -0
  170. package/dist/extract-partition-BG3AF4NZ.js +38 -0
  171. package/dist/{fanout-sidecar-JI3MUPQV.js → fanout-sidecar-TFPWDOME.js} +18 -27
  172. package/dist/fanout-sidecar-TFPWDOME.js.map +1 -0
  173. package/dist/find-KX6OKHDP.js +11 -0
  174. package/dist/forget/index.js +9 -9
  175. package/dist/guards/index.js +4 -4
  176. package/dist/history/index.js +23 -20
  177. package/dist/history/index.js.map +1 -1
  178. package/dist/i18n/index.js +14 -14
  179. package/dist/index.d.ts +16 -0
  180. package/dist/index.js +156 -154
  181. package/dist/index.js.map +1 -1
  182. package/dist/indexing/index.js +2 -2
  183. package/dist/introspection/index.js +11 -11
  184. package/dist/issue-PAUM7NCX.js +19 -0
  185. package/dist/kernel/enclave/index.d.ts +6 -5
  186. package/dist/kernel/enclave/record-aad.d.ts +79 -7
  187. package/dist/kernel/enclave/record-envelope.d.ts +54 -7
  188. package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
  189. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
  190. package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
  191. package/dist/kernel/enclave/record-keys/rekey.d.ts +8 -2
  192. package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -3
  193. package/dist/kernel/errors.d.ts +17 -1
  194. package/dist/kernel/merge-authority.d.ts +25 -0
  195. package/dist/kernel/tier-visibility.d.ts +15 -0
  196. package/dist/kernel/types.d.ts +8 -0
  197. package/dist/kernel/vault.d.ts +1 -1
  198. package/dist/kernel/via/index.d.ts +11 -2
  199. package/dist/{ledger-3RDSSMGD.js → ledger-62CEPRPX.js} +11 -11
  200. package/dist/liberate-7QY7TULB.js +24 -0
  201. package/dist/{link-set-VJIWWUZO.js → link-set-ZRYNUHN3.js} +11 -11
  202. package/dist/{managed-secret-XGGJ3BC3.js → managed-secret-T2WB4O47.js} +11 -3
  203. package/dist/materialized-views/index.js +22 -22
  204. package/dist/money/index.js +4 -4
  205. package/dist/noydb-VZIVPDXG.js +84 -0
  206. package/dist/overlay-views/index.js +4 -4
  207. package/dist/periods/index.js +12 -12
  208. package/dist/pod/index.js +58 -57
  209. package/dist/{pod-handle-7BVSWP5B.js → pod-handle-UPLXOGIK.js} +11 -11
  210. package/dist/{pod-handle-7BVSWP5B.js.map → pod-handle-UPLXOGIK.js.map} +1 -1
  211. package/dist/policy/index.js +11 -4
  212. package/dist/port/with/merge-authority.d.ts +52 -0
  213. package/dist/port/with/strategies.d.ts +2 -0
  214. package/dist/portability/index.js +8 -8
  215. package/dist/{post-register-W3T4U57K.js → post-register-J4DEVNXK.js} +6 -6
  216. package/dist/query/index.js +3 -3
  217. package/dist/reduce/index.js +4 -4
  218. package/dist/register-UPD4GKBC.js +32 -0
  219. package/dist/registry-7KX2HWFS.js +18 -0
  220. package/dist/registry-PKYYEHEW.js +9 -0
  221. package/dist/registry-XHCYMD34.js +19 -0
  222. package/dist/registry-XIRTZHPC.js +9 -0
  223. package/dist/request-withdrawal-XZSESWZV.js +31 -0
  224. package/dist/{reveal-G7W7N3AZ.js → reveal-525XWQU2.js} +6 -6
  225. package/dist/revoke-QI7YHQ2B.js +24 -0
  226. package/dist/satellites/index.js +1 -1
  227. package/dist/schema-update/index.js +2 -2
  228. package/dist/sealed-record/index.js +20 -13
  229. package/dist/sealed-record/index.js.map +1 -1
  230. package/dist/search/index.js +4 -4
  231. package/dist/{seed-2FUULTQV.js → seed-2KZDTFS4.js} +19 -22
  232. package/dist/seed-2KZDTFS4.js.map +1 -0
  233. package/dist/sequence/index.js +9 -9
  234. package/dist/session/index.js +9 -9
  235. package/dist/shadow/index.js +2 -2
  236. package/dist/signer-BZWDPGGN.js +25 -0
  237. package/dist/snapshots/index.js +10 -10
  238. package/dist/{stale-42FPALK2.js → stale-PQYPVCER.js} +10 -10
  239. package/dist/storage-MJHKNQRO.js +23 -0
  240. package/dist/{store-coordination-provider-Y3VXBHPL.js → store-coordination-provider-TFHZLKHG.js} +18 -12
  241. package/dist/store-coordination-provider-TFHZLKHG.js.map +1 -0
  242. package/dist/sync/index.js +11 -11
  243. package/dist/team/index.js +15 -14
  244. package/dist/tiers/index.js +11 -10
  245. package/dist/to/index.js +1 -1
  246. package/dist/transactions/index.js +3 -3
  247. package/dist/util/index.js +1 -1
  248. package/dist/vault-head/index.js +111 -0
  249. package/dist/vault-head/index.js.map +1 -0
  250. package/dist/{verify-SHJXGZGM.js → verify-J6DEOXP3.js} +5 -5
  251. package/dist/walk-BX4NSB5L.js +21 -0
  252. package/dist/with-audit/periods/periods.d.ts +1 -1
  253. package/dist/with-audit/periods/strategy.d.ts +1 -1
  254. package/dist/with-audit/sealed-record/index.d.ts +13 -0
  255. package/dist/with-audit/tiers/index.d.ts +17 -2
  256. package/dist/with-commit/history/history.d.ts +26 -0
  257. package/dist/with-commit/history/index.d.ts +2 -1
  258. package/dist/with-commit/history/ledger/store.d.ts +7 -1
  259. package/dist/with-commit/history/strategy.d.ts +11 -1
  260. package/dist/with-commit/vault-head/active.d.ts +2 -0
  261. package/dist/with-commit/vault-head/head.d.ts +28 -0
  262. package/dist/with-commit/vault-head/index.d.ts +10 -0
  263. package/dist/with-commit/vault-head/strategy.d.ts +127 -0
  264. package/dist/with-commit/vault-head/verify.d.ts +50 -0
  265. package/dist/with-party/directory/cover/storage.d.ts +0 -8
  266. package/dist/with-party/directory/storage.d.ts +0 -11
  267. package/dist/with-party/directory/visibility.d.ts +0 -21
  268. package/dist/with-party/policy/storage.d.ts +0 -10
  269. package/dist/with-party/team/tiers.d.ts +2 -1
  270. package/dist/with-sync/engine.d.ts +33 -5
  271. package/dist/with-sync/strategy.d.ts +3 -0
  272. package/dist/withdraw-accessible-K4CPMH4C.js +28 -0
  273. package/package.json +7 -3
  274. package/dist/api-AEIN3KAQ.js +0 -20
  275. package/dist/chunk-2RGQNFJB.js.map +0 -1
  276. package/dist/chunk-4GR422R5.js.map +0 -1
  277. package/dist/chunk-5UGEGEIJ.js.map +0 -1
  278. package/dist/chunk-6JRIYG2C.js.map +0 -1
  279. package/dist/chunk-7TZTQS4H.js.map +0 -1
  280. package/dist/chunk-AFHQATY4.js +0 -47
  281. package/dist/chunk-AFHQATY4.js.map +0 -1
  282. package/dist/chunk-AKFNLRA2.js.map +0 -1
  283. package/dist/chunk-BKYQNNO3.js.map +0 -1
  284. package/dist/chunk-BMDF4TLU.js.map +0 -1
  285. package/dist/chunk-C3YP5KKS.js.map +0 -1
  286. package/dist/chunk-C52CJDFO.js.map +0 -1
  287. package/dist/chunk-EEBMKMA7.js.map +0 -1
  288. package/dist/chunk-EMFFWSAD.js.map +0 -1
  289. package/dist/chunk-ES36R3U2.js.map +0 -1
  290. package/dist/chunk-I64WQSN3.js.map +0 -1
  291. package/dist/chunk-IEA3TZJE.js.map +0 -1
  292. package/dist/chunk-JAHIL6WO.js.map +0 -1
  293. package/dist/chunk-JV2CNEX2.js.map +0 -1
  294. package/dist/chunk-LUFZPROB.js.map +0 -1
  295. package/dist/chunk-OAWVM5L6.js.map +0 -1
  296. package/dist/chunk-OXRPH6AR.js.map +0 -1
  297. package/dist/chunk-P5ARVLNW.js.map +0 -1
  298. package/dist/chunk-QJBXB4WU.js.map +0 -1
  299. package/dist/chunk-QKRJUPAC.js.map +0 -1
  300. package/dist/chunk-QTDAPDDV.js.map +0 -1
  301. package/dist/chunk-RGPB25DB.js.map +0 -1
  302. package/dist/chunk-RVTOCAHQ.js.map +0 -1
  303. package/dist/chunk-SJOK23XT.js.map +0 -1
  304. package/dist/chunk-UNR6G3EJ.js.map +0 -1
  305. package/dist/chunk-URVL4EKU.js.map +0 -1
  306. package/dist/chunk-V3OLO4TK.js.map +0 -1
  307. package/dist/chunk-VEA35MQP.js.map +0 -1
  308. package/dist/chunk-VUQZYVYX.js.map +0 -1
  309. package/dist/chunk-WBHQX6ME.js.map +0 -1
  310. package/dist/chunk-YVOW35HH.js.map +0 -1
  311. package/dist/chunk-Z2OFE6IX.js.map +0 -1
  312. package/dist/collection-facade-CRXN72DU.js +0 -45
  313. package/dist/delegation-I7XXO5LL.js.map +0 -1
  314. package/dist/derive-NL7X2EFT.js +0 -22
  315. package/dist/executor-FNGVGDW3.js +0 -29
  316. package/dist/executor-PCZN2LTY.js +0 -9
  317. package/dist/executor-WFTHIDT7.js +0 -9
  318. package/dist/export-accessible-QWUYY2OY.js +0 -23
  319. package/dist/extract-partition-CGCURVS4.js +0 -38
  320. package/dist/fanout-sidecar-JI3MUPQV.js.map +0 -1
  321. package/dist/find-VBNMB42N.js +0 -11
  322. package/dist/issue-7IPUNAFA.js +0 -19
  323. package/dist/liberate-2DNJ7AJ7.js +0 -24
  324. package/dist/noydb-CLQP36JR.js +0 -83
  325. package/dist/register-YUVC4G27.js +0 -32
  326. package/dist/registry-BPKJX562.js +0 -19
  327. package/dist/registry-E4FM32KD.js +0 -9
  328. package/dist/registry-HACMULJJ.js +0 -9
  329. package/dist/registry-HRFRBFDV.js +0 -18
  330. package/dist/request-withdrawal-CXTQLMJK.js +0 -31
  331. package/dist/revoke-ZX3TZUI6.js +0 -24
  332. package/dist/seed-2FUULTQV.js.map +0 -1
  333. package/dist/signer-FPUIX63Z.js +0 -25
  334. package/dist/storage-6QODT6K5.js +0 -23
  335. package/dist/store-coordination-provider-Y3VXBHPL.js.map +0 -1
  336. package/dist/walk-5PGJI3ND.js +0 -21
  337. package/dist/withdraw-accessible-PFCQQGNP.js +0 -28
  338. /package/dist/{api-AEIN3KAQ.js.map → api-Z5VHH24A.js.map} +0 -0
  339. /package/dist/{chunk-5SK5AFYH.js.map → chunk-2E2MKBMI.js.map} +0 -0
  340. /package/dist/{chunk-7LLKPODL.js.map → chunk-437FDIHO.js.map} +0 -0
  341. /package/dist/{chunk-QWNYUTI2.js.map → chunk-5EJQNA26.js.map} +0 -0
  342. /package/dist/{chunk-C66T4W5P.js.map → chunk-5J5TTSGW.js.map} +0 -0
  343. /package/dist/{chunk-JHYISAID.js.map → chunk-5JT4Y5UE.js.map} +0 -0
  344. /package/dist/{chunk-IRHOQFA7.js.map → chunk-7RCZ436O.js.map} +0 -0
  345. /package/dist/{chunk-42IDLU5V.js.map → chunk-A3NTKHDB.js.map} +0 -0
  346. /package/dist/{chunk-3JQVA527.js.map → chunk-A4K5GLE6.js.map} +0 -0
  347. /package/dist/{chunk-K2MR7ROB.js.map → chunk-APV42WPR.js.map} +0 -0
  348. /package/dist/{chunk-MVG67SMN.js.map → chunk-AQF7JSQR.js.map} +0 -0
  349. /package/dist/{chunk-VTTRWLOR.js.map → chunk-BP4I7IBK.js.map} +0 -0
  350. /package/dist/{chunk-T7AEAFEE.js.map → chunk-BTNKPDZM.js.map} +0 -0
  351. /package/dist/{chunk-PCXAUC65.js.map → chunk-C7WJNVJG.js.map} +0 -0
  352. /package/dist/{chunk-67XA44HH.js.map → chunk-CVYXIN5B.js.map} +0 -0
  353. /package/dist/{chunk-HVR74IBG.js.map → chunk-DJ23QZIN.js.map} +0 -0
  354. /package/dist/{chunk-VQQUOEON.js.map → chunk-EG6AQBYW.js.map} +0 -0
  355. /package/dist/{chunk-GWI65NDQ.js.map → chunk-EJIVFOOQ.js.map} +0 -0
  356. /package/dist/{chunk-YGJZLOSR.js.map → chunk-GDGTIWB4.js.map} +0 -0
  357. /package/dist/{chunk-TQ7IRULX.js.map → chunk-GILI76SU.js.map} +0 -0
  358. /package/dist/{chunk-4444HN72.js.map → chunk-GT72D4FO.js.map} +0 -0
  359. /package/dist/{chunk-SGEI3IJK.js.map → chunk-HGJC7I5F.js.map} +0 -0
  360. /package/dist/{chunk-V4M4BZNW.js.map → chunk-HTHRCGWP.js.map} +0 -0
  361. /package/dist/{chunk-QC2HGYFX.js.map → chunk-HUYNPLTT.js.map} +0 -0
  362. /package/dist/{chunk-APUP2J5M.js.map → chunk-I26TE27S.js.map} +0 -0
  363. /package/dist/{chunk-DEP24EE3.js.map → chunk-ICCRAPWD.js.map} +0 -0
  364. /package/dist/{chunk-JU3JKOY5.js.map → chunk-IPDS2TYW.js.map} +0 -0
  365. /package/dist/{chunk-YDJXGYYA.js.map → chunk-JIF3IBRK.js.map} +0 -0
  366. /package/dist/{chunk-S6CP5FWF.js.map → chunk-KBOPY6W2.js.map} +0 -0
  367. /package/dist/{chunk-H545OIWE.js.map → chunk-KM6YR5XF.js.map} +0 -0
  368. /package/dist/{chunk-AFSMT3WI.js.map → chunk-KUXTWVAR.js.map} +0 -0
  369. /package/dist/{chunk-466KGOQB.js.map → chunk-L2XQ776M.js.map} +0 -0
  370. /package/dist/{chunk-QI2KXQOM.js.map → chunk-L6FSZFG7.js.map} +0 -0
  371. /package/dist/{chunk-I7JF5TNL.js.map → chunk-LBDTQM3R.js.map} +0 -0
  372. /package/dist/{chunk-5NKSNW25.js.map → chunk-MNMYHZVU.js.map} +0 -0
  373. /package/dist/{chunk-5VVBZ6E4.js.map → chunk-N2HKBESK.js.map} +0 -0
  374. /package/dist/{chunk-HNOBRU24.js.map → chunk-N7YAQKAO.js.map} +0 -0
  375. /package/dist/{chunk-6U7M7AX6.js.map → chunk-NH2MST6J.js.map} +0 -0
  376. /package/dist/{chunk-FSZPQCDR.js.map → chunk-OQLYMBJI.js.map} +0 -0
  377. /package/dist/{chunk-FZ2SSI5R.js.map → chunk-PG6PFMDC.js.map} +0 -0
  378. /package/dist/{chunk-UKKBKEUI.js.map → chunk-QPIUFRUC.js.map} +0 -0
  379. /package/dist/{chunk-RJTGXZT6.js.map → chunk-REL4OLWW.js.map} +0 -0
  380. /package/dist/{chunk-N4GAJSRQ.js.map → chunk-RNSC2ARR.js.map} +0 -0
  381. /package/dist/{chunk-RWPNRCK2.js.map → chunk-RORE2GX2.js.map} +0 -0
  382. /package/dist/{chunk-YH65A6WM.js.map → chunk-RSFG7XHM.js.map} +0 -0
  383. /package/dist/{chunk-QLMUW7OC.js.map → chunk-S55DBWRC.js.map} +0 -0
  384. /package/dist/{chunk-RBTZLXZJ.js.map → chunk-SFD2LL7G.js.map} +0 -0
  385. /package/dist/{chunk-XTC5EKST.js.map → chunk-SG3OQCRG.js.map} +0 -0
  386. /package/dist/{chunk-X2FXEEQT.js.map → chunk-VH2W6BOG.js.map} +0 -0
  387. /package/dist/{chunk-RDPZMUCX.js.map → chunk-W6NFONOV.js.map} +0 -0
  388. /package/dist/{chunk-Z3U2YVNM.js.map → chunk-WBNXZU42.js.map} +0 -0
  389. /package/dist/{chunk-7EIPPOEL.js.map → chunk-WKD5ESK4.js.map} +0 -0
  390. /package/dist/{chunk-NWPR2V5W.js.map → chunk-ZPMXYPBR.js.map} +0 -0
  391. /package/dist/{chunk-TQLYMYOK.js.map → chunk-ZWKF3CD3.js.map} +0 -0
  392. /package/dist/{classified-marker-W2PUYC3K.js.map → classified-marker-PMDG3O36.js.map} +0 -0
  393. /package/dist/{collection-facade-CRXN72DU.js.map → collection-facade-ZJK5EB33.js.map} +0 -0
  394. /package/dist/{computed-5E2OM6EN.js.map → computed-UHCBRILW.js.map} +0 -0
  395. /package/dist/{dead-filter-MVFQCLXY.js.map → dead-filter-777VWDER.js.map} +0 -0
  396. /package/dist/{derive-NL7X2EFT.js.map → derive-3WAS2G5T.js.map} +0 -0
  397. /package/dist/{dispatch-PY5RUHJF.js.map → dispatch-3EN6YCCS.js.map} +0 -0
  398. /package/dist/{dispatch-K3MDLCXG.js.map → dispatch-ASL257KG.js.map} +0 -0
  399. /package/dist/{enclave-77K7UDLS.js.map → enclave-ZJS5R5RH.js.map} +0 -0
  400. /package/dist/{executor-FNGVGDW3.js.map → executor-GPM6DNWV.js.map} +0 -0
  401. /package/dist/{executor-PCZN2LTY.js.map → executor-LJSMZ4IB.js.map} +0 -0
  402. /package/dist/{executor-WFTHIDT7.js.map → executor-SHUVRZUJ.js.map} +0 -0
  403. /package/dist/{export-accessible-QWUYY2OY.js.map → export-accessible-GNY6DC6U.js.map} +0 -0
  404. /package/dist/{extract-partition-CGCURVS4.js.map → extract-partition-BG3AF4NZ.js.map} +0 -0
  405. /package/dist/{find-VBNMB42N.js.map → find-KX6OKHDP.js.map} +0 -0
  406. /package/dist/{issue-7IPUNAFA.js.map → issue-PAUM7NCX.js.map} +0 -0
  407. /package/dist/{ledger-3RDSSMGD.js.map → ledger-62CEPRPX.js.map} +0 -0
  408. /package/dist/{liberate-2DNJ7AJ7.js.map → liberate-7QY7TULB.js.map} +0 -0
  409. /package/dist/{link-set-VJIWWUZO.js.map → link-set-ZRYNUHN3.js.map} +0 -0
  410. /package/dist/{managed-secret-XGGJ3BC3.js.map → managed-secret-T2WB4O47.js.map} +0 -0
  411. /package/dist/{noydb-CLQP36JR.js.map → noydb-VZIVPDXG.js.map} +0 -0
  412. /package/dist/{post-register-W3T4U57K.js.map → post-register-J4DEVNXK.js.map} +0 -0
  413. /package/dist/{register-YUVC4G27.js.map → register-UPD4GKBC.js.map} +0 -0
  414. /package/dist/{registry-BPKJX562.js.map → registry-7KX2HWFS.js.map} +0 -0
  415. /package/dist/{registry-E4FM32KD.js.map → registry-PKYYEHEW.js.map} +0 -0
  416. /package/dist/{registry-HACMULJJ.js.map → registry-XHCYMD34.js.map} +0 -0
  417. /package/dist/{registry-HRFRBFDV.js.map → registry-XIRTZHPC.js.map} +0 -0
  418. /package/dist/{request-withdrawal-CXTQLMJK.js.map → request-withdrawal-XZSESWZV.js.map} +0 -0
  419. /package/dist/{reveal-G7W7N3AZ.js.map → reveal-525XWQU2.js.map} +0 -0
  420. /package/dist/{revoke-ZX3TZUI6.js.map → revoke-QI7YHQ2B.js.map} +0 -0
  421. /package/dist/{signer-FPUIX63Z.js.map → signer-BZWDPGGN.js.map} +0 -0
  422. /package/dist/{stale-42FPALK2.js.map → stale-PQYPVCER.js.map} +0 -0
  423. /package/dist/{storage-6QODT6K5.js.map → storage-MJHKNQRO.js.map} +0 -0
  424. /package/dist/{verify-SHJXGZGM.js.map → verify-J6DEOXP3.js.map} +0 -0
  425. /package/dist/{walk-5PGJI3ND.js.map → walk-BX4NSB5L.js.map} +0 -0
  426. /package/dist/{withdraw-accessible-PFCQQGNP.js.map → withdraw-accessible-K4CPMH4C.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-commit/history/ledger/patch.ts","../src/with-commit/history/ledger/store.ts"],"sourcesContent":["/**\n * RFC 6902 JSON Patch — compute + apply.\n *\n * This module is the \"delta history\" primitive: instead of\n * snapshotting the full record on every put (the behavior),\n * `Collection.put` computes a JSON Patch from the previous version to\n * the new version and stores only the patch in the ledger. To\n * reconstruct version N, we walk from the genesis snapshot forward\n * applying patches. Storage scales with **edit size**, not record\n * size — a 10 KB record edited 1000 times costs ~10 KB of deltas\n * instead of ~10 MB of snapshots.\n *\n * ## Why hand-roll instead of using a library?\n *\n * RFC 6902 has good libraries (`fast-json-patch`, `rfc6902`) but every\n * single one of them adds a runtime dependency to `@noy-db/core`. The\n * \"zero runtime dependencies\" promise is one of the core's load-bearing\n * features, and the patch surface we actually need is small enough\n * (~150 LoC) that vendoring is the right call.\n *\n * What we implement:\n * - `add` — insert a value at a path\n * - `remove` — delete the value at a path\n * - `replace` — overwrite the value at a path\n *\n * What we deliberately skip (out of scope for the ledger use):\n * - `move` and `copy` — optimizations; the diff algorithm doesn't\n * emit them, so the apply path doesn't need them\n * - `test` — used for transactional patches; we already have\n * optimistic concurrency via `_v` at the envelope layer\n * - Sophisticated array diffing (LCS, edit distance) — we treat\n * arrays as atomic values and emit a single `replace` op when\n * they differ. The accounting domain has small arrays where this\n * is fine; if we ever need patch-level array diffing we can add\n * it without changing the storage format.\n *\n * ## Path encoding (RFC 6902 §3)\n *\n * Paths look like `/foo/bar/0`. Each path segment is either an object\n * key or a numeric array index. Two characters need escaping inside\n * keys: `~` becomes `~0` and `/` becomes `~1`. We implement both.\n *\n * Empty path (`\"\"`) refers to the root document. Only `replace` makes\n * sense at the root, and our diff function emits it as a top-level\n * `replace` when `prev` and `next` differ in shape (object vs array,\n * primitive vs object, etc.).\n */\n\n/** A single JSON Patch operation. Subset of RFC 6902 — see file docstring. */\nexport type JsonPatchOp =\n | { readonly op: 'add'; readonly path: string; readonly value: unknown }\n | { readonly op: 'remove'; readonly path: string }\n | { readonly op: 'replace'; readonly path: string; readonly value: unknown }\n\n/** A complete JSON Patch document — an array of operations. */\nexport type JsonPatch = readonly JsonPatchOp[]\n\n// ─── Compute (diff) ──────────────────────────────────────────────────\n\n/**\n * Compute a JSON Patch that, when applied to `prev`, produces `next`.\n *\n * The algorithm is a straightforward recursive object walk:\n *\n * 1. If both inputs are plain objects (and not arrays/null):\n * - For each key in `prev`, recurse if `next` has it, else emit `remove`\n * - For each key in `next` not in `prev`, emit `add`\n * 2. If both inputs are arrays AND structurally equal, no-op.\n * Otherwise emit a single `replace` for the whole array.\n * 3. If both inputs are deeply equal primitives, no-op.\n * 4. Otherwise emit a `replace` at the current path.\n *\n * We do not minimize patches across move-like rearrangements — every\n * generated patch is straightforward enough to apply by hand if you\n * had to debug it.\n */\nexport function computePatch(prev: unknown, next: unknown): JsonPatch {\n const ops: JsonPatchOp[] = []\n diff(prev, next, '', ops)\n return ops\n}\n\nfunction diff(\n prev: unknown,\n next: unknown,\n path: string,\n out: JsonPatchOp[],\n): void {\n // Both null / both undefined → no-op (we don't differentiate them\n // in JSON terms; canonicalJson would reject undefined anyway).\n if (prev === next) return\n\n // One side null, the other not → straight replace.\n if (prev === null || next === null) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n const prevIsArray = Array.isArray(prev)\n const nextIsArray = Array.isArray(next)\n const prevIsObject = typeof prev === 'object' && !prevIsArray\n const nextIsObject = typeof next === 'object' && !nextIsArray\n\n // Type changed (e.g., object → primitive, array → object). Replace.\n if (prevIsArray !== nextIsArray || prevIsObject !== nextIsObject) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n // Both arrays. We don't do clever LCS-based diffing — emit a single\n // replace for the whole array if they differ. See file docstring for\n // the rationale.\n if (prevIsArray && nextIsArray) {\n if (!arrayDeepEqual(prev as unknown[], next as unknown[])) {\n out.push({ op: 'replace', path, value: next })\n }\n return\n }\n\n // Both plain objects. Recurse key by key.\n if (prevIsObject && nextIsObject) {\n const prevObj = prev as Record<string, unknown>\n const nextObj = next as Record<string, unknown>\n const prevKeys = Object.keys(prevObj)\n const nextKeys = Object.keys(nextObj)\n\n // Handle removes and overlapping recursions in one pass over prev.\n for (const key of prevKeys) {\n const childPath = path + '/' + escapePathSegment(key)\n if (!(key in nextObj)) {\n out.push({ op: 'remove', path: childPath })\n } else {\n diff(prevObj[key], nextObj[key], childPath, out)\n }\n }\n // Handle adds.\n for (const key of nextKeys) {\n if (!(key in prevObj)) {\n out.push({\n op: 'add',\n path: path + '/' + escapePathSegment(key),\n value: nextObj[key],\n })\n }\n }\n return\n }\n\n // Two primitives that aren't strictly equal — replace.\n out.push({ op: 'replace', path, value: next })\n}\n\nfunction arrayDeepEqual(a: unknown[], b: unknown[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false\n }\n return true\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n if (typeof a !== 'object') return false\n const aArray = Array.isArray(a)\n const bArray = Array.isArray(b)\n if (aArray !== bArray) return false\n if (aArray && bArray) return arrayDeepEqual(a, b as unknown[])\n const aObj = a as Record<string, unknown>\n const bObj = b as Record<string, unknown>\n const aKeys = Object.keys(aObj)\n const bKeys = Object.keys(bObj)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!(key in bObj)) return false\n if (!deepEqual(aObj[key], bObj[key])) return false\n }\n return true\n}\n\n// ─── Apply ──────────────────────────────────────────────────────────\n\n/**\n * Apply a JSON Patch to a base document and return the result.\n *\n * The base document is **not mutated** — every op clones the parent\n * container before writing to it, so the caller's reference to `base`\n * stays untouched. This costs an extra allocation per op but makes\n * the apply pipeline reorderable and safe to interrupt.\n *\n * Throws on:\n * - Removing a path that doesn't exist\n * - Adding to a path whose parent doesn't exist\n * - A path component that doesn't match the document shape (e.g.,\n * trying to step into a primitive)\n *\n * Throwing is the right behavior for the ledger use case: a failed\n * apply means the chain is corrupted, which should be loud rather\n * than silently producing a wrong reconstruction.\n */\nexport function applyPatch<T = unknown>(base: T, patch: JsonPatch): T {\n let result: unknown = clone(base)\n for (const op of patch) {\n result = applyOp(result, op)\n }\n return result as T\n}\n\nfunction applyOp(doc: unknown, op: JsonPatchOp): unknown {\n // Empty path → operation targets the root. Only `replace` and `add`\n // make sense at the root, but we handle `remove` for completeness\n // (root removal returns null).\n if (op.path === '') {\n if (op.op === 'remove') return null\n return clone(op.value)\n }\n\n const segments = parsePath(op.path)\n return walkAndApply(doc, segments, op)\n}\n\nfunction walkAndApply(\n doc: unknown,\n segments: string[],\n op: JsonPatchOp,\n): unknown {\n if (segments.length === 0) {\n // Should never happen — empty path is handled in applyOp().\n throw new Error('walkAndApply: empty segments (internal error)')\n }\n\n const [head, ...rest] = segments\n if (head === undefined) throw new Error('walkAndApply: undefined segment')\n\n if (rest.length === 0) {\n return applyAtTerminal(doc, head, op)\n }\n\n // Recurse into the child container, then rebuild the parent with\n // the modified child.\n if (Array.isArray(doc)) {\n const idx = parseArrayIndex(head, doc.length)\n const child = doc[idx]\n const newChild = walkAndApply(child, rest, op)\n const next = doc.slice()\n next[idx] = newChild\n return next\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (!(head in obj)) {\n throw new Error(`applyPatch: path segment \"${head}\" not found in object`)\n }\n const newChild = walkAndApply(obj[head], rest, op)\n return { ...obj, [head]: newChild }\n }\n throw new Error(\n `applyPatch: cannot step into ${typeof doc} at segment \"${head}\"`,\n )\n}\n\nfunction applyAtTerminal(\n doc: unknown,\n segment: string,\n op: JsonPatchOp,\n): unknown {\n if (Array.isArray(doc)) {\n const idx =\n segment === '-' ? doc.length : parseArrayIndex(segment, doc.length + 1)\n const next = doc.slice()\n if (op.op === 'remove') {\n next.splice(idx, 1)\n return next\n }\n if (op.op === 'add') {\n next.splice(idx, 0, clone(op.value))\n return next\n }\n if (op.op === 'replace') {\n if (idx >= doc.length) {\n throw new Error(\n `applyPatch: replace at out-of-bounds array index ${idx}`,\n )\n }\n next[idx] = clone(op.value)\n return next\n }\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (op.op === 'remove') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: remove on missing key \"${segment}\"`,\n )\n }\n const next = { ...obj }\n delete next[segment]\n return next\n }\n if (op.op === 'add') {\n // RFC 6902: `add` on an existing key replaces it.\n return { ...obj, [segment]: clone(op.value) }\n }\n if (op.op === 'replace') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: replace on missing key \"${segment}\"`,\n )\n }\n return { ...obj, [segment]: clone(op.value) }\n }\n }\n throw new Error(\n `applyPatch: cannot apply ${op.op} at terminal segment \"${segment}\"`,\n )\n}\n\n// ─── Path encoding (RFC 6902 §3) ─────────────────────────────────────\n\n/**\n * Escape a single path segment per RFC 6902 §3:\n * `~` → `~0`\n * `/` → `~1`\n *\n * Order matters: `~` must be escaped first, otherwise the `~1` we\n * just emitted would be re-escaped to `~01`.\n */\nfunction escapePathSegment(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1')\n}\n\nfunction unescapePathSegment(segment: string): string {\n return segment.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\nfunction parsePath(path: string): string[] {\n if (!path.startsWith('/')) {\n throw new Error(`applyPatch: path must start with '/', got \"${path}\"`)\n }\n return path\n .slice(1)\n .split('/')\n .map(unescapePathSegment)\n}\n\nfunction parseArrayIndex(segment: string, max: number): number {\n if (!/^\\d+$/.test(segment)) {\n throw new Error(\n `applyPatch: array index must be a non-negative integer, got \"${segment}\"`,\n )\n }\n const idx = Number.parseInt(segment, 10)\n if (idx < 0 || idx > max) {\n throw new Error(\n `applyPatch: array index ${idx} out of range [0, ${max}]`,\n )\n }\n return idx\n}\n\n// ─── Cheap structural clone ─────────────────────────────────────────\n\n/**\n * Plain-JSON clone via JSON.parse(JSON.stringify(value)).\n *\n * Faster than `structuredClone` for our use because (a) we know our\n * inputs are JSON-compatible (no Dates, Maps, or BigInts — anything\n * else gets rejected by canonicalJson upstream), and (b) `structuredClone`\n * has overhead for handling arbitrary structured data we don't need.\n *\n * For tiny ledger entries (< 1 KB), the JSON round-trip is in the\n * single-digit microsecond range.\n */\nfunction clone<T>(value: T): T {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n","/**\n * `LedgerStore` — read/write access to a compartment's hash-chained\n * audit log.\n *\n * The store is a thin wrapper around the adapter's `_ledger/` internal\n * collection. Every append:\n *\n * 1. Loads the current head (or treats an empty ledger as head = -1)\n * 2. Computes `prevHash` = sha256(canonicalJson(head))\n * 3. Builds the new entry with `index = head.index + 1`\n * 4. Encrypts the entry with the compartment's ledger DEK\n * 5. Writes the encrypted envelope to `_ledger/<paddedIndex>`\n *\n * `verify()` walks the chain from genesis forward and returns\n * `{ ok: true, head }` on success or `{ ok: false, divergedAt }` on the\n * first broken link.\n *\n * ## Thread / concurrency model\n *\n * For we assume a **single writer per vault**. Two\n * concurrent `append()` calls would race on the \"read head, write\n * head+1\" cycle and could produce a broken chain. The sync engine\n * is the primary concurrent-writer scenario, and it uses\n * optimistic-concurrency via `expectedVersion` on the adapter — but\n * the ledger path has no such guard today. Multi-writer hardening is a\n * follow-up.\n *\n * Single-writer usage IS safe, including across process restarts:\n * `head()` reads the adapter fresh each call, so a crash between the\n * adapter.put of a data record and the ledger append just means the\n * ledger is missing an entry for that record. `verify()` still\n * succeeds; a future `verifyIntegrity()` helper can cross-check the\n * ledger against the data collections to catch the gap.\n *\n * ## Why hide the ledger from `vault.collection()`?\n *\n * The `_ledger` name starts with `_`, matching the existing prefix\n * convention for internal collections (`_keyring`, `_sync`,\n * `_history`). The Vault's public `collection()` method already\n * returns entries for any name, but `loadAll()` filters out\n * underscore-prefixed collections so backups and exports don't leak\n * ledger metadata. We keep the ledger accessible ONLY via\n * `vault.ledger()` to enforce the hash-chain invariants — direct\n * puts via `collection('_ledger')` would bypass the `append()` logic.\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../../kernel/types.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, openEnvelopeJson, type EnclaveKey } from '../../../kernel/enclave/index.js'\nimport type { ConflictError} from '../../../kernel/errors.js';\nimport { isConflictError, LedgerContentionError } from '../../../kernel/errors.js'\nimport {\n canonicalJson,\n hashEntry,\n paddedIndex,\n sha256Hex,\n type LedgerEntry,\n} from './entry.js'\nimport type { JsonPatch } from './patch.js'\nimport { applyPatch } from './patch.js'\nimport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from './constants.js'\nimport { envelopePayloadHash } from './hash.js'\n\n/**\n * Maximum optimistic-CAS retries on the ledger head. Each failed\n * attempt invalidates the head cache, re-reads, and retries with a\n * fresh next-index. After N failures we surface\n * `LedgerContentionError` so the caller can decide whether to retry,\n * queue, or alert.\n */\nconst MAX_APPEND_ATTEMPTS = 8\n\n// — re-export the constants + helper so any existing\n// `import { LEDGER_COLLECTION } from '...store.js'` paths keep\n// working. Internal core paths (vault.ts) import from the leaf\n// modules directly to avoid pulling this file's class into the\n// floor bundle.\nexport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, envelopePayloadHash }\n\n/**\n * Input shape for `LedgerStore.append()`. The caller supplies the\n * operation metadata; the store fills in `index` and `prevHash`.\n */\nexport interface AppendInput {\n op: LedgerEntry['op']\n collection: string\n id: string\n version: number\n actor: string\n payloadHash: string\n /**\n * Optional JSON Patch representing the delta from the previous\n * version to the new version. Present only for `put` operations\n * that had a previous version; omitted for genesis puts and for\n * deletes. When present, `LedgerStore.append` persists the patch\n * in `_ledger_deltas/<paddedIndex>` and records its sha256 hash\n * as the entry's `deltaHash` field.\n */\n delta?: JsonPatch\n /**\n * Present only for `op === 'amendment'` — structured audit\n * payload for multi-record repair operations performed via\n * `withTransactions(...)`. Carried through verbatim to the\n * resulting ledger entry.\n */\n amendment?: LedgerEntry['amendment']\n /**\n * Optional human-readable tag describing why this mutation happened.\n * Threaded from `collection.put(_, _, { reason })`.\n * Carried verbatim onto the resulting ledger entry's `reason` field;\n * omitted from canonical JSON when undefined.\n */\n reason?: string\n}\n\n/**\n * Result of `LedgerStore.verify()`. On success, `head` is the hash of\n * the last entry — the same value that should be published to any\n * external anchoring service (blockchain, OpenTimestamps, etc.). On\n * failure, `divergedAt` is the 0-based index of the first entry whose\n * recorded `prevHash` does not match the recomputed hash of its\n * predecessor. Entries at `divergedAt` and later are untrustworthy;\n * entries before that index are still valid.\n */\nexport type VerifyResult =\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly divergedAt: number\n readonly expected: string\n readonly actual: string\n }\n\n/**\n * A LedgerStore is bound to a single vault. Callers obtain one\n * via `vault.ledger()` — there is no public constructor to keep\n * the hash-chain invariants in one place.\n *\n * The class holds no mutable state beyond its dependencies (adapter,\n * vault name, DEK resolver, actor id). Every method reads the\n * adapter fresh so multiple instances against the same vault\n * see each other's writes immediately (at the cost of re-parsing the\n * ledger on every head() / verify() call; acceptable at scale).\n */\nexport class LedgerStore {\n private readonly adapter: NoydbStore\n private readonly vault: string\n private readonly encrypted: boolean\n private readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n private readonly actor: string\n\n /**\n * In-memory cache of the chain head — the most recently appended\n * entry along with its precomputed hash. Without this, every\n * `append()` would re-load every prior entry to recompute the\n * prevHash, making N puts O(N²) — a 1K-record stress test goes from\n * < 100ms to a multi-second timeout.\n *\n * The cache is populated on first read (`append`, `head`, `verify`)\n * and updated in-place on every successful `append`. Single-writer\n * usage (the assumption) keeps it consistent. A second\n * LedgerStore instance writing to the same vault would not\n * see the first instance's appends in its cached state — that's the\n * concurrency caveat documented at the class level.\n *\n * Sentinel `undefined` means \"not yet loaded\"; an explicit `null`\n * value means \"loaded and confirmed empty\" — distinguishing these\n * matters because an empty ledger is a valid state (genesis prevHash\n * is the empty string), and we don't want to re-scan the adapter\n * just because the chain is freshly initialized.\n */\n private headCache: { entry: LedgerEntry; hash: string } | null | undefined = undefined\n\n constructor(opts: {\n adapter: NoydbStore\n vault: string\n encrypted: boolean\n getDEK: (collectionName: string) => Promise<EnclaveKey>\n actor: string\n }) {\n this.adapter = opts.adapter\n this.vault = opts.vault\n this.encrypted = opts.encrypted\n this.getDEK = opts.getDEK\n this.actor = opts.actor\n }\n\n /**\n * Lazily load (or return cached) the current chain head. The cache\n * sentinel is `undefined` until first access; after the first call,\n * the cache holds either a `{ entry, hash }` for non-empty ledgers\n * or `null` for empty ones.\n */\n private async getCachedHead(): Promise<{ entry: LedgerEntry; hash: string } | null> {\n if (this.headCache !== undefined) return this.headCache\n const entries = await this.loadAllEntries()\n const last = entries[entries.length - 1]\n if (!last) {\n this.headCache = null\n return null\n }\n this.headCache = { entry: last, hash: await hashEntry(last) }\n return this.headCache\n }\n\n /**\n * Append a new entry to the ledger. Returns the full entry that was\n * written (with its assigned index and computed prevHash) so the\n * caller can use the hash for downstream purposes (e.g., embedding\n * in a verifiable backup).\n *\n * This is the **only** way to add entries. Direct adapter writes to\n * `_ledger/` would bypass the chain math and would be caught by the\n * next `verify()` call as a divergence.\n *\n * ## Multi-writer correctness\n *\n * Append is implemented as an optimistic-CAS retry loop. On every\n * attempt:\n *\n * 1. Read fresh head (cache invalidated on retry).\n * 2. Compute `nextIndex = head.index + 1`, `prevHash = hash(head)`.\n * 3. Encrypt delta payload IN MEMORY (no adapter write yet) so we\n * can compute `deltaHash` before claiming the chain slot.\n * 4. Build + encrypt the entry envelope.\n * 5. `adapter.put(_ledger, paddedIndex, envelope, expectedVersion: 0)`\n * — the `expectedVersion: 0` asserts \"this slot must not exist.\"\n * Stores with `casAtomic: true` honor the CAS check; under\n * contention the second writer's put throws `ConflictError`.\n * 6. On `ConflictError`: invalidate the head cache, sleep with\n * bounded backoff + jitter, retry. After `MAX_APPEND_ATTEMPTS`\n * retries throw {@link LedgerContentionError}.\n * 7. On success: write the delta envelope (if any) at the same\n * index. Update the head cache.\n *\n * Entry-first ordering matters: writing the delta first under\n * contention would orphan delta records at indices the writer never\n * actually claimed. The deltaHash is computed off the encrypted\n * envelope's `_data` field, which doesn't require the envelope to\n * be persisted.\n *\n * Stores with `casAtomic: false` (file, s3, r2 by default) silently\n * accept the `expectedVersion: 0` argument and proceed without a\n * CAS check. Concurrent appends against those stores remain\n * best-effort — pair them with an advisory lock or with sync\n * single-writer discipline.\n */\n async append(input: AppendInput): Promise<LedgerEntry> {\n let lastConflict: ConflictError | undefined\n for (let attempt = 0; attempt < MAX_APPEND_ATTEMPTS; attempt++) {\n // Force a fresh head read on every retry. The first attempt may\n // hit the cache; subsequent attempts must re-scan the adapter\n // because the prior conflict means our cached state is stale.\n if (attempt > 0) {\n this.headCache = undefined\n }\n try {\n return await this.appendOnce(input)\n } catch (err) {\n if (isConflictError(err)) {\n lastConflict = err\n if (attempt < MAX_APPEND_ATTEMPTS - 1) {\n await sleepBackoff(attempt)\n }\n continue\n }\n throw err\n }\n }\n void lastConflict\n throw new LedgerContentionError(MAX_APPEND_ATTEMPTS)\n }\n\n /**\n * One attempt at the append cycle. Throws `ConflictError` when the\n * CAS check on the entry put fails — `append()` catches that and\n * retries. Any other error propagates to the caller.\n */\n private async appendOnce(input: AppendInput): Promise<LedgerEntry> {\n const cached = await this.getCachedHead()\n const lastEntry = cached?.entry\n const prevHash = cached?.hash ?? ''\n const nextIndex = lastEntry ? lastEntry.index + 1 : 0\n\n // Encrypt the delta in memory so we can compute deltaHash WITHOUT\n // claiming the deltas slot yet — entry-put is the chain claim.\n let deltaEnvelope: EncryptedEnvelope | undefined\n let deltaHash: string | undefined\n if (input.delta !== undefined) {\n deltaEnvelope = await this.encryptDelta(input.delta, nextIndex)\n deltaHash = await sha256Hex(deltaEnvelope._data)\n }\n\n // Build the entry. Conditionally include `deltaHash` so\n // canonicalJson (which rejects undefined) never sees it when\n // there's no delta.\n const entryBase = {\n index: nextIndex,\n prevHash,\n op: input.op,\n collection: input.collection,\n id: input.id,\n version: input.version,\n ts: new Date().toISOString(),\n actor: input.actor === '' ? this.actor : input.actor,\n payloadHash: input.payloadHash,\n } as const\n const entry: LedgerEntry = {\n ...entryBase,\n ...(deltaHash !== undefined ? { deltaHash } : {}),\n ...(input.amendment !== undefined ? { amendment: input.amendment } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n }\n\n const envelope = await this.encryptEntry(entry)\n // expectedVersion: 0 ≡ \"the slot must not yet exist.\" Honored by\n // casAtomic stores; silently passed through by non-CAS stores.\n await this.adapter.put(\n this.vault,\n LEDGER_COLLECTION,\n paddedIndex(entry.index),\n envelope,\n 0,\n )\n\n // Chain slot claimed. Now write the delta record (if any).\n if (deltaEnvelope) {\n await this.adapter.put(\n this.vault,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(entry.index),\n deltaEnvelope,\n 0,\n )\n }\n\n // Update the head cache so the next append() doesn't re-scan the\n // adapter.\n this.headCache = { entry, hash: await hashEntry(entry) }\n return entry\n }\n\n /**\n * Load a delta payload by its entry index. Returns `null` if the\n * entry at that index doesn't reference a delta (genesis puts and\n * deletes leave the slot empty) or if the delta row is missing\n * (possible after a `pruneHistory` fold).\n *\n * The caller is responsible for deciding what to do with a missing\n * delta — `ledger.reconstruct()` uses it as a \"stop walking\n * backward\" signal and falls back to the on-disk current value.\n */\n async loadDelta(index: number): Promise<JsonPatch | null> {\n const envelope = await this.adapter.get(\n this.vault,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(index),\n )\n if (!envelope) return null\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as JsonPatch\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await openEnvelopeJson({ collection: LEDGER_DELTAS_COLLECTION, id: paddedIndex(index) }, envelope, dek)\n return JSON.parse(json) as JsonPatch\n }\n\n /**\n * Purge a record's tier-0-era plaintext deltas (#729). Deletes each\n * `_ledger_deltas/<paddedIndex>` row whose entry matches\n * `(collection, id)` and carries a delta, then returns the count\n * deleted.\n *\n * Chain-safe by construction: `verify()` recomputes the tamper-chain\n * from each `_ledger` entry's own canonical fields — including the\n * stored `deltaHash` field, which lives on the entry, not on the\n * `_ledger_deltas` row — and never re-reads `_ledger_deltas`.\n * Deleting a delta row therefore cannot break `verify()`.\n * `reconstruct()` already treats a missing delta (`loadDelta` →\n * `null`) as a pruned stop, so a purge just makes the pre-purge\n * plaintext unreachable rather than corrupting anything.\n *\n * No `_ledger` entry is touched and nothing is re-encrypted — this\n * is irreversible (the deleted plaintext cannot be recovered) but\n * leaves the entry metadata (that the record was mutated, at which\n * version/timestamp/actor) fully intact for audit purposes.\n */\n async purgeRecordDeltas(collection: string, id: string): Promise<number> {\n const entries = await this.loadAllEntries()\n let purged = 0\n for (const entry of entries) {\n if (entry.collection !== collection || entry.id !== id) continue\n if (entry.deltaHash === undefined) continue\n const key = paddedIndex(entry.index)\n // adapter.delete() is a void no-op on an already-missing key, so\n // check for the row's presence first — the count must reflect\n // rows actually removed (idempotent repeat purges return 0), not\n // the number of delta-bearing entries (which never changes).\n const existing = await this.adapter.get(this.vault, LEDGER_DELTAS_COLLECTION, key)\n if (!existing) continue\n await this.adapter.delete(this.vault, LEDGER_DELTAS_COLLECTION, key)\n purged++\n }\n return purged\n }\n\n /**\n * Encrypt a JSON Patch into an envelope for storage. Mirrors encryptEntry.\n *\n * `index` is the ledger index this delta will be stored under — the caller\n * knows it before the entry exists, and it is what makes the envelope's\n * identity bindable (#1051).\n */\n private async encryptDelta(patch: JsonPatch, index: number): Promise<EncryptedEnvelope> {\n const identity = { collection: LEDGER_DELTAS_COLLECTION, id: paddedIndex(index), by: this.actor, version: 1 }\n const json = JSON.stringify(patch)\n if (!this.encrypted) {\n return buildRecordEnvelope(identity, { iv: '', data: json})\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n return buildRecordEnvelope(identity, { iv, data})\n }\n\n /**\n * Read all entries in ascending-index order. Used internally by\n * `append()`, `head()`, `verify()`, and `entries()`. Decryption is\n * serial because the entries are tiny and the overhead of a Promise\n * pool would dominate at realistic chain lengths (< 100K entries).\n */\n async loadAllEntries(): Promise<LedgerEntry[]> {\n const keys = await this.adapter.list(this.vault, LEDGER_COLLECTION)\n // Sort lexicographically, which matches numeric order because\n // keys are zero-padded to 10 digits.\n keys.sort()\n const entries: LedgerEntry[] = []\n for (const key of keys) {\n const envelope = await this.adapter.get(\n this.vault,\n LEDGER_COLLECTION,\n key,\n )\n if (!envelope) continue\n entries.push(await this.decryptEntry(key, envelope))\n }\n return entries\n }\n\n /**\n * Return the current head of the ledger: the last entry, its hash,\n * and the total chain length. `null` on an empty ledger so callers\n * can distinguish \"no history yet\" from \"empty history\".\n */\n async head(): Promise<\n | { readonly entry: LedgerEntry; readonly hash: string; readonly length: number }\n | null\n > {\n const cached = await this.getCachedHead()\n if (!cached) return null\n // `length` is `entry.index + 1` because indices are zero-based and\n // contiguous. We don't need to re-scan the adapter to compute it.\n return {\n entry: cached.entry,\n hash: cached.hash,\n length: cached.entry.index + 1,\n }\n }\n\n /**\n * Return entries in the requested half-open range `[from, to)`.\n * Defaults: `from = 0`, `to = length`. The indices are clipped to\n * the valid range; no error is thrown for out-of-range queries.\n */\n async entries(opts: { from?: number; to?: number } = {}): Promise<LedgerEntry[]> {\n const all = await this.loadAllEntries()\n const from = Math.max(0, opts.from ?? 0)\n const to = Math.min(all.length, opts.to ?? all.length)\n return all.slice(from, to)\n }\n\n /**\n * Reconstruct a record's state at a given historical version by\n * walking the ledger's delta chain backward from the current state.\n *\n * ## Algorithm\n *\n * Ledger deltas are stored in **reverse** form — each entry's\n * patch describes how to undo that put, transforming the new\n * record back into the previous one. `reconstruct` exploits this\n * by:\n *\n * 1. Finding every ledger entry for `(collection, id)` in the\n * chain, sorted by index ascending.\n * 2. Starting from `current` (the present value of the record,\n * as held by the caller — typically fetched via\n * `Collection.get()`).\n * 3. Walking entries in **descending** index order and applying\n * each entry's reverse patch, stopping when we reach the\n * entry whose version equals `atVersion`.\n *\n * The result is the record as it existed immediately AFTER the\n * put at `atVersion`. To get the state at the genesis put\n * (version 1), the walk runs all the way back through every put\n * after the first.\n *\n * ## Caveats\n *\n * - **Delete entries** break the walk: once we see a delete, the\n * record didn't exist before that point, so there's nothing to\n * reconstruct. We return `null` in that case.\n * - **Missing deltas** (e.g., after `pruneHistory` folds old\n * entries into a base snapshot) also stop the walk. does\n * not ship pruneHistory, so today this only happens if an entry\n * was deleted out-of-band.\n * - The caller MUST pass the correct current value. Passing a\n * mutated object would corrupt the reconstruction — the patch\n * chain is only valid against the exact state that was in\n * effect when the most recent put happened.\n *\n * For, `reconstruct` is the only way to read a historical\n * version via deltas. The legacy `_history` collection still\n * holds full snapshots and `Collection.getVersion()` still reads\n * from there — the two paths coexist until pruneHistory lands in\n * a follow-up and delta becomes the default.\n */\n async reconstruct<T>(\n collection: string,\n id: string,\n current: T,\n atVersion: number,\n ): Promise<T | null> {\n const all = await this.loadAllEntries()\n // Filter to entries for this (collection, id), in ascending index.\n const matching = all.filter(\n (e) => e.collection === collection && e.id === id,\n )\n if (matching.length === 0) {\n // No ledger history at all; the current state IS version 1\n // (or there's nothing), so the only valid atVersion is the\n // current record's version. We can't verify that here, so\n // return current if atVersion is plausible, null otherwise.\n return null\n }\n\n // Walk entries in descending index order, applying each reverse\n // delta until we reach the target version.\n let state: T | null = current\n for (let i = matching.length - 1; i >= 0; i--) {\n const entry = matching[i]\n if (!entry) continue\n\n // Defensive: skip every non-put/non-delete op variant. The\n // outer filter on `e.collection === collection && e.id === id`\n // already excludes `amendment` entries (their collection/id are\n // empty strings), but a top-of-loop guard keeps the walker\n // robust if a future op variant slips through the filter.\n if (entry.op !== 'put' && entry.op !== 'delete') continue\n\n // Match check FIRST — before applying this entry's reverse\n // patch. `state` at this point is the record state immediately\n // after this entry's put (or before this entry's delete), so\n // if the caller asked for this exact version, we're done.\n if (entry.version === atVersion && entry.op !== 'delete') {\n return state\n }\n\n if (entry.op === 'delete') {\n // A delete erases the live state. If the caller asks for a\n // version older than the delete we should continue walking\n // (state becomes null and the next put resets it). But we\n // can't reconstruct that pre-delete state from the current\n // in-memory `state` — the delete has no reverse patch. So\n // anything past this point is unreachable; return null.\n return null\n }\n\n if (entry.deltaHash === undefined) {\n // Genesis put — the earliest state for this lifecycle. We\n // can't walk further back. If the caller asked for exactly\n // this version, return the current state (we already failed\n // the match check above because a fresh genesis after a\n // delete can have version === atVersion). Otherwise the\n // target is unreachable from here.\n if (entry.version === atVersion) return state\n return null\n }\n\n const patch = await this.loadDelta(entry.index)\n if (!patch) {\n // Delta row is missing (probably pruned). Stop walking.\n return null\n }\n\n if (state === null) {\n // We're trying to walk back across a delete range and there's\n // nothing to apply a reverse patch to. Bail.\n return null\n }\n\n state = applyPatch(state, patch)\n }\n\n // Ran off the end of the walk without matching. The target\n // version doesn't exist in this record's chain.\n return null\n }\n\n /**\n * Walk the chain from genesis forward and verify every link.\n *\n * Returns `{ ok: true, head, length }` if every entry's `prevHash`\n * matches the recomputed hash of its predecessor (and the genesis\n * entry's `prevHash` is the empty string).\n *\n * Returns `{ ok: false, divergedAt, expected, actual }` on the first\n * mismatch. `divergedAt` is the 0-based index of the BROKEN entry\n * — entries before that index still verify cleanly; entries at and\n * after `divergedAt` are untrustworthy.\n *\n * This method detects:\n * - Mutated entry content (fields changed)\n * - Reordered entries (if any adjacent pair swaps, the prevHash\n * of the second no longer matches)\n * - Inserted entries (the inserted entry's prevHash likely fails,\n * and the following entry's prevHash definitely fails)\n * - Deleted entries (the entry after the deletion sees a wrong\n * prevHash)\n *\n * It does NOT detect:\n * - Tampering with the DATA collections that bypassed the ledger\n * entirely (e.g., an attacker who modifies records without\n * appending matching ledger entries — this is why we also\n * plan a `verifyIntegrity()` helper in a follow-up)\n * - Truncation of the chain at the tail (dropping the last N\n * entries leaves a shorter but still consistent chain). External\n * anchoring of `head.hash` to a trusted service is the defense\n * against this.\n */\n async verify(): Promise<VerifyResult> {\n const entries = await this.loadAllEntries()\n let expectedPrevHash = ''\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]\n if (!entry) continue\n if (entry.prevHash !== expectedPrevHash) {\n return {\n ok: false,\n divergedAt: i,\n expected: expectedPrevHash,\n actual: entry.prevHash,\n }\n }\n if (entry.index !== i) {\n // An entry whose stored index doesn't match its position in\n // the sorted list means someone rewrote the adapter keys.\n // Treat as divergence.\n return {\n ok: false,\n divergedAt: i,\n expected: `index=${i}`,\n actual: `index=${entry.index}`,\n }\n }\n expectedPrevHash = await hashEntry(entry)\n }\n return {\n ok: true,\n head: expectedPrevHash,\n length: entries.length,\n }\n }\n\n // ─── Encryption plumbing ─────────────────────────────────────────\n\n /**\n * Serialize + encrypt a ledger entry into an EncryptedEnvelope. The\n * envelope's `_v` field is set to `entry.index + 1` so the usual\n * optimistic-concurrency machinery has a reasonable version number\n * to compare against (the ledger is append-only, so concurrent\n * writes should always bump the index).\n */\n private async encryptEntry(entry: LedgerEntry): Promise<EncryptedEnvelope> {\n const identity = { collection: LEDGER_COLLECTION, id: paddedIndex(entry.index), by: entry.actor, version: entry.index + 1 }\n const body = { ts: entry.ts }\n const json = canonicalJson(entry)\n if (!this.encrypted) {\n return buildRecordEnvelope(identity, { ...body, iv: '', data: json })\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n return buildRecordEnvelope(identity, { ...body, iv, data })\n }\n\n /** Decrypt an envelope into a LedgerEntry. Throws on bad key / tamper. */\n private async decryptEntry(key: string, envelope: EncryptedEnvelope): Promise<LedgerEntry> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as LedgerEntry\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await openEnvelopeJson({ collection: LEDGER_COLLECTION, id: key }, envelope, dek)\n return JSON.parse(json) as LedgerEntry\n }\n}\n\n// `envelopePayloadHash` lives in `./hash.ts` so it can be\n// imported by core code without dragging this file's `LedgerStore`\n// class into the floor bundle. The re-export at the top of this\n// file keeps the `import { envelopePayloadHash } from '.../store.js'`\n// path working.\n\n/**\n * Exponential backoff with jitter for the append CAS retry loop.\n * Attempt 0 → ~5–10 ms, attempt 7 → ~640–1280 ms. Jitter avoids the\n * thundering-herd problem when multiple writers collide repeatedly.\n */\nfunction sleepBackoff(attempt: number): Promise<void> {\n const base = 5 * Math.pow(2, attempt)\n const jitter = Math.random() * base\n return new Promise((resolve) => setTimeout(resolve, base + jitter))\n}\n"],"mappings":"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","names":[]}
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  installViaBinder
3
- } from "./chunk-RVTOCAHQ.js";
3
+ } from "./chunk-KDX6VEYV.js";
4
4
  import {
5
- encrypt
6
- } from "./chunk-QTDAPDDV.js";
5
+ buildRecordAad
6
+ } from "./chunk-D5UDZANO.js";
7
+ import {
8
+ buildRecordEnvelope
9
+ } from "./chunk-ZVAFJQZB.js";
7
10
  import {
8
- NOYDB_FORMAT_VERSION
9
- } from "./chunk-5UGEGEIJ.js";
11
+ encrypt
12
+ } from "./chunk-VJ4B3ZXY.js";
10
13
  import {
11
14
  ValidationError
12
- } from "./chunk-I64WQSN3.js";
15
+ } from "./chunk-P7L3OQLL.js";
13
16
 
14
17
  // src/via/blob/binding.ts
15
18
  async function eraseBlobs(ctx, cfg) {
@@ -367,27 +370,15 @@ function generateEvictionId(collection, recordId, slotName) {
367
370
  }
368
371
  async function writeAuditEntry(ctx, entry) {
369
372
  const json = JSON.stringify(entry);
373
+ const identity = { collection: BLOB_EVICTION_AUDIT_COLLECTION, id: entry.id, by: entry.actor, version: 1 };
374
+ const body = { ts: entry.evictedAt };
370
375
  let envelope;
371
376
  if (ctx.encrypted) {
372
377
  const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION);
373
- const { iv, data } = await encrypt(json, dek);
374
- envelope = {
375
- _noydb: NOYDB_FORMAT_VERSION,
376
- _v: 1,
377
- _ts: entry.evictedAt,
378
- _iv: iv,
379
- _data: data,
380
- _by: entry.actor
381
- };
378
+ const { iv, data } = await encrypt(json, dek, buildRecordAad(identity));
379
+ envelope = buildRecordEnvelope(identity, { ...body, iv, data });
382
380
  } else {
383
- envelope = {
384
- _noydb: NOYDB_FORMAT_VERSION,
385
- _v: 1,
386
- _ts: entry.evictedAt,
387
- _iv: "",
388
- _data: json,
389
- _by: entry.actor
390
- };
381
+ envelope = buildRecordEnvelope(identity, { ...body, iv: "", data: json });
391
382
  }
392
383
  await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope);
393
384
  }
@@ -400,4 +391,4 @@ export {
400
391
  BLOB_EVICTION_AUDIT_COLLECTION,
401
392
  runCompaction
402
393
  };
403
- //# sourceMappingURL=chunk-Z2OFE6IX.js.map
394
+ //# sourceMappingURL=chunk-MQXYR65M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/via/blob/binding.ts","../src/port/with/blob-strategy.ts","../src/with-shape/blobs/export-blobs.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * The blob `ViaBinding` (#629 Task 7) — wires the `blobFields` declaration\n * into the kernel's generic Via port as a deliberately THIN binding:\n * declaration + posture + `describeFragment` + `erase` glue, and nothing\n * else. Blob content is fully out-of-band (`collection.blob(id)` →\n * `BlobSet` writing `_blob_*` side-collections; it never touches\n * `_putInternal` or the record codec), so this binding declares **no\n * write/read pipeline hooks** — in particular no `encodeAtRest`/\n * `decodeAtRest`: a blobFields collection must not flip the pipeline's\n * `hasAtRestHooks`, or the codec would abandon its inline seal path for a\n * feature that never seals record fields. The routing/compaction/TTL/\n * legal-hold/exportBlobs machinery stays service-side in\n * `with-shape/blobs/` (the i18n-dictionary precedent — that machinery\n * does real chunk AEAD + key-lifecycle work `via-enclave-isolation`\n * forbids under `via/*`).\n *\n * Like classified (see `port/with/classified-strategy.ts`), the link is\n * EAGER — `port/with/blob-strategy.ts` calls {@link linkBlobVia} at module\n * load. `blobFields` policies are plain object literals (there is no\n * `blob.*()` declaration factory to hang a lazy link on), so the binder\n * must be installed before `compileViaBindings` ever sees one.\n *\n * There is no `declare`-time validation: `blobFields` has never had a\n * construction-time refusal matrix (policies are consulted lazily by\n * `vault.compact()` and `BlobSet.put`), and inventing one here would\n * break the behavior lock.\n *\n * `covers` (#629 Task 8) is a passive coverage predicate, not a pipeline\n * hook — it lets `ViaPipeline.postureFor` recognize a `blobFields` slot so\n * `.where()`/`.orderBy()`/`.aggregate()` can refuse it (posture\n * `queryable: 'none'`); it does not participate in clause building or\n * evaluation.\n */\nimport type { ViaBinding, ViaEraseCtx, ViaEraseReport } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\n\n/**\n * Config a blob collection's `blobFields` declaration resolves to — the\n * binding's construction input. `purgeBlobsForRecord` is an OPTIONAL\n * vault/service-provided closure for the `erase` hook, mapping\n * `collection.blob(id).shredAllForRecord()`'s `{shredded, retainedShared,\n * residue}: string[]` accounting onto `ViaEraseReport` — `shredded`/\n * `retainedShared` become counts, each residue eTag becomes a tagged\n * `{kind: 'blob-residue', eTag}` entry.\n *\n * DELIBERATELY left unwired by `compileViaBindings` (#629 Task 10, resolved\n * within parity-first — see task-10-report.md \"blob purge\" section):\n * `per-blob-cek.test.ts`/`forget.test.ts` prove blob-shred-on-forget is\n * gated SOLELY on the vault's `blobsStrategy` being configured, never on a\n * given collection's OWN `blobFields` declaration (`.blob(id)` is callable,\n * and IS exercised by those tests, on collections with no `blobFields` at\n * all). Routing it exclusively through this binding would silently stop\n * blob crypto-shredding for every such collection; `vault.forget()` keeps\n * calling `collection.blob(id).shredAllForRecord()` directly for that\n * reason. The hook stays real/tested (see `blob-binding.test.ts`) for a\n * future caller that wants it.\n */\nexport interface BlobViaConfig {\n readonly fields: BlobFieldsConfig\n readonly collectionName: string\n /** Purge one record's blob slots (crypto-shred participation). Returns the erase report for `forget()`'s summary. */\n readonly purgeBlobsForRecord?: (id: string) => Promise<ViaEraseReport>\n}\n\n/**\n * `forget()`'s per-ref blob-purge participation: delegate to the wired\n * closure, or report a clean zero-shredded, zero-residue no-op — the\n * default in production too (#629 Task 10 deliberately leaves this unwired;\n * see `BlobViaConfig`'s doc comment).\n */\nasync function eraseBlobs(ctx: ViaEraseCtx, cfg: BlobViaConfig): Promise<ViaEraseReport> {\n if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id)\n return { shredded: 0, residue: [] }\n}\n\n/**\n * One field's blob knobs as they appear on `describeFragment()`'s payload\n * (#657 — the second real `ViaBinding.describeFragment` consumer after\n * `lookup`'s; see `with-shape/introspection/describe.ts`'s `buildDescription`).\n * Declarative scalars (`retainDays`/`external`/`public`/`backlink`) verbatim;\n * predicate knobs (`evictWhen`/`legalHold`/`retainUntil` — functions over the\n * decrypted record) as presence flags, since a predicate has no serializable\n * form — mirrors `buildBlobDescribeFragment`'s per-field shape below.\n */\nexport interface BlobDescribeFragmentEntry {\n readonly retainDays?: number\n readonly evictWhen?: true\n readonly legalHold?: true\n readonly retainUntil?: true\n readonly external?: true\n readonly public?: true\n readonly backlink?: string\n}\n\n/** The `'blob'` binding's `describeFragment()` payload shape. */\nexport interface BlobDescribeFragment {\n readonly blobFields: Record<string, BlobDescribeFragmentEntry>\n}\n\n/**\n * `{ blobFields: { <field>: <knobs> } }` — declarative scalars\n * (`retainDays`/`external`/`public`/`backlink`) verbatim; predicate knobs\n * (`evictWhen`/`legalHold`/`retainUntil` — functions over the decrypted\n * record) as presence flags, since a predicate has no serializable form.\n */\nfunction buildBlobDescribeFragment(fields: BlobFieldsConfig): Record<string, unknown> {\n return {\n blobFields: Object.fromEntries(\n Object.entries(fields).map(([field, p]) => [field, {\n ...(p.retainDays !== undefined ? { retainDays: p.retainDays } : {}),\n ...(p.evictWhen !== undefined ? { evictWhen: true } : {}),\n ...(p.legalHold !== undefined ? { legalHold: true } : {}),\n ...(p.retainUntil !== undefined ? { retainUntil: true } : {}),\n ...(p.external === true ? { external: true } : {}),\n ...(p.public === true ? { public: true } : {}),\n ...(p.backlink !== undefined ? { backlink: p.backlink } : {}),\n }]),\n ),\n }\n}\n\nexport function blobBinding(cfg: BlobViaConfig): ViaBinding {\n return {\n brand: 'blob',\n // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes\n // in `_blob_chunks` (service-side), not sealed record fields; queryable:\n // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()\n // is a first-class door; forgettable — shredAllForRecord participates in\n // vault.forget().\n posture: { encryptedAtRest: 'envelope', queryable: 'none', exportable: true, forgettable: true },\n covers: (field) => field in cfg.fields,\n erase: (ctx) => eraseBlobs(ctx, cfg),\n describeFragment: () => buildBlobDescribeFragment(cfg.fields),\n }\n}\n\nexport function linkBlobVia(): void {\n installViaBinder('blob', (c) => blobBinding(c as BlobViaConfig))\n}\n","/**\n * Strategy seam between core Collection and the optional blob service\n * (#629 Task 7 — moved out of `with-shape/blobs/strategy.ts`, precedent:\n * `port/with/classified-strategy.ts`/`i18n-strategy.ts`). Lives on the\n * `/with` port (the one seam the kernel spine may import statically) so\n * `Collection`/`Vault` can hold the `NO_BLOBS` default without a\n * spine→service static import.\n *\n * Core imports `BlobsStrategy` as a TYPE-ONLY symbol and `NO_BLOBS` as a\n * minimal runtime stub. Neither pulls in the heavy `BlobSet` / chunk /\n * MIME machinery — those only arrive when the consumer explicitly\n * imports `@noy-db/hub/blobs` (see `via/blob/index.ts` →\n * `withBlobs()` factory).\n *\n * This file is intentionally tiny so the bundler keeps it in the graph\n * without dragging everything else in.\n *\n * @internal\n */\n\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { ObjectProjection } from '../../with-shape/blobs/object-projection.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { linkBlobVia } from '../../via/blob/binding.js'\n\n// Install the blob Via binder EAGERLY (#629 Task 7) — `blobFields` policies\n// are plain object literals (there is no `blob.*()` declaration factory to\n// hang a lazy money/i18n-style link on), so the binder must be installed\n// before `compileViaBindings` ever compiles one. This port module is\n// already unconditionally imported by the kernel spine (for the `NO_BLOBS`\n// strategy default below), so linking here guarantees it — same rationale\n// as `port/with/classified-strategy.ts`'s eager `linkClassifiedVia()`. The\n// binding module is hook-free glue (declare/describe/erase) and imports no\n// machinery, so the eager cost is a few hundred bytes, not `BlobSet`.\nlinkBlobVia()\n\n/**\n * Args forwarded by `Collection.blob(id)` to the active strategy's\n * `openSlot`. The strategy is responsible for returning a live\n * `BlobSet` bound to the given record.\n *\n * @internal\n */\nexport interface BlobsStrategyOpenArgs {\n readonly store: NoydbStore\n readonly vault: string\n readonly collection: string\n readonly recordId: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly encrypted: boolean\n readonly userId: string\n /**\n * Collection opts into per-record keys (`perRecordKeys`), so its blobs are\n * erasable: new uploads mint a per-blob content CEK (crypto-shreddable at\n * `refCount → 0`). Off → legacy shared-`_blob`-DEK chunks. See the per-blob\n * CEK design spec.\n */\n readonly erasableBlobs?: boolean\n /**\n * Collection has `tiers` active (#724 I1 completion). Combined with\n * `erasableBlobs` false, a content write is refused —\n * `UnsupportedTierCompositionError` — since a legacy blob has no\n * per-blob `_cek` and can never be tier-isolated on elevate/demote.\n */\n readonly tiersActive?: boolean\n /**\n * Vault is in debug-plaintext mode (`encrypt: false` + `debugPlaintext: true`).\n * Blobs are then written as a single un-gzipped object (one base64 chunk) so\n * the stored object is directly recoverable with native tools (`base64 -d`),\n * matching the directly-inspectable record layout. Dev-only — see the\n * plaintext/debug-store-mode design.\n */\n readonly debugPlaintext?: boolean\n /**\n * Object projection for `external` blob fields (`createNoydb({ objectStore })`).\n * When present, fields declared `external` in `blobFields` route their raw\n * bytes here instead of the encrypted-chunk path.\n */\n readonly objectStore?: ObjectProjection\n /** Per-collection blob field policies — used to resolve `external` / `public`. */\n readonly blobFields?: BlobFieldsConfig\n /**\n * The caller's unlocked keyring (#749). Threaded through so `BlobSet.atTier()`\n * can run `assertTierAccess` — the SAME clearance gate `putAtTier`/`elevate`/\n * `demote` run before any tier `getDEK` call — rather than relying on a bare\n * `getDEK`, which auto-mints a DEK for ANY role and never throws (verified: it\n * is not itself a gate). Omitted only by a construction path that never calls\n * `atTier()`.\n */\n readonly keyring?: UnlockedKeyring\n}\n\n/**\n * The seam interface. `@internal` — do not build public APIs on this\n * shape; it can evolve freely until blobs are extracted into their\n * own package, at which point it will be promoted to public.\n *\n * @internal\n */\nexport interface BlobsStrategy {\n openSlot(args: BlobsStrategyOpenArgs): BlobSet\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store (#808).\n *\n * Lives on the seam rather than on the `withBlobs()` return type so the\n * service has exactly one name (#844b) — `NO_BLOBS` answers with zeros,\n * which is the truthful reading of \"blobs were never enabled\".\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Default strategy for collections that did not opt into blob storage.\n * Every operation surfaces an actionable error that points the caller\n * at the opt-in path.\n *\n * @internal\n */\nexport const NO_BLOBS: BlobsStrategy = {\n openSlot() {\n throw new Error(\n 'Blob storage is not enabled on this Noydb instance. ' +\n 'Import `{ withBlobs }` from \"@noy-db/hub/blobs\" and pass `withBlobs()` to `createNoydb({ blobsStrategy: withBlobs() })`.',\n )\n },\n cacheStats() {\n return { hits: 0, misses: 0, bytesDownloaded: 0 }\n },\n}\n","/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../../kernel/collection.js'\nimport type { SlotInfo } from '../../kernel/types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../../kernel/types.js'\nimport { ValidationError } from '../../kernel/errors.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Via port brand marker — lets a `BlobFieldPolicy` satisfy the kernel's\n * opaque `ViaDescriptor` (#629 Task 7). Optional (the\n * `ClassifiedFieldSpec._viaBrand` precedent): `blobFields` policies are\n * plain object literals with no declaration factory to stamp it, so a\n * mandatory field would break every existing declaration.\n */\n readonly _viaBrand?: 'blob'\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n /**\n * **Legal hold.** When this predicate returns `true`, the slot is\n * never evicted — `retainDays`/`evictWhen` are overridden. Use for a\n * litigation / audit hold on a fiscal document: the blob stays until\n * the predicate returns `false` (the hold is released). Fail-closed:\n * if the predicate throws, the slot is treated as held.\n */\n readonly legalHold?: (record: T) => boolean\n /**\n * **Period-bound retention.** Returns the date (Date / ISO string /\n * epoch ms) until which the slot must be retained — typically derived\n * from the record's fiscal period (e.g. period end + 10 years). While\n * `now < retainUntil`, the slot is never evicted, regardless of\n * `retainDays`. Return `null`/`undefined` to impose no floor.\n * Fail-closed: a throwing function holds the slot.\n */\n readonly retainUntil?: (record: T) => Date | string | number | null | undefined\n /**\n * **External projection.** When `true`, this field's bytes are stored in the\n * vault's `ObjectProjection` (`createNoydb({ objectStore })`) as a single raw,\n * **unencrypted** object — servable directly from S3/CDN and processable by\n * native tooling — instead of the encrypted-chunk path. The encrypted slot\n * record remains the catalog (anchoring invariant). Requires an `objectStore`;\n * **outside the zero-knowledge guarantee** — use only for assets meant to\n * leave the vault. See the as-aws-s3 design spec.\n */\n readonly external?: boolean\n /**\n * For an `external` field: make the object world-readable (CDN origin) rather\n * than presigned-only. Default `false` (presigned). Ignored unless `external`.\n */\n readonly public?: boolean\n /**\n * For an `external` field: how to stamp a **backlink** (this record's\n * vault/collection/id/field) onto the object's metadata — the self-describing\n * \"secondary store\" that powers reconcile / DR / import re-pairing.\n * - `'opaque-token'` (default): a random id; preserves the opaque-bucket\n * property (no names leak); the token is also recorded on the slot.\n * - `'encrypted'`: the reference encrypted under the blob DEK (ZK-preserving;\n * falls back to `'opaque-token'` on a plaintext vault).\n * - `'plain'`: the reference in cleartext metadata — **leaks structure** to\n * bucket readers; only for non-sensitive deployments.\n * - `'none'`: no backlink.\n */\n readonly backlink?: 'opaque-token' | 'encrypted' | 'plain' | 'none'\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n /** `'budget'` — evicted by the #808 cache-budget LRU pass, not a policy. */\n readonly reason: 'ttl' | 'predicate' | 'both' | 'budget'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted by the policy pass across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /**\n * Number of audit entries written — one per policy eviction plus one per\n * budget SLOT eviction (dropping a device-local cache copy writes none).\n */\n readonly auditEntries: number\n /**\n * Number of slots that would have evicted (TTL/predicate triggered)\n * but were retained by a `legalHold` or `retainUntil` floor.\n */\n readonly held: number\n /**\n * #808: number of slots whose due policy eviction was skipped because they\n * are pinned for offline on THIS device (pin state is device-local).\n */\n readonly pinned: number\n /**\n * #808: number of evictions performed by the `cacheBudget` LRU pass —\n * internal slots evicted through the standard eviction writer plus\n * device-local external cache copies dropped. 0 when no budget was given.\n */\n readonly budgetEvicted: number\n /** #808: plaintext bytes freed by the `cacheBudget` LRU pass. */\n readonly budgetBytesFreed: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n /**\n * #808 mobile cache budget: cap the locally-cached UNPINNED blob bytes.\n * After the policy pass, a dedicated LRU pass walks every collection's\n * slots (pinned slots and `legalHold`/`retainUntil`-held slots are exempt\n * and uncounted) and evicts oldest-access-first — internal slots through\n * the standard eviction writer (`deleteSlot` + a `'budget'` audit entry),\n * external slots by dropping their device-local encrypted cache copy only\n * (the object-store copy is authoritative and untouched) — until the\n * remaining unpinned bytes fit `maxBytes`. Internal slots count their\n * uncompressed `size`; external slots count `cachedBytes` (0 = nothing\n * local, nothing to evict). LRU order comes from the device-local\n * `lastAccessAt` stamp, falling back to `uploadedAt`.\n */\n readonly cacheBudget?: { readonly maxBytes: number }\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<EnclaveKey>\n /**\n * Resolve a collection's declared `blobFields` config. Returns an\n * empty map for collections without the config — the walk skips\n * those.\n */\n readonly getBlobFields: <T>(collection: string) => BlobFieldsConfig<T> | null\n /** List collection names in the vault. */\n readonly listCollections: () => Promise<string[]>\n /** List record ids in a collection. */\n readonly listRecords: (collection: string) => Promise<string[]>\n /** Decrypt and return the record. Null when absent. */\n readonly getRecord: <T>(collection: string, id: string) => Promise<T | null>\n /** Return the BlobSet-like handle for a record's slots. */\n readonly listSlots: (collection: string, id: string) => Promise<SlotInfo[]>\n /** Delete a slot and decrement its blob's refCount. */\n readonly deleteSlot: (collection: string, id: string, slotName: string) => Promise<void>\n /**\n * #808: drop a slot's DEVICE-LOCAL cached copy (the encrypted external\n * side-cache) without touching the slot or the object store. Returns the\n * plaintext bytes freed. Optional — absent (or a 0 return) means external\n * cache entries simply cannot be budget-evicted through this context.\n */\n readonly dropLocalCache?: (collection: string, id: string, slotName: string) => Promise<number>\n}\n\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n if (options.cacheBudget !== undefined\n && (!Number.isFinite(options.cacheBudget.maxBytes) || options.cacheBudget.maxBytes < 0)) {\n throw new ValidationError('compact(): cacheBudget.maxBytes must be a non-negative finite number (#808)')\n }\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let held = 0\n let pinned = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n // #808: device-local offline pin — an exemption inside this existing\n // pass, exactly like a hold: counted, never evicted. `slot.pinned` is\n // the device-local annotation `BlobSet.list()` stamps from the\n // withBlobs() pin registry (never synced state).\n if (slot.pinned === true) {\n pinned += 1\n continue\n }\n\n // Retention floor: a legal hold or period-bound retainUntil\n // blocks an otherwise-due eviction. Counted, never evicted.\n if (isHeld(policy, record, now)) {\n held += 1\n continue\n }\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n // #808: cache-budget LRU pass — runs AFTER the policy pass so a slot that\n // just evicted no longer counts toward the budget. Reuses this module's own\n // eviction writer (deleteSlot + audit), never a parallel one.\n let budgetEvicted = 0\n let budgetBytesFreed = 0\n if (options.cacheBudget !== undefined) {\n const pass = await runBudgetPass(ctx, options.cacheBudget.maxBytes, {\n now, dryRun, remainingEvictions: maxEvictions - evicted,\n })\n budgetEvicted = pass.evicted\n budgetBytesFreed = pass.bytesFreed\n auditEntries += pass.auditEntries\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n pinned,\n budgetEvicted,\n budgetBytesFreed,\n byCollection,\n }\n}\n\n// ─── #808: cache-budget LRU pass ───────────────────────────────────────\n\ninterface BudgetCandidate {\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly eTag: string\n readonly bytes: number\n /** `'slot'` — internal blob, evicted via `deleteSlot`; `'cache'` — external device-local copy, dropped via `dropLocalCache`. */\n readonly kind: 'slot' | 'cache'\n readonly lastAccessMs: number\n}\n\n/**\n * Enforce `cacheBudget.maxBytes` over the locally-cached UNPINNED blob bytes\n * (see {@link CompactRunOptions.cacheBudget} for the full contract). Walks\n * EVERY collection — unlike the policy pass, the budget is not gated on a\n * declared `blobFields` config — but still honors a declared policy's\n * `legalHold`/`retainUntil` floor where one exists.\n */\nasync function runBudgetPass(\n ctx: CompactionContext,\n maxBytes: number,\n opts: { now: Date; dryRun: boolean; remainingEvictions: number },\n): Promise<{ evicted: number; bytesFreed: number; auditEntries: number }> {\n const candidates: BudgetCandidate[] = []\n let total = 0\n\n for (const collectionName of await ctx.listCollections()) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n for (const recordId of await ctx.listRecords(collectionName)) {\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n if (slots.length === 0) continue\n // The record is only needed to evaluate a declared policy's retention\n // floor — skip the decrypt entirely for policy-less collections.\n const record = config\n ? await ctx.getRecord<unknown>(collectionName, recordId).catch(() => null)\n : null\n for (const slot of slots) {\n if (slot.pinned === true) continue // pinned: exempt AND uncounted (#808)\n const policy = config?.[slot.name]\n if (policy && record !== null && isHeld(policy, record, opts.now)) continue // floor blocks budget too\n const lastAccessMs = Date.parse(slot.lastAccessAt ?? slot.uploadedAt) || 0\n const isExternal = slot.external !== undefined\n const bytes = isExternal ? slot.cachedBytes ?? 0 : slot.size\n if (bytes <= 0) continue // external with no local copy: nothing cached to evict\n total += bytes\n candidates.push({\n collection: collectionName, recordId, slotName: slot.name, eTag: slot.eTag,\n bytes, kind: isExternal ? 'cache' : 'slot', lastAccessMs,\n })\n }\n }\n }\n\n // Oldest access first; deterministic tiebreak so equal timestamps (common\n // within one ms) never make the eviction order flap between runs.\n candidates.sort((a, b) =>\n a.lastAccessMs - b.lastAccessMs\n || a.collection.localeCompare(b.collection)\n || a.recordId.localeCompare(b.recordId)\n || a.slotName.localeCompare(b.slotName))\n\n let evicted = 0\n let bytesFreed = 0\n let auditEntries = 0\n for (const c of candidates) {\n if (total <= maxBytes || evicted >= opts.remainingEvictions) break\n if (!opts.dryRun) {\n if (c.kind === 'slot') {\n await ctx.deleteSlot(c.collection, c.recordId, c.slotName)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(c.collection, c.recordId, c.slotName),\n collection: c.collection,\n recordId: c.recordId,\n slotName: c.slotName,\n blobHash: c.eTag,\n reason: 'budget',\n evictedAt: opts.now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n } else {\n // Device-local cache drop only — the slot (catalog) and the\n // object-store copy are untouched, so no eviction audit entry.\n await ctx.dropLocalCache?.(c.collection, c.recordId, c.slotName)\n }\n }\n evicted += 1\n bytesFreed += c.bytes\n total -= c.bytes\n }\n\n return { evicted, bytesFreed, auditEntries }\n}\n\n/**\n * Whether a retention floor (legal hold or period-bound `retainUntil`)\n * currently blocks eviction of this record's slots. Fail-closed: a\n * throwing predicate holds the slot.\n */\nfunction isHeld<T>(policy: BlobFieldPolicy<T>, record: T, now: Date): boolean {\n if (policy.legalHold) {\n try {\n if (policy.legalHold(record)) return true\n } catch {\n return true\n }\n }\n if (policy.retainUntil) {\n try {\n const until = policy.retainUntil(record)\n if (until !== null && until !== undefined) {\n const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(String(until))\n if (!Number.isFinite(t)) return true // fail-closed: unparseable retainUntil holds the slot\n if (t > now.getTime()) return true\n }\n } catch {\n return true\n }\n }\n return false\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n const identity = { collection: BLOB_EVICTION_AUDIT_COLLECTION, id: entry.id, by: entry.actor, version: 1 }\n const body = { ts: entry.evictedAt }\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n envelope = buildRecordEnvelope(identity, { ...body, iv, data })\n } else {\n envelope = buildRecordEnvelope(identity, { ...body, iv: '', data: json })\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  installViaBinder
3
- } from "./chunk-RVTOCAHQ.js";
3
+ } from "./chunk-KDX6VEYV.js";
4
4
  import {
5
5
  SealedHandle
6
- } from "./chunk-5UGEGEIJ.js";
6
+ } from "./chunk-3Y4OFWQJ.js";
7
7
  import {
8
8
  ClassifiedConfigError,
9
9
  ClassifiedNotEnabledError
10
- } from "./chunk-I64WQSN3.js";
10
+ } from "./chunk-P7L3OQLL.js";
11
11
 
12
12
  // src/via/classified/descriptor.ts
13
13
  function isClassifiedFieldSpec(x) {
@@ -262,4 +262,4 @@ export {
262
262
  enforceClassifiedWrite,
263
263
  NO_CLASSIFIED
264
264
  };
265
- //# sourceMappingURL=chunk-5VVBZ6E4.js.map
265
+ //# sourceMappingURL=chunk-N2HKBESK.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-Y2NBBT6K.js";
4
4
  import {
5
5
  sha256Hex
6
- } from "./chunk-QTDAPDDV.js";
6
+ } from "./chunk-VJ4B3ZXY.js";
7
7
 
8
8
  // src/with-shape/persisted-schemas/derive.ts
9
9
  function isZodSchema(value) {
@@ -79,4 +79,4 @@ export {
79
79
  isZod4Schema,
80
80
  derivePersistedSchema
81
81
  };
82
- //# sourceMappingURL=chunk-HNOBRU24.js.map
82
+ //# sourceMappingURL=chunk-N7YAQKAO.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  NonAdditiveSchemaChangeError,
3
3
  SchemaLockedError
4
- } from "./chunk-I64WQSN3.js";
4
+ } from "./chunk-P7L3OQLL.js";
5
5
 
6
6
  // src/with-shape/schema-update/strategies.ts
7
7
  function blindUpdate() {
@@ -62,4 +62,4 @@ export {
62
62
  lockSchema,
63
63
  coordinatedCutover
64
64
  };
65
- //# sourceMappingURL=chunk-6U7M7AX6.js.map
65
+ //# sourceMappingURL=chunk-NH2MST6J.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
- NOYDB_FORMAT_VERSION
3
- } from "./chunk-5UGEGEIJ.js";
2
+ buildRecordEnvelope
3
+ } from "./chunk-ZVAFJQZB.js";
4
4
 
5
5
  // src/with-party/team/managed-secret.ts
6
6
  var MemorySealingKeyProvider = class {
@@ -178,14 +178,11 @@ async function saveSealedSecret(store, vault, payload) {
178
178
  payload: bytesToBase64(payload.sealed)
179
179
  };
180
180
  const prior = await store.get(vault, "_meta", SEALED_SECRET_RECORD_ID);
181
- const env = {
182
- _noydb: NOYDB_FORMAT_VERSION,
183
- _v: (prior?._v ?? 0) + 1,
184
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
181
+ const env = buildRecordEnvelope(
182
+ { collection: "_meta", id: SEALED_SECRET_RECORD_ID, version: (prior?._v ?? 0) + 1 },
185
183
  // AES-GCM bypassed — the sealing layer is the security boundary.
186
- _iv: "",
187
- _data: JSON.stringify(persisted)
188
- };
184
+ { iv: "", data: JSON.stringify(persisted) }
185
+ );
189
186
  await store.put(vault, "_meta", SEALED_SECRET_RECORD_ID, env);
190
187
  }
191
188
  async function loadSealedSecret(store, vault) {
@@ -227,4 +224,4 @@ export {
227
224
  loadSealedSecret,
228
225
  resolveManagedSecret
229
226
  };
230
- //# sourceMappingURL=chunk-JAHIL6WO.js.map
227
+ //# sourceMappingURL=chunk-NRNTXBHT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-party/team/managed-secret.ts"],"sourcesContent":["/**\n * Managed-secret mode — rubber-hose-resistant vaults.\n *\n * A vault mode where the secret is machine-generated and never\n * exposed to the user, sealed under a developer-provided\n * {@link SealingKeyProvider} (macOS Keychain, Windows Credential\n * Manager, libsecret, AWS KMS, …). The user has no secret to give\n * up to coercion — they can't reveal what they don't know.\n *\n * ## Components in this file\n *\n * - {@link SealingKeyProvider} — the interface concrete providers\n * implement. Provider implementations live OUTSIDE hub (per-\n * platform packages).\n * - {@link MemorySealingKeyProvider} — in-memory test provider; uses\n * a deterministic per-instance \"key\" so two providers with\n * different ids cannot unseal each other's outputs.\n * - {@link RecipientHint} — public material a sender uses to seal\n * plaintext for a specific recipient; published by\n * {@link RecipientSealer.publishRecipientHint} and transported\n * out-of-band to the sender before bundle writes.\n * - {@link RecipientSealer} — interface for asymmetric/granted\n * providers that support recipient-target sealing (RSA-OAEP,\n * cloud-KMS asymmetric, etc.); distinct from self-only\n * {@link SealingKeyProvider} (macOS Keychain, WebAuthn-PRF).\n * - {@link MemoryRecipientSealer} — in-process reference\n * implementation of both `RecipientSealer` and\n * `SealingKeyProvider` using real WebCrypto RSA-OAEP + AES-GCM;\n * safe for tests and same-process sender/recipient scenarios.\n * - {@link loadSealedSecret} / {@link saveSealedSecret} —\n * plaintext envelope storage at `_meta/sealed-secret`.\n * Mirrors the `_meta/handle` and `_meta/public-envelope` AES-\n * GCM-bypassed patterns. The sealing layer (provider's job)\n * is the security boundary; hub doesn't have a key to encrypt\n * with at this layer — that's the whole point of the design.\n * - {@link resolveManagedSecret} — orchestrates the \"generate +\n * seal + persist on first open; unseal on reopen\" flow.\n * Returns the plaintext secret string that the rest of the\n * `createNoydb` keyring path consumes.\n *\n * Deferred to follow-ups:\n * - Block `rotate-secret` policy gate under managed mode.\n * - Mandatory strong-recovery enforcement.\n * - Recovery flow under managed mode (generates fresh sealed phrase).\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Managed-secret mode\n *\n * @module\n */\n\nimport { buildRecordEnvelope } from '../../kernel/enclave/index.js'\nimport type { NoydbStore, RecipientSealer } from '../../kernel/types.js'\n\n/**\n * The contract concrete providers (per-platform key stores) implement\n * to seal and unseal a hub-generated random secret. The plaintext\n * secret NEVER leaves hub-controlled memory in unsealed form —\n * the provider receives the bytes, returns opaque sealed bytes, and\n * later reverses the operation. Hub treats the sealed bytes as\n * fully opaque.\n *\n * Implementations live OUTSIDE `@noy-db/hub` (separate packages\n * per the issue's \"Concrete providers (live outside hub)\" note):\n *\n * | Platform | Package (TBD) | Backing |\n * |---|---|---|\n * | macOS | `@noy-db/seal-macos-keychain` | Security.framework |\n * | Windows | `@noy-db/seal-wincred` | Credential Manager |\n * | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |\n * | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |\n */\nexport interface SealingKeyProvider {\n /**\n * Non-sensitive identifier disclosed in the persisted envelope.\n * Surfaced to consumers via `loadSealedSecret().providerId` so\n * a vault opened with the wrong provider class can detect the\n * mismatch and surface a clear error. NOT secret — fine to log.\n *\n * Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,\n * `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never\n * parses this; it's purely audit metadata.\n */\n readonly id: string\n\n /** Seal raw secret bytes. Output bytes are opaque to hub. */\n seal(secret: Uint8Array): Promise<Uint8Array>\n\n /**\n * Reverse {@link seal}. MUST throw on tamper, wrong-provider, or\n * any other failure — hub treats a thrown error as \"this provider\n * cannot unlock this vault\" and surfaces it to the caller.\n */\n unseal(sealed: Uint8Array): Promise<Uint8Array>\n}\n\n/**\n * In-memory test provider. NOT secure — uses a deterministic\n * per-instance \"key\" (16-byte SHA-256 of `id`) XOR'd over the\n * secret plus a 4-byte provider-id fingerprint prefix. The XOR is\n * sufficient to make different `id` values produce mutually-unsealable\n * outputs (the contract tests for that), but offers ZERO real\n * confidentiality — never use outside tests.\n *\n * Replace with a real platform provider in production.\n */\nexport class MemorySealingKeyProvider implements SealingKeyProvider {\n readonly id: string\n private readonly fingerprint: Uint8Array\n private readonly keyBytes: Uint8Array\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n // Deterministic 4-byte fingerprint of the provider id, prepended\n // to every sealed output so we can detect \"wrong provider\" at\n // unseal time without leaking anything sensitive about either\n // provider's actual key material.\n const encoded = new TextEncoder().encode(opts.id)\n let h = 0\n for (let i = 0; i < encoded.length; i++) {\n h = (h * 31 + encoded[i]!) >>> 0\n }\n this.fingerprint = new Uint8Array([\n (h >>> 24) & 0xff, (h >>> 16) & 0xff, (h >>> 8) & 0xff, h & 0xff,\n ])\n // Deterministic 16-byte \"key\" derived from the id by repeating\n // the fingerprint with offsets. Good enough for the XOR-stream\n // test cipher; never confuse this with real key derivation.\n this.keyBytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n this.keyBytes[i] = this.fingerprint[i % 4]! ^ (i * 17)\n }\n }\n\n async seal(secret: Uint8Array): Promise<Uint8Array> {\n const out = new Uint8Array(4 + secret.length)\n out.set(this.fingerprint, 0)\n for (let i = 0; i < secret.length; i++) {\n out[4 + i] = secret[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n\n async unseal(sealed: Uint8Array): Promise<Uint8Array> {\n if (sealed.length < 4) {\n throw new Error('MemorySealingKeyProvider: sealed input too short')\n }\n for (let i = 0; i < 4; i++) {\n if (sealed[i] !== this.fingerprint[i]) {\n throw new Error(\n `MemorySealingKeyProvider(\"${this.id}\"): provider-id mismatch on unseal `\n + '(sealed bytes were produced by a different provider)',\n )\n }\n }\n const body = sealed.subarray(4)\n const out = new Uint8Array(body.length)\n for (let i = 0; i < body.length; i++) {\n out[i] = body[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n}\n\n/**\n * Public material a sender uses to seal-for-this-recipient. Published by\n * a recipient's RecipientSealer; transported to the sender out-of-band\n * (email, S3, in-app message). The sender obtains the hint, supplies it\n * to writePod's sealedCredentials.perUser[userId].hint, and the\n * hub seals each user's credential against it. Per foundation §11.4.\n */\nexport type RecipientHint = {\n readonly v: 1\n /** Recipient's provider id; matches the SealedAutoUnlockEntry.pid they'll unseal under. */\n readonly pid: string\n /** Algorithm the sender uses to produce the seal. Slice 1 ships RSA-OAEP-SHA256 only. */\n readonly alg: 'rsa-oaep-sha256'\n /** Public material — alg-specific. For 'rsa-oaep-sha256': { publicKeyPem: string }. */\n readonly material: Readonly<Record<string, unknown>>\n}\n\n// Hoisted to kernel/types.ts (C3 — enclave self-containment: sealing.ts\n// consumes this as a spine-owned contract type). Re-exported here so existing\n// importers of this module are unaffected.\nexport type { RecipientSealer } from '../../kernel/types.js'\n\n/**\n * Shared RSA-OAEP-SHA256 + AES-GCM seal in the canonical recipient-target\n * TLV wire format. Mints a fresh 32-byte CEK, AES-GCM-encrypts `plaintext`\n * under it, RSA-OAEP-SHA256-wraps the CEK to `publicKeyPem`, and packs:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * This is the single source of truth for the wire format — both\n * {@link MemoryRecipientSealer} and external sealers (e.g. `@noy-db/at-aws-kms`'s\n * asymmetric-KMS recipient sealer) call it so a blob sealed by one unseals\n * by the other. WebCrypto RSA-OAEP/SHA-256 here is wire-compatible with\n * AWS KMS `RSAES_OAEP_SHA_256` (both RSAES-OAEP, SHA-256 hash, MGF1-SHA256,\n * empty label).\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function sealRsaOaepTlv(plaintext: Uint8Array, publicKeyPem: string): Promise<Uint8Array> {\n // Parse PEM → SPKI bytes.\n const b64 = publicKeyPem.replace(/-----BEGIN PUBLIC KEY-----/, '').replace(/-----END PUBLIC KEY-----/, '').replace(/\\s+/g, '')\n const spki = base64ToBytes(b64)\n const recipientPub = await crypto.subtle.importKey(\n 'spki', spki as BufferSource,\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false, ['encrypt'],\n )\n // Mint fresh CEK + IV, AES-GCM encrypt plaintext.\n const cekBytes = crypto.getRandomValues(new Uint8Array(32))\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, plaintext as BufferSource))\n // RSA-OAEP-wrap the CEK bytes.\n const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, recipientPub, cekBytes as BufferSource))\n cekBytes.fill(0)\n if (wrapped.length !== 256) {\n throw new Error(`sealRsaOaepTlv: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`)\n }\n // TLV layout.\n const out = new Uint8Array(1 + 256 + 12 + ct.length)\n out[0] = 0x01\n out.set(wrapped, 1)\n out.set(iv, 1 + 256)\n out.set(ct, 1 + 256 + 12)\n return out\n}\n\n/**\n * Parse a {@link sealRsaOaepTlv} blob into its three segments without\n * decrypting. The `wrapped` CEK is RSA-OAEP-SHA256 ciphertext over a\n * 32-byte CEK — the unwrap step is pluggable: {@link MemoryRecipientSealer}\n * decrypts it with a local RSA private key; `@noy-db/at-aws-kms` hands it to\n * KMS `Decrypt`. After unwrapping, pass the CEK + `iv` + `ct` to\n * {@link aesGcmOpen}.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport function parseRsaOaepTlv(bytes: Uint8Array): { wrapped: Uint8Array; iv: Uint8Array; ct: Uint8Array } {\n if (bytes.length < 1 + 256 + 12 + 16) {\n throw new Error('parseRsaOaepTlv: sealed input too short')\n }\n if (bytes[0] !== 0x01) {\n throw new Error(`parseRsaOaepTlv: unknown TLV version ${bytes[0]}`)\n }\n return {\n wrapped: bytes.subarray(1, 1 + 256),\n iv: bytes.subarray(1 + 256, 1 + 256 + 12),\n ct: bytes.subarray(1 + 256 + 12),\n }\n}\n\n/**\n * AES-GCM-decrypt the `ct` segment of a {@link parseRsaOaepTlv} result under\n * the unwrapped 32-byte CEK and its `iv`. Throws on a bad tag (tamper) — the\n * same authenticated-decryption guarantee the TLV relies on.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function aesGcmOpen(cekBytes: Uint8Array, iv: Uint8Array, ct: Uint8Array): Promise<Uint8Array> {\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['decrypt'])\n return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, ct as BufferSource))\n}\n\n/**\n * Reference implementation of `RecipientSealer` + `SealingKeyProvider`.\n * Uses WebCrypto RSA-OAEP-SHA256 (2048-bit) to wrap a fresh 32-byte\n * AES-GCM CEK, AES-GCM-encrypts plaintext under it, and packs the\n * result into a self-describing TLV:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * Implements BOTH interfaces. `seal(plaintext)` (self-target) is just\n * `sealForRecipient(plaintext, this own hint)` — same TLV. Convenient\n * for tests where one provider plays both ends. Real cloud providers\n * (`at-aws-kms`, etc.) will pick their own internal layouts; the only\n * contract is round-trip identity.\n *\n * SAFE for production within its scope — the cryptography is real\n * (RSA-OAEP + AES-GCM via WebCrypto), but the keypair lives in-process\n * and is regenerated on every construction. Not suitable as a managed\n * keychain; use it for tests and for shipping bundles where the\n * recipient instance lives in the same process as the sender (rare).\n */\nexport class MemoryRecipientSealer implements SealingKeyProvider, RecipientSealer {\n readonly id: string\n private readonly keypair: Promise<CryptoKeyPair>\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n this.keypair = crypto.subtle.generateKey(\n { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },\n true,\n ['encrypt', 'decrypt'],\n )\n }\n\n async publishRecipientHint(): Promise<RecipientHint> {\n const { publicKey } = await this.keypair\n const spki = await crypto.subtle.exportKey('spki', publicKey)\n const pem = '-----BEGIN PUBLIC KEY-----\\n'\n + bytesToBase64(new Uint8Array(spki)).match(/.{1,64}/g)!.join('\\n')\n + '\\n-----END PUBLIC KEY-----\\n'\n return { v: 1, pid: this.id, alg: 'rsa-oaep-sha256', material: { publicKeyPem: pem } }\n }\n\n async sealForRecipient(plaintext: Uint8Array, hint: RecipientHint): Promise<Uint8Array> {\n if (hint.v !== 1) {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.v ${String(hint.v)} (expected 1)`)\n }\n if (hint.alg !== 'rsa-oaep-sha256') {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.alg '${String(hint.alg)}' (expected 'rsa-oaep-sha256')`)\n }\n const pem = hint.material['publicKeyPem']\n if (typeof pem !== 'string') {\n throw new Error('MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string')\n }\n return sealRsaOaepTlv(plaintext, pem)\n }\n\n async seal(plaintext: Uint8Array): Promise<Uint8Array> {\n const hint = await this.publishRecipientHint()\n return this.sealForRecipient(plaintext, hint)\n }\n\n async unseal(bytes: Uint8Array): Promise<Uint8Array> {\n const { wrapped, iv, ct } = parseRsaOaepTlv(bytes)\n const { privateKey } = await this.keypair\n // Local RSA-OAEP-SHA256 unwrap of the CEK — the pluggable step external\n // sealers replace with KMS Decrypt.\n const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, wrapped as BufferSource))\n const pt = await aesGcmOpen(cekBytes, iv, ct)\n cekBytes.fill(0)\n return pt\n }\n}\n\n// ─── Persisted envelope ────────────────────────────────────────────────\n\n/** Reserved id for the managed-secret envelope under `_meta`. */\nexport const SEALED_SECRET_RECORD_ID = 'sealed-secret' as const\n\n/** Plaintext payload stored inside the `_meta/sealed-secret` envelope. */\nexport interface SealedSecret {\n readonly _noydb_sealed: 1\n readonly providerId: string\n /** Sealed bytes. Base64-encoded on the wire; decoded on load. */\n readonly sealed: Uint8Array\n}\n\n/**\n * Wire-format envelope persisted at `_meta/sealed-secret` for\n * managed-mode vaults. The provider produces raw sealed bytes via\n * {@link SealingKeyProvider.seal}; this wrapper carries the dispatch\n * metadata hub needs to pick the right provider on the unseal path.\n *\n * Stability boundary: once shipped, the wire format only grows by\n * adding optional fields. See the at-* sealing dimension foundation\n * doc, §11.9.1.\n *\n * v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.\n *\n * Legacy shape (earlier releases): `{ _noydb_sealed: 1, providerId, sealed }`\n * — accepted on read for backwards compatibility; never produced on\n * write going forward.\n */\nexport interface SealedEnvelope {\n /** Envelope schema version. v1 is the current shape. */\n readonly v: 1\n /** Magic marker for forensics + legacy-shape detection. */\n readonly _noydb_sealed: 1\n /** Matches the producing provider's `.id`. Dispatch key on unseal. */\n readonly pid: string\n /** Sealed bytes from the provider, base64-encoded on the wire. */\n readonly payload: string\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Parse a `_meta/sealed-secret` `_data` JSON string into the\n * in-memory {@link SealedSecret} representation. Accepts both:\n *\n * 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —\n * the current shape.\n * 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —\n * read-only; never written\n * going forward.\n *\n * Returns `undefined` for any input that doesn't match either shape,\n * so callers can fall back to \"no managed-mode envelope present.\"\n *\n * @internal — exported only for the migration safety-net test suite.\n */\nexport function parseSealedEnvelope(raw: unknown): SealedSecret | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined\n const r = raw as Record<string, unknown>\n if (r._noydb_sealed !== 1) return undefined\n\n // v1 shape — preferred.\n if (\n r.v === 1\n && typeof r.pid === 'string'\n && typeof r.payload === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.pid,\n sealed: base64ToBytes(r.payload),\n }\n }\n\n // Legacy shape — earlier releases. Accept on read for compat.\n if (\n typeof r.providerId === 'string'\n && typeof r.sealed === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.providerId,\n sealed: base64ToBytes(r.sealed),\n }\n }\n\n return undefined\n}\n\nexport async function saveSealedSecret(\n store: NoydbStore,\n vault: string,\n payload: { readonly providerId: string; readonly sealed: Uint8Array },\n): Promise<void> {\n const persisted: SealedEnvelope = {\n v: 1,\n _noydb_sealed: 1,\n pid: payload.providerId,\n payload: bytesToBase64(payload.sealed),\n }\n const prior = await store.get(vault, '_meta', SEALED_SECRET_RECORD_ID)\n const env = buildRecordEnvelope(\n { collection: '_meta', id: SEALED_SECRET_RECORD_ID, version: (prior?._v ?? 0) + 1 },\n // AES-GCM bypassed — the sealing layer is the security boundary.\n { iv: '', data: JSON.stringify(persisted) },\n )\n await store.put(vault, '_meta', SEALED_SECRET_RECORD_ID, env)\n}\n\nexport async function loadSealedSecret(\n store: NoydbStore,\n vault: string,\n): Promise<SealedSecret | undefined> {\n const envelope = await store.get(vault, '_meta', SEALED_SECRET_RECORD_ID)\n if (!envelope) return undefined\n try {\n return parseSealedEnvelope(JSON.parse(envelope._data))\n } catch {\n return undefined\n }\n}\n\n// ─── createNoydb orchestration ─────────────────────────────────────────\n\n/**\n * Resolve the effective plaintext secret string for a managed-mode\n * vault. Two paths:\n *\n * 1. **First open (no envelope persisted):** generate a 256-bit random\n * via `crypto.getRandomValues`, base64-encode for use as a\n * secret string, seal the underlying bytes under the\n * provider, persist `_meta/sealed-secret`, return the\n * base64 string.\n *\n * 2. **Reopen (envelope exists):** read + unseal + decode → return.\n * A different provider whose `seal` output disagrees on the\n * stored bytes throws here, surfaced as a clear error.\n *\n * The returned string is the same shape that `secret:` would take in\n * standard mode — the rest of the keyring path consumes it\n * unchanged.\n *\n * @internal — called from `createNoydb` / `getKeyringInternal`.\n */\nexport async function resolveManagedSecret(\n store: NoydbStore,\n vault: string,\n provider: SealingKeyProvider,\n): Promise<string> {\n const existing = await loadSealedSecret(store, vault)\n if (existing) {\n if (existing.providerId !== provider.id) {\n throw new Error(\n `Managed-mode vault \"${vault}\" was sealed under provider id `\n + `\"${existing.providerId}\" but the current SealingKeyProvider is `\n + `\"${provider.id}\". Pass the same provider that originally enrolled `\n + 'the vault, or treat this as a fresh enrollment and clear '\n + '`_meta/sealed-secret` first.',\n )\n }\n const plaintext = await provider.unseal(existing.sealed)\n return bytesToBase64(plaintext)\n }\n\n // First open: mint a 256-bit random, seal, persist.\n const random = new Uint8Array(32)\n globalThis.crypto.getRandomValues(random)\n const sealed = await provider.seal(random)\n await saveSealedSecret(store, vault, { providerId: provider.id, sealed })\n return bytesToBase64(random)\n}\n"],"mappings":"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","names":[]}
@@ -2,14 +2,15 @@ import {
2
2
  generateULID
3
3
  } from "./chunk-4AOERLTV.js";
4
4
  import {
5
+ buildRecordAad,
5
6
  openEnvelopeJson
6
- } from "./chunk-BKYQNNO3.js";
7
+ } from "./chunk-D5UDZANO.js";
7
8
  import {
8
- encrypt
9
- } from "./chunk-QTDAPDDV.js";
9
+ buildRecordEnvelope
10
+ } from "./chunk-ZVAFJQZB.js";
10
11
  import {
11
- NOYDB_FORMAT_VERSION
12
- } from "./chunk-5UGEGEIJ.js";
12
+ encrypt
13
+ } from "./chunk-VJ4B3ZXY.js";
13
14
 
14
15
  // src/with-audit/consent/consent.ts
15
16
  var CONSENT_AUDIT_COLLECTION = "_consent_audit";
@@ -29,7 +30,7 @@ async function loadConsentEntries(adapter, vault, encrypted, getDEK, filter = {}
29
30
  for (const id of ids.sort()) {
30
31
  const envelope = await adapter.get(vault, CONSENT_AUDIT_COLLECTION, id);
31
32
  if (!envelope) continue;
32
- const entry = await decryptEntry(envelope, encrypted, getDEK);
33
+ const entry = await decryptEntry(id, envelope, encrypted, getDEK);
33
34
  if (!matchesFilter(entry, filter)) continue;
34
35
  entries.push(entry);
35
36
  }
@@ -37,28 +38,18 @@ async function loadConsentEntries(adapter, vault, encrypted, getDEK, filter = {}
37
38
  }
38
39
  async function buildEnvelope(entry, encrypted, getDEK) {
39
40
  const json = JSON.stringify(entry);
41
+ const identity = { collection: CONSENT_AUDIT_COLLECTION, id: entry.id, version: 1 };
42
+ const body = { ts: entry.timestamp };
40
43
  if (!encrypted) {
41
- return {
42
- _noydb: NOYDB_FORMAT_VERSION,
43
- _v: 1,
44
- _ts: entry.timestamp,
45
- _iv: "",
46
- _data: json
47
- };
44
+ return buildRecordEnvelope(identity, { ...body, iv: "", data: json });
48
45
  }
49
46
  const dek = await getDEK(CONSENT_AUDIT_COLLECTION);
50
- const { iv, data } = await encrypt(json, dek);
51
- return {
52
- _noydb: NOYDB_FORMAT_VERSION,
53
- _v: 1,
54
- _ts: entry.timestamp,
55
- _iv: iv,
56
- _data: data
57
- };
47
+ const { iv, data } = await encrypt(json, dek, buildRecordAad(identity));
48
+ return buildRecordEnvelope(identity, { ...body, iv, data });
58
49
  }
59
- async function decryptEntry(envelope, encrypted, getDEK) {
50
+ async function decryptEntry(id, envelope, encrypted, getDEK) {
60
51
  if (!encrypted) return JSON.parse(envelope._data);
61
- const json = await openEnvelopeJson(envelope, await getDEK(CONSENT_AUDIT_COLLECTION));
52
+ const json = await openEnvelopeJson({ collection: CONSENT_AUDIT_COLLECTION, id }, envelope, await getDEK(CONSENT_AUDIT_COLLECTION));
62
53
  return JSON.parse(json);
63
54
  }
64
55
  function matchesFilter(entry, f) {
@@ -75,4 +66,4 @@ export {
75
66
  writeConsentEntry,
76
67
  loadConsentEntries
77
68
  };
78
- //# sourceMappingURL=chunk-URVL4EKU.js.map
69
+ //# sourceMappingURL=chunk-NYSD43OE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-audit/consent/consent.ts"],"sourcesContent":["/**\n * Consent boundaries — per-access audit log.\n *\n * ```ts\n * const audit = await vault.withConsent(\n * { purpose: 'quarterly-review', consentHash: '7f3a...' },\n * async () => {\n * const invoices = await vault.collection<Invoice>('invoices').list()\n * return invoices\n * },\n * )\n *\n * const log = await vault.consentAudit({ since: '2026-01-01T00:00:00Z' })\n * // → entries: { actor, purpose, consentHash, ts, op, collection, id }\n * ```\n *\n * ## Contract\n *\n * Every `get` / `put` / `delete` that happens inside a `withConsent`\n * callback writes one entry to the reserved `_consent_audit`\n * collection. Entries are encrypted with the vault's consent-audit\n * DEK (separate from per-user-collection DEKs so access-log queries\n * don't require unwrapping individual collection keys). Outside a\n * `withConsent` scope, no entries are written — consent is\n * opt-in by design (GDPR Art. 7: *demonstrable*, *specific*\n * consent).\n *\n * ## Why store the hash, not the consent text?\n *\n * The `consentHash` is the sha256 of whatever consent receipt the\n * actor presented (a signed GDPR banner click, a HIPAA authorisation\n * PDF, an API-level `X-Consent-Hash` header). Storing only the hash:\n *\n * 1. Keeps the audit log small and indexable.\n * 2. Preserves zero-knowledge at the adapter — adapters see\n * ciphertext envelopes of `{ actor, purpose, consentHash, ts,\n * op, collection, id }`, never the consent record itself.\n * 3. Lets the regulator verify a presented consent doc matches\n * the logged hash at audit time without the system ever\n * possessing the doc.\n *\n * ## Concurrency\n *\n * The consent context lives on the {@link Vault} instance. Two\n * concurrent `withConsent` calls on the same Vault would stomp each\n * other — documented limitation; adopters needing per-flight scope\n * should use separate Vault instances or an AsyncLocalStorage shim.\n *\n * @module\n */\nimport type { EncryptedEnvelope, NoydbStore } from '../../kernel/types.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, openEnvelopeJson, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { generateULID } from '../../with-pod/ulid.js'\n\n/** Reserved collection for consent-audit entries. */\nexport const CONSENT_AUDIT_COLLECTION = '_consent_audit'\n\n/**\n * The consent scope active for a block of work. Set via\n * `vault.withConsent()`; observed by the collection's access hooks.\n */\nexport interface ConsentContext {\n /**\n * What this access is for. Used by the audit query (`consentAudit\n * ({ purpose })`) and carried in the stored entry. Free-form; the\n * regulator or compliance tooling decides the vocabulary.\n */\n readonly purpose: string\n /**\n * Hex-encoded sha256 of whatever consent artefact the actor\n * presented. Stored as-is in each entry.\n */\n readonly consentHash: string\n}\n\n/**\n * Access operation recorded in an audit entry.\n *\n * Union-widening checklist (I-2) — these three sites widen together or a\n * downstream exhaustive switch breaks at its next in-range minor:\n * 1. `ConsentOp` here (public, re-exported at `index.ts`).\n * 2. `onAccess` op union on `kernel/collection-config.ts`.\n * 3. classified ctx `onAccess` op union on `port/with/classified-strategy.ts`\n * (the `ClassifiedVerifyCtx` one — the `ClassifiedRevealCtx` one stays `'reveal'`-only).\n */\nexport type ConsentOp = 'get' | 'put' | 'delete' | 'reveal' | 'verify' | 'find'\n\n/** One consent-audit record, as decrypted for the caller. */\nexport interface ConsentAuditEntry {\n /** ULID — stable insertion-order key. */\n readonly id: string\n readonly timestamp: string\n readonly actor: string\n readonly purpose: string\n readonly consentHash: string\n readonly op: ConsentOp\n readonly collection: string\n readonly recordId: string\n}\n\n/** Filter passed to `vault.consentAudit()`. */\nexport interface ConsentAuditFilter {\n /** Only entries at or after this ISO timestamp. */\n readonly since?: string\n /** Only entries at or before this ISO timestamp. */\n readonly until?: string\n /** Match entries targeting this collection. */\n readonly collection?: string\n /** Match entries written by this actor. */\n readonly actor?: string\n /** Match entries with this purpose. */\n readonly purpose?: string\n}\n\n/**\n * Write one audit entry. Called by Vault's onAccess hook when a\n * consent context is active.\n */\nexport async function writeConsentEntry(\n adapter: NoydbStore,\n vault: string,\n encrypted: boolean,\n entry: Omit<ConsentAuditEntry, 'id' | 'timestamp'>,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n): Promise<void> {\n const id = generateULID()\n const full: ConsentAuditEntry = {\n id,\n timestamp: new Date().toISOString(),\n ...entry,\n }\n const envelope = await buildEnvelope(full, encrypted, getDEK)\n await adapter.put(vault, CONSENT_AUDIT_COLLECTION, id, envelope)\n}\n\n/** Load + decrypt + filter all entries. */\nexport async function loadConsentEntries(\n adapter: NoydbStore,\n vault: string,\n encrypted: boolean,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n filter: ConsentAuditFilter = {},\n): Promise<ConsentAuditEntry[]> {\n const ids = await adapter.list(vault, CONSENT_AUDIT_COLLECTION)\n const entries: ConsentAuditEntry[] = []\n\n for (const id of ids.sort()) {\n const envelope = await adapter.get(vault, CONSENT_AUDIT_COLLECTION, id)\n if (!envelope) continue\n const entry = await decryptEntry(id, envelope, encrypted, getDEK)\n if (!matchesFilter(entry, filter)) continue\n entries.push(entry)\n }\n return entries\n}\n\n// ── internals ──────────────────────────────────────────────────────\n\nasync function buildEnvelope(\n entry: ConsentAuditEntry,\n encrypted: boolean,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(entry)\n // `entry.id` IS the record id the caller writes under, so identity needs no\n // threading here (#1051).\n const identity = { collection: CONSENT_AUDIT_COLLECTION, id: entry.id, version: 1 }\n const body = { ts: entry.timestamp }\n if (!encrypted) {\n return buildRecordEnvelope(identity, { ...body, iv: '', data: json })\n }\n const dek = await getDEK(CONSENT_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n return buildRecordEnvelope(identity, { ...body, iv, data })\n}\n\nasync function decryptEntry(\n id: string,\n envelope: EncryptedEnvelope,\n encrypted: boolean,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n): Promise<ConsentAuditEntry> {\n if (!encrypted) return JSON.parse(envelope._data) as ConsentAuditEntry\n const json = await openEnvelopeJson({ collection: CONSENT_AUDIT_COLLECTION, id }, envelope, await getDEK(CONSENT_AUDIT_COLLECTION))\n return JSON.parse(json) as ConsentAuditEntry\n}\n\nfunction matchesFilter(entry: ConsentAuditEntry, f: ConsentAuditFilter): boolean {\n if (f.since && entry.timestamp < f.since) return false\n if (f.until && entry.timestamp > f.until) return false\n if (f.collection && entry.collection !== f.collection) return false\n if (f.actor && entry.actor !== f.actor) return false\n if (f.purpose && entry.purpose !== f.purpose) return false\n return true\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuDO,IAAM,2BAA2B;AA+DxC,eAAsB,kBACpB,SACA,OACA,WACA,OACA,QACe;AACf,QAAM,KAAK,aAAa;AACxB,QAAM,OAA0B;AAAA,IAC9B;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,GAAG;AAAA,EACL;AACA,QAAM,WAAW,MAAM,cAAc,MAAM,WAAW,MAAM;AAC5D,QAAM,QAAQ,IAAI,OAAO,0BAA0B,IAAI,QAAQ;AACjE;AAGA,eAAsB,mBACpB,SACA,OACA,WACA,QACA,SAA6B,CAAC,GACA;AAC9B,QAAM,MAAM,MAAM,QAAQ,KAAK,OAAO,wBAAwB;AAC9D,QAAM,UAA+B,CAAC;AAEtC,aAAW,MAAM,IAAI,KAAK,GAAG;AAC3B,UAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,0BAA0B,EAAE;AACtE,QAAI,CAAC,SAAU;AACf,UAAM,QAAQ,MAAM,aAAa,IAAI,UAAU,WAAW,MAAM;AAChE,QAAI,CAAC,cAAc,OAAO,MAAM,EAAG;AACnC,YAAQ,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAIA,eAAe,cACb,OACA,WACA,QAC4B;AAC5B,QAAM,OAAO,KAAK,UAAU,KAAK;AAGjC,QAAM,WAAW,EAAE,YAAY,0BAA0B,IAAI,MAAM,IAAI,SAAS,EAAE;AAClF,QAAM,OAAO,EAAE,IAAI,MAAM,UAAU;AACnC,MAAI,CAAC,WAAW;AACd,WAAO,oBAAoB,UAAU,EAAE,GAAG,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;AAAA,EACtE;AACA,QAAM,MAAM,MAAM,OAAO,wBAAwB;AACjD,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,QAAQ,MAAM,KAAK,eAAe,QAAQ,CAAC;AACtE,SAAO,oBAAoB,UAAU,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC;AAC5D;AAEA,eAAe,aACb,IACA,UACA,WACA,QAC4B;AAC5B,MAAI,CAAC,UAAW,QAAO,KAAK,MAAM,SAAS,KAAK;AAChD,QAAM,OAAO,MAAM,iBAAiB,EAAE,YAAY,0BAA0B,GAAG,GAAG,UAAU,MAAM,OAAO,wBAAwB,CAAC;AAClI,SAAO,KAAK,MAAM,IAAI;AACxB;AAEA,SAAS,cAAc,OAA0B,GAAgC;AAC/E,MAAI,EAAE,SAAS,MAAM,YAAY,EAAE,MAAO,QAAO;AACjD,MAAI,EAAE,SAAS,MAAM,YAAY,EAAE,MAAO,QAAO;AACjD,MAAI,EAAE,cAAc,MAAM,eAAe,EAAE,WAAY,QAAO;AAC9D,MAAI,EAAE,SAAS,MAAM,UAAU,EAAE,MAAO,QAAO;AAC/C,MAAI,EAAE,WAAW,MAAM,YAAY,EAAE,QAAS,QAAO;AACrD,SAAO;AACT;","names":[]}