@noy-db/hub 0.8.0-pre.0 → 0.9.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +8 -8
- package/codemods/0.4.0-pre.json +1 -1
- package/codemods/0.8.0-pre.json +1 -1
- package/dist/api-MSJXVYQG.js +18 -0
- package/dist/as/index.js +34 -25
- package/dist/as/index.js.map +1 -1
- package/dist/at/index.js +9 -10
- package/dist/attestation/index.js +39 -28
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-7TFRVFLH.js → backup-7RKNZMQO.js} +15 -16
- package/dist/backup-7RKNZMQO.js.map +1 -0
- package/dist/blobs/index.js +13 -14
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +20 -13
- package/dist/broker/index.js.map +1 -1
- package/dist/by/index.js +2 -2
- package/dist/capsule/contract.d.ts +159 -0
- package/dist/capsule/contract.js +95 -0
- package/dist/capsule/enclave-aes/bound.d.ts +52 -0
- package/dist/{kernel/enclave → capsule/enclave-aes}/broker/proof.d.ts +12 -3
- package/dist/capsule/enclave-aes/capabilities.d.ts +2 -0
- package/dist/capsule/enclave-aes/classify/compare.d.ts +20 -0
- package/dist/{kernel/enclave → capsule/enclave-aes}/classify/find.d.ts +1 -1
- package/dist/{kernel/enclave → capsule/enclave-aes}/classify/reveal.d.ts +1 -1
- package/dist/{kernel/enclave → capsule/enclave-aes}/classify/verify.d.ts +2 -2
- package/dist/{kernel/enclave → capsule/enclave-aes}/classify/write.d.ts +1 -1
- package/dist/{kernel/enclave → capsule/enclave-aes}/crypto.d.ts +33 -129
- package/dist/capsule/enclave-aes/index.d.ts +113 -0
- package/dist/capsule/enclave-aes/index.js +361 -0
- package/dist/capsule/enclave-aes/index.js.map +1 -0
- package/dist/capsule/enclave-aes/primitives.d.ts +29 -0
- package/dist/capsule/index.d.ts +27 -0
- package/dist/capsule/plumbing/ct-equal.d.ts +11 -0
- package/dist/{kernel/enclave/record-keys → capsule/plumbing}/deterministic.d.ts +8 -21
- package/dist/capsule/plumbing/digest.d.ts +122 -0
- package/dist/{kernel/enclave/record-keys → capsule/plumbing}/envelope-body.d.ts +44 -48
- package/dist/capsule/plumbing/index.d.ts +100 -0
- package/dist/capsule/plumbing/lifecycle.d.ts +50 -0
- package/dist/{kernel/enclave/record-keys → capsule/plumbing}/record-codec.d.ts +62 -20
- package/dist/{kernel/enclave → capsule/plumbing}/record-envelope.d.ts +1 -1
- package/dist/capsule/plumbing/rekey-blob.d.ts +33 -0
- package/dist/capsule/plumbing/rekey.d.ts +44 -0
- package/dist/capsule/plumbing/sealed-slot.d.ts +22 -0
- package/dist/capsule/plumbing/sealed-slots.d.ts +37 -0
- package/dist/capsule/plumbing/sealing.d.ts +29 -0
- package/dist/capsule/plumbing/sign.d.ts +9 -0
- package/dist/{kernel/enclave/record-keys → capsule/plumbing}/tombstone.d.ts +2 -2
- package/dist/capsule-57BDK7LG.js +209 -0
- package/dist/cargo/index.js +36 -36
- package/dist/{chunk-BUDAGWL5.js → chunk-226V6XXJ.js} +41 -6
- package/dist/chunk-226V6XXJ.js.map +1 -0
- package/dist/{chunk-QWGU66MP.js → chunk-2A56DSRI.js} +5 -7
- package/dist/chunk-2A56DSRI.js.map +1 -0
- package/dist/chunk-2DX4JCRI.js +1 -0
- package/dist/{chunk-ODVPTDPK.js → chunk-2ELOCEXE.js} +1 -1
- package/dist/chunk-2ELOCEXE.js.map +1 -0
- package/dist/{chunk-CKNGS5MH.js → chunk-2WJKJZSO.js} +3 -5
- package/dist/chunk-2WJKJZSO.js.map +1 -0
- package/dist/{chunk-ZSX4S5XB.js → chunk-2ZFD2UJC.js} +9 -9
- package/dist/{chunk-YSK2CU2Q.js → chunk-3V6LZHUO.js} +3 -3
- package/dist/chunk-3V6LZHUO.js.map +1 -0
- package/dist/{chunk-HT2AHGQI.js → chunk-3Y5JLSXS.js} +2 -2
- package/dist/{chunk-M7NI3IWE.js → chunk-45VHWE4C.js} +67 -60
- package/dist/chunk-45VHWE4C.js.map +1 -0
- package/dist/{chunk-LGNDFRYC.js → chunk-4CFHL5RY.js} +1 -1
- package/dist/chunk-4CFHL5RY.js.map +1 -0
- package/dist/{enclave-27SMF6GF.js → chunk-4J3MNVVX.js} +83 -56
- package/dist/chunk-4J3MNVVX.js.map +1 -0
- package/dist/{chunk-SI3V7D4G.js → chunk-4UIP3QNW.js} +2 -2
- package/dist/{chunk-WNJ4AVGE.js → chunk-55P6OBB7.js} +2 -2
- package/dist/{chunk-6LNL42F5.js → chunk-5HNW4ZTA.js} +12 -15
- package/dist/chunk-5HNW4ZTA.js.map +1 -0
- package/dist/{chunk-SYVZW77S.js → chunk-5LMCVBMY.js} +6 -8
- package/dist/chunk-5LMCVBMY.js.map +1 -0
- package/dist/{chunk-MKXXMNJH.js → chunk-5UUJE4FP.js} +3 -3
- package/dist/chunk-5XONHAFD.js +12 -0
- package/dist/chunk-5XONHAFD.js.map +1 -0
- package/dist/{chunk-X4773RBR.js → chunk-6QLCS7ET.js} +2 -2
- package/dist/chunk-6QLCS7ET.js.map +1 -0
- package/dist/{chunk-FW22ZCXT.js → chunk-6WS35E6X.js} +2 -2
- package/dist/{chunk-FCKWQE2H.js → chunk-6Y5E4SJU.js} +7 -8
- package/dist/chunk-6Y5E4SJU.js.map +1 -0
- package/dist/{chunk-4AF3TUMP.js → chunk-72XBVN4J.js} +8 -2
- package/dist/{chunk-4AF3TUMP.js.map → chunk-72XBVN4J.js.map} +1 -1
- package/dist/{chunk-QBBUZ3I3.js → chunk-7BMOS4JQ.js} +11 -11
- package/dist/chunk-7BMOS4JQ.js.map +1 -0
- package/dist/{chunk-5FDLC6G7.js → chunk-7CT6SX4M.js} +3 -3
- package/dist/chunk-7CT6SX4M.js.map +1 -0
- package/dist/{chunk-LQ4ZHOKP.js → chunk-ABU6A4Z5.js} +36 -27
- package/dist/chunk-ABU6A4Z5.js.map +1 -0
- package/dist/{chunk-WX5ADGTZ.js → chunk-AJFPAHMW.js} +3 -3
- package/dist/chunk-AJFPAHMW.js.map +1 -0
- package/dist/{chunk-ZE4CHIED.js → chunk-APFFJN5T.js} +39 -37
- package/dist/chunk-APFFJN5T.js.map +1 -0
- package/dist/{chunk-6OWXYZK2.js → chunk-B65CC6Z6.js} +2 -2
- package/dist/{chunk-F2TDU7MQ.js → chunk-BJH77UY2.js} +2 -2
- package/dist/{chunk-L7DOS3TE.js → chunk-BMHQHH7Q.js} +3 -3
- package/dist/chunk-BMHQHH7Q.js.map +1 -0
- package/dist/{chunk-BJ2IXQ5Q.js → chunk-BNFNQNEP.js} +2 -2
- package/dist/chunk-BU2ABHFW.js +74 -0
- package/dist/chunk-BU2ABHFW.js.map +1 -0
- package/dist/{chunk-HLEK5VAN.js → chunk-BUE7R3B5.js} +2 -2
- package/dist/{chunk-ISZQJBML.js → chunk-BVF3SXD7.js} +6 -6
- package/dist/chunk-BVF3SXD7.js.map +1 -0
- package/dist/{chunk-M522UYZ2.js → chunk-BYHCKX73.js} +6 -10
- package/dist/chunk-BYHCKX73.js.map +1 -0
- package/dist/{chunk-EOVMT6TB.js → chunk-C53ER7AG.js} +2 -2
- package/dist/{chunk-K6W7BVZ6.js → chunk-CAD7N6HQ.js} +3 -3
- package/dist/chunk-CB4467G3.js +565 -0
- package/dist/chunk-CB4467G3.js.map +1 -0
- package/dist/{chunk-NWHBJQAL.js → chunk-D4V5JJAK.js} +4 -4
- package/dist/chunk-D4V5JJAK.js.map +1 -0
- package/dist/{chunk-QWFHIOD2.js → chunk-D5A3GCZ4.js} +11 -12
- package/dist/chunk-D5A3GCZ4.js.map +1 -0
- package/dist/{chunk-5O7D6ZTW.js → chunk-DHU2AQ6T.js} +2 -2
- package/dist/{chunk-DC5QVPV7.js → chunk-DL6BZKP3.js} +3 -3
- package/dist/chunk-DL6BZKP3.js.map +1 -0
- package/dist/{chunk-FQ54DWUQ.js → chunk-EA4VDI5D.js} +4 -4
- package/dist/chunk-EA4VDI5D.js.map +1 -0
- package/dist/{chunk-IT22PWKV.js → chunk-ECGXA4LT.js} +2 -2
- package/dist/{chunk-BP6DARYA.js → chunk-EMAWOGVH.js} +4 -4
- package/dist/chunk-EMAWOGVH.js.map +1 -0
- package/dist/{chunk-3F7GLNAE.js → chunk-EUD3HQM4.js} +7 -11
- package/dist/chunk-EUD3HQM4.js.map +1 -0
- package/dist/{chunk-T2LBQ5DV.js → chunk-FSQNGPHH.js} +335 -250
- package/dist/chunk-FSQNGPHH.js.map +1 -0
- package/dist/{chunk-7VCXZDA4.js → chunk-FZXRLNJO.js} +7 -11
- package/dist/chunk-FZXRLNJO.js.map +1 -0
- package/dist/{chunk-6LMQ5R26.js → chunk-G46JSSVX.js} +7 -7
- package/dist/{chunk-6LMQ5R26.js.map → chunk-G46JSSVX.js.map} +1 -1
- package/dist/{chunk-GF5BZ7D4.js → chunk-G5EDYVSE.js} +11 -13
- package/dist/chunk-G5EDYVSE.js.map +1 -0
- package/dist/chunk-G66YXUTY.js +60 -0
- package/dist/chunk-G66YXUTY.js.map +1 -0
- package/dist/{chunk-FFWI7UH7.js → chunk-GAGT3YZN.js} +1 -1
- package/dist/chunk-GAGT3YZN.js.map +1 -0
- package/dist/{chunk-4MXI3ODD.js → chunk-GCUMOKTN.js} +3 -3
- package/dist/{chunk-FH2L2E34.js → chunk-GETUTTZU.js} +4 -4
- package/dist/{chunk-FH2L2E34.js.map → chunk-GETUTTZU.js.map} +1 -1
- package/dist/chunk-GMKGGY7V.js +234 -0
- package/dist/chunk-GMKGGY7V.js.map +1 -0
- package/dist/{chunk-B6SJIBQK.js → chunk-GOJQ5DZA.js} +4 -4
- package/dist/{chunk-EIPVYUEP.js → chunk-GQ4MF3CM.js} +3 -3
- package/dist/chunk-GQ4MF3CM.js.map +1 -0
- package/dist/{chunk-PDCG6PR5.js → chunk-GRS2HIV6.js} +2 -2
- package/dist/chunk-GRS2HIV6.js.map +1 -0
- package/dist/{chunk-PEOZ34EM.js → chunk-GSAK56VF.js} +1 -1
- package/dist/chunk-GSAK56VF.js.map +1 -0
- package/dist/{chunk-3H5KLDZN.js → chunk-H3YM66EI.js} +4 -8
- package/dist/chunk-H3YM66EI.js.map +1 -0
- package/dist/{chunk-NRT6QZQB.js → chunk-IVMFIKNA.js} +6 -6
- package/dist/chunk-IVMFIKNA.js.map +1 -0
- package/dist/{chunk-PSIV7CJV.js → chunk-J4KAPKBJ.js} +2 -2
- package/dist/chunk-J4KAPKBJ.js.map +1 -0
- package/dist/{chunk-N5DNADLQ.js → chunk-JA25QG5M.js} +224 -34
- package/dist/chunk-JA25QG5M.js.map +1 -0
- package/dist/{chunk-T7IR4N2S.js → chunk-JBMMWYOH.js} +3 -1
- package/dist/{chunk-T7IR4N2S.js.map → chunk-JBMMWYOH.js.map} +1 -1
- package/dist/{chunk-YNIUIERI.js → chunk-JPSL5GIZ.js} +18 -59
- package/dist/chunk-JPSL5GIZ.js.map +1 -0
- package/dist/{chunk-TKUOEU2F.js → chunk-JSWKVZCR.js} +2 -2
- package/dist/chunk-JZ4EDEIP.js +22 -0
- package/dist/chunk-JZ4EDEIP.js.map +1 -0
- package/dist/{chunk-QOOUUR4A.js → chunk-K673OO6B.js} +10 -4
- package/dist/chunk-K673OO6B.js.map +1 -0
- package/dist/{chunk-NNARWO72.js → chunk-K7RW7FI4.js} +13 -12
- package/dist/chunk-K7RW7FI4.js.map +1 -0
- package/dist/{chunk-ZXABPVM6.js → chunk-L2F3CFCH.js} +9 -13
- package/dist/chunk-L2F3CFCH.js.map +1 -0
- package/dist/{chunk-X25NWUZB.js → chunk-L5A3KR7R.js} +3 -3
- package/dist/{chunk-T52GX4SA.js → chunk-LGGIFKJP.js} +4 -4
- package/dist/{chunk-ID6Z6E66.js → chunk-MQ7YWBQ5.js} +2 -2
- package/dist/{chunk-FXQBGFOX.js → chunk-MRJDLXI3.js} +14 -8
- package/dist/{chunk-FXQBGFOX.js.map → chunk-MRJDLXI3.js.map} +1 -1
- package/dist/chunk-NH2UOV3B.js +18 -0
- package/dist/chunk-NH2UOV3B.js.map +1 -0
- package/dist/{chunk-FN5EBVTQ.js → chunk-NIXRB5BP.js} +2 -2
- package/dist/{chunk-RHVMZSUU.js → chunk-NV4OESHC.js} +1 -1
- package/dist/chunk-NV4OESHC.js.map +1 -0
- package/dist/{chunk-HBRLNOHL.js → chunk-NYJBYVBG.js} +4 -4
- package/dist/{chunk-DF73H4UN.js → chunk-NZULV7LK.js} +5 -5
- package/dist/chunk-NZULV7LK.js.map +1 -0
- package/dist/{chunk-HK67PX4J.js → chunk-OFD2HD6F.js} +1 -1
- package/dist/chunk-OFD2HD6F.js.map +1 -0
- package/dist/{chunk-WY2XXTET.js → chunk-OJU67C23.js} +4 -4
- package/dist/{chunk-WY2XXTET.js.map → chunk-OJU67C23.js.map} +1 -1
- package/dist/{chunk-ZUVBC32X.js → chunk-OK57EATL.js} +2 -2
- package/dist/{chunk-KJFW62P4.js → chunk-OM3SEDI4.js} +652 -662
- package/dist/chunk-OM3SEDI4.js.map +1 -0
- package/dist/{chunk-TOPYQ6GA.js → chunk-PCYPXLEX.js} +6 -6
- package/dist/{chunk-GYGLAVCX.js → chunk-PFCTSXHU.js} +6 -2
- package/dist/chunk-PFCTSXHU.js.map +1 -0
- package/dist/{chunk-VNNRUGMM.js → chunk-PGC35KXE.js} +2 -2
- package/dist/{chunk-4I522ERM.js → chunk-PT2YCQVK.js} +4 -4
- package/dist/{chunk-Z5HTYMMQ.js → chunk-PTJY5R3O.js} +2 -2
- package/dist/chunk-PUBI455O.js +157 -0
- package/dist/chunk-PUBI455O.js.map +1 -0
- package/dist/{chunk-MTL7MXGR.js → chunk-QDHHHI5Q.js} +16 -17
- package/dist/chunk-QDHHHI5Q.js.map +1 -0
- package/dist/{chunk-F24ZZTHQ.js → chunk-QGFEBNHO.js} +4 -4
- package/dist/{chunk-A6IJU4TK.js → chunk-QGSNJD3B.js} +2 -2
- package/dist/chunk-QJNAHPVT.js +91 -0
- package/dist/chunk-QJNAHPVT.js.map +1 -0
- package/dist/{chunk-BK2HHQGN.js → chunk-QSHATAXJ.js} +2 -2
- package/dist/{chunk-ANWPSTZU.js → chunk-QXJXG7DN.js} +57 -30
- package/dist/chunk-QXJXG7DN.js.map +1 -0
- package/dist/{chunk-HBPPGWUM.js → chunk-RDOEOPOI.js} +14 -9
- package/dist/chunk-RDOEOPOI.js.map +1 -0
- package/dist/{chunk-JCN44NCY.js → chunk-RIZTZJX6.js} +2 -2
- package/dist/{chunk-MVZ2TPVP.js → chunk-RNACRT5H.js} +2 -2
- package/dist/chunk-RWFQVNXR.js +24 -0
- package/dist/chunk-RWFQVNXR.js.map +1 -0
- package/dist/{chunk-TROR3ALR.js → chunk-SFCD6OU7.js} +2 -2
- package/dist/{chunk-S57HTZ6X.js → chunk-SNM7LSYG.js} +5 -8
- package/dist/chunk-SNM7LSYG.js.map +1 -0
- package/dist/chunk-SPPOUJLN.js +40 -0
- package/dist/chunk-SPPOUJLN.js.map +1 -0
- package/dist/{chunk-3ELRMVJK.js → chunk-SWXLZ4HX.js} +31 -35
- package/dist/chunk-SWXLZ4HX.js.map +1 -0
- package/dist/{chunk-6B3VNR4P.js → chunk-TFWFPZRU.js} +26 -35
- package/dist/chunk-TFWFPZRU.js.map +1 -0
- package/dist/{chunk-F7XGHL5O.js → chunk-TMIJAR2X.js} +3 -3
- package/dist/{chunk-F7XGHL5O.js.map → chunk-TMIJAR2X.js.map} +1 -1
- package/dist/{chunk-I3JDRCMW.js → chunk-TZYTMZZY.js} +10 -10
- package/dist/{chunk-AOWGXU6D.js → chunk-UAKY6E4N.js} +2 -2
- package/dist/chunk-UHFGK6YA.js +40 -0
- package/dist/chunk-UHFGK6YA.js.map +1 -0
- package/dist/{chunk-3JWHAR6E.js → chunk-UNSQZSZP.js} +2 -2
- package/dist/{chunk-FQ5VRHIB.js → chunk-UOZOWN73.js} +5 -9
- package/dist/chunk-UOZOWN73.js.map +1 -0
- package/dist/{chunk-GHLTDZRS.js → chunk-V55JASXO.js} +25 -4
- package/dist/chunk-V55JASXO.js.map +1 -0
- package/dist/{chunk-244VXYPG.js → chunk-VSUQI5Q6.js} +45 -4
- package/dist/chunk-VSUQI5Q6.js.map +1 -0
- package/dist/{chunk-6K7DPM7D.js → chunk-WFWFXDNS.js} +2 -2
- package/dist/{chunk-CF5UU4S7.js → chunk-WHOKGHJ3.js} +7 -7
- package/dist/{chunk-LGRLME4M.js → chunk-X2RP4TO5.js} +16 -16
- package/dist/chunk-X2RP4TO5.js.map +1 -0
- package/dist/{chunk-VNPEWFPD.js → chunk-XA34THLF.js} +8 -6
- package/dist/chunk-XA34THLF.js.map +1 -0
- package/dist/{chunk-HVSCEIYL.js → chunk-XITHENBI.js} +12 -14
- package/dist/chunk-XITHENBI.js.map +1 -0
- package/dist/{chunk-ENAG3IY7.js → chunk-XKBTJGFP.js} +26 -28
- package/dist/chunk-XKBTJGFP.js.map +1 -0
- package/dist/{chunk-32YV3TJT.js → chunk-XXNWU5QZ.js} +2 -2
- package/dist/{chunk-B57AFBY2.js → chunk-XXQ5XIYY.js} +2 -2
- package/dist/{chunk-ORDURZBU.js → chunk-YFFMPZPY.js} +2 -2
- package/dist/{chunk-MDWKM2XC.js → chunk-YKGJ44Z7.js} +3 -3
- package/dist/{chunk-IUWCVHQ7.js → chunk-YQFBH5ZJ.js} +4 -4
- package/dist/{chunk-IUWCVHQ7.js.map → chunk-YQFBH5ZJ.js.map} +1 -1
- package/dist/{chunk-UPD5NWI5.js → chunk-Z3ZXVB2Y.js} +29 -16
- package/dist/chunk-Z3ZXVB2Y.js.map +1 -0
- package/dist/chunk-ZBUAX4EF.js +27 -0
- package/dist/chunk-ZBUAX4EF.js.map +1 -0
- package/dist/{chunk-MLPHLJIQ.js → chunk-ZHQJAV6D.js} +2 -2
- package/dist/{chunk-UD5EVGZ7.js → chunk-ZMVSKYTJ.js} +56 -96
- package/dist/chunk-ZMVSKYTJ.js.map +1 -0
- package/dist/{chunk-7JE7BDYJ.js → chunk-ZVSKSUGI.js} +5 -5
- package/dist/{chunk-J6UOHJY5.js → chunk-ZWF3EWZA.js} +4 -8
- package/dist/chunk-ZWF3EWZA.js.map +1 -0
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-W3USLL5M.js → classified-marker-KHDOYIJB.js} +3 -3
- package/dist/classified-marker-KHDOYIJB.js.map +1 -0
- package/dist/collection-facade-LZIF7GWD.js +41 -0
- package/dist/{computed-TPFBX7U4.js → computed-XMFTHAXV.js} +3 -3
- package/dist/consent/index.js +10 -11
- package/dist/consent/index.js.map +1 -1
- package/dist/cover/index.js +9 -10
- package/dist/coverage/index.js +1 -1
- package/dist/custody/index.js +13 -13
- package/dist/{dead-filter-U2UYKSZL.js → dead-filter-CXV5NS67.js} +11 -3
- package/dist/dead-filter-CXV5NS67.js.map +1 -0
- package/dist/debug/index.js +11 -2
- package/dist/debug/index.js.map +1 -1
- package/dist/{delegation-S3AEPW2T.js → delegation-ONLM4JFI.js} +52 -31
- package/dist/delegation-ONLM4JFI.js.map +1 -0
- package/dist/derivations/index.js +25 -25
- package/dist/derive-3YSIS36W.js +20 -0
- package/dist/directory/index.js +10 -11
- package/dist/{dispatch-RH4LHKBH.js → dispatch-2MQFMEGY.js} +22 -23
- package/dist/dispatch-2MQFMEGY.js.map +1 -0
- package/dist/{dispatch-3CQDJV5Q.js → dispatch-OFDA4WQV.js} +5 -5
- package/dist/executor-E6YDJ7YA.js +8 -0
- package/dist/executor-NJX3YQQ4.js +35 -0
- package/dist/executor-WWM3DD4P.js +8 -0
- package/dist/export-accessible-RLHU5G4W.js +22 -0
- package/dist/extract-partition-QII2EDCS.js +35 -0
- package/dist/{fanout-sidecar-OT6RYLOL.js → fanout-sidecar-BO67PCPO.js} +13 -15
- package/dist/fanout-sidecar-BO67PCPO.js.map +1 -0
- package/dist/find-6M7GYDUZ.js +10 -0
- package/dist/forget/index.js +26 -10
- package/dist/forget/index.js.map +1 -1
- package/dist/guards/index.js +4 -4
- package/dist/history/index.js +13 -14
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +14 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.d.ts +15 -13
- package/dist/index.js +198 -187
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +6 -6
- package/dist/introspection/index.js +16 -13
- package/dist/issue-LPEEEMPP.js +19 -0
- package/dist/kernel/attestation-types.d.ts +62 -0
- package/dist/kernel/cache/index.d.ts +1 -1
- package/dist/kernel/collection-config.d.ts +1 -48
- package/dist/kernel/collection.d.ts +2 -1
- package/dist/kernel/cutover-transform.d.ts +1 -1
- package/dist/kernel/errors.d.ts +63 -6
- package/dist/kernel/export-scope.d.ts +40 -0
- package/dist/kernel/merge-authority.d.ts +1 -1
- package/dist/kernel/noydb.d.ts +3 -1
- package/dist/kernel/paths.d.ts +29 -5
- package/dist/kernel/prepared-write.d.ts +1 -1
- package/dist/kernel/put-many-atomic.d.ts +1 -1
- package/dist/kernel/query/live/live.d.ts +2 -1
- package/dist/kernel/query/sort-key.d.ts +0 -2
- package/dist/kernel/secret-mode.d.ts +1 -1
- package/dist/kernel/tx-write-gate.d.ts +2 -4
- package/dist/kernel/types.d.ts +135 -11
- package/dist/kernel/validation.d.ts +0 -10
- package/dist/kernel/vault.d.ts +6 -2
- package/dist/kernel/via/reconcile.d.ts +1 -42
- package/dist/kernel/write-queue.d.ts +17 -0
- package/dist/{ledger-KZPCMVZY.js → ledger-MSJGWKGW.js} +11 -12
- package/dist/liberate-LEKDYKTC.js +25 -0
- package/dist/link-set-QXXSPPHL.js +29 -0
- package/dist/{managed-secret-S3MQ6SH6.js → managed-secret-VSK7ANQI.js} +10 -11
- package/dist/materialized-views/index.js +27 -28
- package/dist/{migrate-cek-V7GKJZ7O.js → migrate-cek-GEBFYBDC.js} +2 -2
- package/dist/money/index.js +5 -5
- package/dist/noydb-F2AUF6KL.js +98 -0
- package/dist/on/index.js +25 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +12 -13
- package/dist/pod/index.js +76 -74
- package/dist/{pod-handle-WGDFB4C2.js → pod-handle-YE7CT66T.js} +10 -11
- package/dist/pod-handle-YE7CT66T.js.map +1 -0
- package/dist/policy/index.js +10 -11
- package/dist/port/as/active.d.ts +4 -0
- package/dist/port/as/index.d.ts +1 -0
- package/dist/port/by/index.d.ts +14 -5
- package/dist/port/on/index.d.ts +27 -1
- package/dist/port/to/index.d.ts +1 -1
- package/dist/port/with/blob-strategy.d.ts +1 -1
- package/dist/port/with/broker-strategy.d.ts +14 -0
- package/dist/port/with/classified-strategy.d.ts +1 -1
- package/dist/port/with/collection-options.d.ts +1 -1
- package/dist/port/with/collection-registries.d.ts +1 -1
- package/dist/port/with/coverage-strategy.d.ts +17 -1
- package/dist/port/with/formats-strategy.d.ts +34 -1
- package/dist/port/with/lookup-strategy.d.ts +87 -6
- package/dist/port/with/service-bus.d.ts +2 -1
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-SLSXIPBV.js → post-register-QC5TEAW3.js} +6 -6
- package/dist/post-register-QC5TEAW3.js.map +1 -0
- package/dist/query/all/index.js +16 -16
- package/dist/query/index.js +8 -8
- package/dist/query/live/index.js +7 -7
- package/dist/query/reduce/index.js +10 -10
- package/dist/query/relate/index.js +10 -10
- package/dist/reduce/index.js +8 -8
- package/dist/register-SN72QALV.js +30 -0
- package/dist/registry-253FF7C6.js +35 -0
- package/dist/registry-3AZWQTDT.js +23 -0
- package/dist/registry-7UEBTHWG.js +8 -0
- package/dist/registry-IVJ67NFQ.js +8 -0
- package/dist/request-withdrawal-UKVJZKX2.js +28 -0
- package/dist/{reveal-4BCKPEC2.js → reveal-YQTRNQFH.js} +10 -9
- package/dist/reveal-YQTRNQFH.js.map +1 -0
- package/dist/revoke-G2KXJQA5.js +24 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +16 -18
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +4 -4
- package/dist/{seed-4JS2RJ4A.js → seed-JMQTRKRE.js} +125 -34
- package/dist/seed-JMQTRKRE.js.map +1 -0
- package/dist/sequence/index.js +9 -10
- package/dist/session/index.js +9 -10
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.js +2 -2
- package/dist/signer-IUIYR3CX.js +23 -0
- package/dist/snapshots/index.js +11 -11
- package/dist/stale-SKV33PBU.js +28 -0
- package/dist/storage-NPBYP2SM.js +21 -0
- package/dist/store/index.js +1 -1
- package/dist/{store-coordination-provider-2O2VUHKW.js → store-coordination-provider-Z6HKPOGM.js} +11 -12
- package/dist/store-coordination-provider-Z6HKPOGM.js.map +1 -0
- package/dist/sync/index.js +18 -13
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.js +18 -17
- package/dist/tiers/index.js +11 -12
- package/dist/to/index.js +2 -2
- package/dist/transactions/index.js +3 -3
- package/dist/util/index.js +1 -1
- package/dist/vault-head/index.js +11 -14
- package/dist/vault-head/index.js.map +1 -1
- package/dist/{verify-RVT67IMH.js → verify-KWQIWLGH.js} +14 -12
- package/dist/verify-KWQIWLGH.js.map +1 -0
- package/dist/via/classified/binding.d.ts +2 -2
- package/dist/via/classified/config-drift.d.ts +2 -2
- package/dist/via/classified/errors.d.ts +1 -1
- package/dist/via/classified/presets.d.ts +29 -0
- package/dist/via/classified/resolve.d.ts +7 -0
- package/dist/via/geo/binding.d.ts +0 -4
- package/dist/via/lookup/handle.d.ts +4 -4
- package/dist/via/lookup/index.d.ts +32 -0
- package/dist/via/money/binding.d.ts +0 -9
- package/dist/walk-F4OLXI5H.js +20 -0
- package/dist/with-audit/attestation/index.d.ts +2 -1
- package/dist/with-audit/attestation/issue.d.ts +2 -2
- package/dist/with-audit/attestation/revoke.d.ts +2 -2
- package/dist/with-audit/attestation/scheme.d.ts +7 -0
- package/dist/with-audit/attestation/signer.d.ts +1 -1
- package/dist/with-audit/attestation/strategy.d.ts +4 -3
- package/dist/with-audit/attestation/types.d.ts +10 -0
- package/dist/with-audit/attestation/vault-facade.d.ts +9 -2
- package/dist/with-audit/attestation/verifier.d.ts +6 -0
- package/dist/with-audit/consent/consent.d.ts +1 -1
- package/dist/with-audit/consent/strategy.d.ts +1 -1
- package/dist/with-audit/forget/completeness.d.ts +66 -0
- package/dist/with-audit/forget/index.d.ts +2 -0
- package/dist/with-audit/forget/strategy.d.ts +65 -0
- package/dist/with-audit/forget/subject-index.d.ts +1 -1
- package/dist/with-audit/periods/periods.d.ts +0 -9
- package/dist/with-audit/periods/reserved-collections.d.ts +7 -0
- package/dist/with-audit/periods/vault-facade.d.ts +1 -1
- package/dist/with-audit/portability/request-withdrawal.d.ts +0 -1
- package/dist/with-audit/portability/withdraw-accessible.d.ts +0 -1
- package/dist/with-audit/sealed-record/index.d.ts +2 -2
- package/dist/with-audit/sealed-record/strategy.d.ts +1 -1
- package/dist/with-audit/tiers/index.d.ts +3 -3
- package/dist/with-audit/tiers/strategy.d.ts +19 -0
- package/dist/with-cargo/adopt-partition.d.ts +1 -1
- package/dist/with-cargo/extract-partition.d.ts +1 -47
- package/dist/with-cargo/floor.d.ts +1 -1
- package/dist/with-commit/history/history.d.ts +2 -2
- package/dist/with-commit/history/index.d.ts +1 -1
- package/dist/with-commit/history/ledger/store.d.ts +1 -1
- package/dist/with-commit/history/strategy.d.ts +1 -1
- package/dist/with-commit/history/time-machine.d.ts +1 -1
- package/dist/with-commit/numbering/index.d.ts +1 -3
- package/dist/with-commit/sequence/index.d.ts +1 -1
- package/dist/with-commit/tx/transaction.d.ts +2 -24
- package/dist/with-commit/vault-head/head.d.ts +1 -1
- package/dist/with-commit/vault-head/index.d.ts +1 -1
- package/dist/with-commit/vault-head/strategy.d.ts +8 -1
- package/dist/with-commit/vault-head/verify.d.ts +1 -1
- package/dist/with-fork/snapshots/noydb-facade.d.ts +7 -0
- package/dist/with-formula/derivations/dispatch.d.ts +1 -1
- package/dist/with-formula/derivations/fanout-sidecar.d.ts +1 -1
- package/dist/with-formula/derivations/trigger-match.d.ts +8 -5
- package/dist/with-formula/materialized-views/emit-memo.d.ts +0 -2
- package/dist/with-lookup/embeddings/index.d.ts +1 -2
- package/dist/with-lookup/indexing/collection-facade.d.ts +2 -16
- package/dist/with-lookup/indexing/eager-indexes.d.ts +2 -1
- package/dist/with-lookup/search/collection-facade.d.ts +1 -13
- package/dist/with-party/broker/index.d.ts +4 -4
- package/dist/with-party/broker/seed.d.ts +25 -10
- package/dist/with-party/directory/user-envelope/storage.d.ts +1 -1
- package/dist/with-party/policy/noydb-facade.d.ts +1 -31
- package/dist/with-party/team/delegation.d.ts +59 -3
- package/dist/with-party/team/echo-secret.d.ts +2 -4
- package/dist/with-party/team/index.d.ts +1 -1
- package/dist/with-party/team/keyring.d.ts +2 -22
- package/dist/with-party/team/magic-link-grant.d.ts +12 -2
- package/dist/with-party/team/noydb-facade.d.ts +5 -0
- package/dist/with-party/team/recovery.d.ts +1 -1
- package/dist/with-party/team/reserved-secret-collections.d.ts +10 -8
- package/dist/with-party/team/roster-tag.d.ts +43 -2
- package/dist/with-party/team/rotate-recover.d.ts +1 -1
- package/dist/with-party/team/tiers.d.ts +36 -5
- package/dist/with-party/team/wrapped-deks.d.ts +1 -1
- package/dist/with-pod/index.d.ts +1 -1
- package/dist/with-shape/blobs/blob-compaction.d.ts +1 -1
- package/dist/with-shape/blobs/blob-intent.d.ts +1 -1
- package/dist/with-shape/blobs/blob-set.d.ts +25 -2
- package/dist/with-shape/blobs/legacy-sweep.d.ts +17 -1
- package/dist/with-shape/introspection/describe.d.ts +2 -3
- package/dist/with-shape/introspection/index.d.ts +1 -0
- package/dist/with-shape/introspection/types.d.ts +2 -1
- package/dist/with-shape/introspection/walk.d.ts +1 -1
- package/dist/with-shape/links/lazy-handle.d.ts +1 -1
- package/dist/with-shape/links/link-set.d.ts +1 -1
- package/dist/with-shape/manifest/derive.d.ts +1 -1
- package/dist/with-shape/manifest/storage.d.ts +1 -1
- package/dist/with-shape/persisted-schemas/register.d.ts +1 -1
- package/dist/with-shape/persisted-schemas/storage.d.ts +1 -1
- package/dist/with-shape/satellites/dead-filter.d.ts +1 -1
- package/dist/with-shape/satellites/declare.d.ts +1 -1
- package/dist/with-shape/satellites/marker.d.ts +1 -1
- package/dist/with-shape/satellites/post-register.d.ts +1 -1
- package/dist/with-shape/schema-update/fence.d.ts +7 -0
- package/dist/with-sync/engine.d.ts +30 -1
- package/dist/with-sync/presence.d.ts +5 -10
- package/dist/with-sync/reserved-mirror.d.ts +89 -0
- package/dist/with-sync/strategy.d.ts +7 -0
- package/dist/with-sync/tab-write-relay.d.ts +0 -10
- package/dist/withdraw-accessible-ZKPLZ66E.js +25 -0
- package/dist/withdraw-accessible-ZKPLZ66E.js.map +1 -0
- package/package.json +14 -5
- package/dist/api-GVYEKSJE.js +0 -19
- package/dist/backup-7TFRVFLH.js.map +0 -1
- package/dist/chunk-244VXYPG.js.map +0 -1
- package/dist/chunk-3ELRMVJK.js.map +0 -1
- package/dist/chunk-3F7GLNAE.js.map +0 -1
- package/dist/chunk-3H5KLDZN.js.map +0 -1
- package/dist/chunk-5FDLC6G7.js.map +0 -1
- package/dist/chunk-6B3VNR4P.js.map +0 -1
- package/dist/chunk-6LNL42F5.js.map +0 -1
- package/dist/chunk-6QSQQLSE.js +0 -95
- package/dist/chunk-6QSQQLSE.js.map +0 -1
- package/dist/chunk-7VCXZDA4.js.map +0 -1
- package/dist/chunk-ANWPSTZU.js.map +0 -1
- package/dist/chunk-BP6DARYA.js.map +0 -1
- package/dist/chunk-BUDAGWL5.js.map +0 -1
- package/dist/chunk-CKNGS5MH.js.map +0 -1
- package/dist/chunk-DC5QVPV7.js.map +0 -1
- package/dist/chunk-DE4DH2RZ.js +0 -29
- package/dist/chunk-DE4DH2RZ.js.map +0 -1
- package/dist/chunk-DF73H4UN.js.map +0 -1
- package/dist/chunk-EIPVYUEP.js.map +0 -1
- package/dist/chunk-ENAG3IY7.js.map +0 -1
- package/dist/chunk-FCKWQE2H.js.map +0 -1
- package/dist/chunk-FFWI7UH7.js.map +0 -1
- package/dist/chunk-FQ54DWUQ.js.map +0 -1
- package/dist/chunk-FQ5VRHIB.js.map +0 -1
- package/dist/chunk-GF5BZ7D4.js.map +0 -1
- package/dist/chunk-GHLTDZRS.js.map +0 -1
- package/dist/chunk-GYGLAVCX.js.map +0 -1
- package/dist/chunk-HBPPGWUM.js.map +0 -1
- package/dist/chunk-HK67PX4J.js.map +0 -1
- package/dist/chunk-HVSCEIYL.js.map +0 -1
- package/dist/chunk-IEX7NK2K.js +0 -125
- package/dist/chunk-IEX7NK2K.js.map +0 -1
- package/dist/chunk-ISZQJBML.js.map +0 -1
- package/dist/chunk-J6UOHJY5.js.map +0 -1
- package/dist/chunk-KJFW62P4.js.map +0 -1
- package/dist/chunk-L7DOS3TE.js.map +0 -1
- package/dist/chunk-LGNDFRYC.js.map +0 -1
- package/dist/chunk-LGRLME4M.js.map +0 -1
- package/dist/chunk-LP7BEXCT.js +0 -32
- package/dist/chunk-LP7BEXCT.js.map +0 -1
- package/dist/chunk-LQ4ZHOKP.js.map +0 -1
- package/dist/chunk-M522UYZ2.js.map +0 -1
- package/dist/chunk-M7NI3IWE.js.map +0 -1
- package/dist/chunk-MTL7MXGR.js.map +0 -1
- package/dist/chunk-N5DNADLQ.js.map +0 -1
- package/dist/chunk-NNARWO72.js.map +0 -1
- package/dist/chunk-NRT6QZQB.js.map +0 -1
- package/dist/chunk-NWHBJQAL.js.map +0 -1
- package/dist/chunk-ODVPTDPK.js.map +0 -1
- package/dist/chunk-PDCG6PR5.js.map +0 -1
- package/dist/chunk-PEOZ34EM.js.map +0 -1
- package/dist/chunk-PSIV7CJV.js.map +0 -1
- package/dist/chunk-QBBUZ3I3.js.map +0 -1
- package/dist/chunk-QOOUUR4A.js.map +0 -1
- package/dist/chunk-QWFHIOD2.js.map +0 -1
- package/dist/chunk-QWGU66MP.js.map +0 -1
- package/dist/chunk-RHVMZSUU.js.map +0 -1
- package/dist/chunk-S57HTZ6X.js.map +0 -1
- package/dist/chunk-SEE332EV.js +0 -66
- package/dist/chunk-SEE332EV.js.map +0 -1
- package/dist/chunk-SOIQ4MM4.js +0 -20
- package/dist/chunk-SOIQ4MM4.js.map +0 -1
- package/dist/chunk-SYVZW77S.js.map +0 -1
- package/dist/chunk-T2LBQ5DV.js.map +0 -1
- package/dist/chunk-UBSFPGVJ.js +0 -1
- package/dist/chunk-UD5EVGZ7.js.map +0 -1
- package/dist/chunk-UPD5NWI5.js.map +0 -1
- package/dist/chunk-UYB234ZR.js +0 -479
- package/dist/chunk-UYB234ZR.js.map +0 -1
- package/dist/chunk-VNPEWFPD.js.map +0 -1
- package/dist/chunk-WX5ADGTZ.js.map +0 -1
- package/dist/chunk-X4773RBR.js.map +0 -1
- package/dist/chunk-YNIUIERI.js.map +0 -1
- package/dist/chunk-YSK2CU2Q.js.map +0 -1
- package/dist/chunk-ZE4CHIED.js.map +0 -1
- package/dist/chunk-ZXABPVM6.js.map +0 -1
- package/dist/classified-marker-W3USLL5M.js.map +0 -1
- package/dist/collection-facade-QU6IJBN3.js +0 -48
- package/dist/dead-filter-U2UYKSZL.js.map +0 -1
- package/dist/delegation-S3AEPW2T.js.map +0 -1
- package/dist/derive-OX7F6TGG.js +0 -21
- package/dist/dispatch-RH4LHKBH.js.map +0 -1
- package/dist/executor-B6SRO2QS.js +0 -36
- package/dist/executor-BI5CHWRY.js +0 -8
- package/dist/executor-RBKM5W5K.js +0 -8
- package/dist/export-accessible-VMA3YM5V.js +0 -22
- package/dist/extract-partition-NF7PXGPP.js +0 -37
- package/dist/fanout-sidecar-OT6RYLOL.js.map +0 -1
- package/dist/find-LZMP26KF.js +0 -10
- package/dist/issue-IHARXWW5.js +0 -18
- package/dist/kernel/enclave/classify/compare.d.ts +0 -10
- package/dist/kernel/enclave/index.d.ts +0 -87
- package/dist/kernel/enclave/record-keys/lifecycle.d.ts +0 -124
- package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +0 -88
- package/dist/kernel/enclave/record-keys/rekey.d.ts +0 -91
- package/dist/kernel/enclave/record-keys/sealed-slot.d.ts +0 -14
- package/dist/kernel/enclave/record-keys/sealed-slots.d.ts +0 -123
- package/dist/kernel/enclave/record-keys/sealing.d.ts +0 -70
- package/dist/liberate-GRLCZMW4.js +0 -24
- package/dist/link-set-JCMJ7ULP.js +0 -30
- package/dist/noydb-SWQJ3MZS.js +0 -97
- package/dist/pod-handle-WGDFB4C2.js.map +0 -1
- package/dist/post-register-SLSXIPBV.js.map +0 -1
- package/dist/register-F3OYE7MJ.js +0 -31
- package/dist/registry-CILQFXFK.js +0 -8
- package/dist/registry-XNNYQVKV.js +0 -8
- package/dist/registry-YCDZV3VW.js +0 -35
- package/dist/registry-ZGYS33TE.js +0 -24
- package/dist/request-withdrawal-Z5NC6TXT.js +0 -30
- package/dist/reveal-4BCKPEC2.js.map +0 -1
- package/dist/revoke-ZL62WC2D.js +0 -23
- package/dist/seed-4JS2RJ4A.js.map +0 -1
- package/dist/signer-MBZZMXTC.js +0 -24
- package/dist/stale-TJ3DYZMQ.js +0 -29
- package/dist/storage-SVMKRGCW.js +0 -22
- package/dist/store-coordination-provider-2O2VUHKW.js.map +0 -1
- package/dist/verify-RVT67IMH.js.map +0 -1
- package/dist/walk-HJ42EISE.js +0 -21
- package/dist/withdraw-accessible-5NZUPAVA.js +0 -27
- /package/dist/{api-GVYEKSJE.js.map → api-MSJXVYQG.js.map} +0 -0
- /package/dist/{collection-facade-QU6IJBN3.js.map → capsule/contract.js.map} +0 -0
- /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/bidx.d.ts +0 -0
- /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/digest.d.ts +0 -0
- /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/vdig.d.ts +0 -0
- /package/dist/{kernel/enclave/classify → capsule/plumbing}/kofn.d.ts +0 -0
- /package/dist/{kernel/enclave/classify → capsule/plumbing}/normalize.d.ts +0 -0
- /package/dist/{kernel/enclave → capsule/plumbing}/record-aad.d.ts +0 -0
- /package/dist/{chunk-UBSFPGVJ.js.map → capsule-57BDK7LG.js.map} +0 -0
- /package/dist/{computed-TPFBX7U4.js.map → chunk-2DX4JCRI.js.map} +0 -0
- /package/dist/{chunk-ZSX4S5XB.js.map → chunk-2ZFD2UJC.js.map} +0 -0
- /package/dist/{chunk-HT2AHGQI.js.map → chunk-3Y5JLSXS.js.map} +0 -0
- /package/dist/{chunk-SI3V7D4G.js.map → chunk-4UIP3QNW.js.map} +0 -0
- /package/dist/{chunk-WNJ4AVGE.js.map → chunk-55P6OBB7.js.map} +0 -0
- /package/dist/{chunk-MKXXMNJH.js.map → chunk-5UUJE4FP.js.map} +0 -0
- /package/dist/{chunk-FW22ZCXT.js.map → chunk-6WS35E6X.js.map} +0 -0
- /package/dist/{chunk-6OWXYZK2.js.map → chunk-B65CC6Z6.js.map} +0 -0
- /package/dist/{chunk-F2TDU7MQ.js.map → chunk-BJH77UY2.js.map} +0 -0
- /package/dist/{chunk-BJ2IXQ5Q.js.map → chunk-BNFNQNEP.js.map} +0 -0
- /package/dist/{chunk-HLEK5VAN.js.map → chunk-BUE7R3B5.js.map} +0 -0
- /package/dist/{chunk-EOVMT6TB.js.map → chunk-C53ER7AG.js.map} +0 -0
- /package/dist/{chunk-K6W7BVZ6.js.map → chunk-CAD7N6HQ.js.map} +0 -0
- /package/dist/{chunk-5O7D6ZTW.js.map → chunk-DHU2AQ6T.js.map} +0 -0
- /package/dist/{chunk-IT22PWKV.js.map → chunk-ECGXA4LT.js.map} +0 -0
- /package/dist/{chunk-4MXI3ODD.js.map → chunk-GCUMOKTN.js.map} +0 -0
- /package/dist/{chunk-B6SJIBQK.js.map → chunk-GOJQ5DZA.js.map} +0 -0
- /package/dist/{chunk-TKUOEU2F.js.map → chunk-JSWKVZCR.js.map} +0 -0
- /package/dist/{chunk-X25NWUZB.js.map → chunk-L5A3KR7R.js.map} +0 -0
- /package/dist/{chunk-T52GX4SA.js.map → chunk-LGGIFKJP.js.map} +0 -0
- /package/dist/{chunk-ID6Z6E66.js.map → chunk-MQ7YWBQ5.js.map} +0 -0
- /package/dist/{chunk-FN5EBVTQ.js.map → chunk-NIXRB5BP.js.map} +0 -0
- /package/dist/{chunk-HBRLNOHL.js.map → chunk-NYJBYVBG.js.map} +0 -0
- /package/dist/{chunk-ZUVBC32X.js.map → chunk-OK57EATL.js.map} +0 -0
- /package/dist/{chunk-TOPYQ6GA.js.map → chunk-PCYPXLEX.js.map} +0 -0
- /package/dist/{chunk-VNNRUGMM.js.map → chunk-PGC35KXE.js.map} +0 -0
- /package/dist/{chunk-4I522ERM.js.map → chunk-PT2YCQVK.js.map} +0 -0
- /package/dist/{chunk-Z5HTYMMQ.js.map → chunk-PTJY5R3O.js.map} +0 -0
- /package/dist/{chunk-F24ZZTHQ.js.map → chunk-QGFEBNHO.js.map} +0 -0
- /package/dist/{chunk-A6IJU4TK.js.map → chunk-QGSNJD3B.js.map} +0 -0
- /package/dist/{chunk-BK2HHQGN.js.map → chunk-QSHATAXJ.js.map} +0 -0
- /package/dist/{chunk-JCN44NCY.js.map → chunk-RIZTZJX6.js.map} +0 -0
- /package/dist/{chunk-MVZ2TPVP.js.map → chunk-RNACRT5H.js.map} +0 -0
- /package/dist/{chunk-TROR3ALR.js.map → chunk-SFCD6OU7.js.map} +0 -0
- /package/dist/{chunk-I3JDRCMW.js.map → chunk-TZYTMZZY.js.map} +0 -0
- /package/dist/{chunk-AOWGXU6D.js.map → chunk-UAKY6E4N.js.map} +0 -0
- /package/dist/{chunk-3JWHAR6E.js.map → chunk-UNSQZSZP.js.map} +0 -0
- /package/dist/{chunk-6K7DPM7D.js.map → chunk-WFWFXDNS.js.map} +0 -0
- /package/dist/{chunk-CF5UU4S7.js.map → chunk-WHOKGHJ3.js.map} +0 -0
- /package/dist/{chunk-32YV3TJT.js.map → chunk-XXNWU5QZ.js.map} +0 -0
- /package/dist/{chunk-B57AFBY2.js.map → chunk-XXQ5XIYY.js.map} +0 -0
- /package/dist/{chunk-ORDURZBU.js.map → chunk-YFFMPZPY.js.map} +0 -0
- /package/dist/{chunk-MDWKM2XC.js.map → chunk-YKGJ44Z7.js.map} +0 -0
- /package/dist/{chunk-MLPHLJIQ.js.map → chunk-ZHQJAV6D.js.map} +0 -0
- /package/dist/{chunk-7JE7BDYJ.js.map → chunk-ZVSKSUGI.js.map} +0 -0
- /package/dist/{derive-OX7F6TGG.js.map → collection-facade-LZIF7GWD.js.map} +0 -0
- /package/dist/{enclave-27SMF6GF.js.map → computed-XMFTHAXV.js.map} +0 -0
- /package/dist/{executor-B6SRO2QS.js.map → derive-3YSIS36W.js.map} +0 -0
- /package/dist/{dispatch-3CQDJV5Q.js.map → dispatch-OFDA4WQV.js.map} +0 -0
- /package/dist/{executor-BI5CHWRY.js.map → executor-E6YDJ7YA.js.map} +0 -0
- /package/dist/{executor-RBKM5W5K.js.map → executor-NJX3YQQ4.js.map} +0 -0
- /package/dist/{export-accessible-VMA3YM5V.js.map → executor-WWM3DD4P.js.map} +0 -0
- /package/dist/{extract-partition-NF7PXGPP.js.map → export-accessible-RLHU5G4W.js.map} +0 -0
- /package/dist/{find-LZMP26KF.js.map → extract-partition-QII2EDCS.js.map} +0 -0
- /package/dist/{issue-IHARXWW5.js.map → find-6M7GYDUZ.js.map} +0 -0
- /package/dist/{ledger-KZPCMVZY.js.map → issue-LPEEEMPP.js.map} +0 -0
- /package/dist/{liberate-GRLCZMW4.js.map → ledger-MSJGWKGW.js.map} +0 -0
- /package/dist/{link-set-JCMJ7ULP.js.map → liberate-LEKDYKTC.js.map} +0 -0
- /package/dist/{managed-secret-S3MQ6SH6.js.map → link-set-QXXSPPHL.js.map} +0 -0
- /package/dist/{noydb-SWQJ3MZS.js.map → managed-secret-VSK7ANQI.js.map} +0 -0
- /package/dist/{migrate-cek-V7GKJZ7O.js.map → migrate-cek-GEBFYBDC.js.map} +0 -0
- /package/dist/{register-F3OYE7MJ.js.map → noydb-F2AUF6KL.js.map} +0 -0
- /package/dist/{registry-CILQFXFK.js.map → register-SN72QALV.js.map} +0 -0
- /package/dist/{registry-XNNYQVKV.js.map → registry-253FF7C6.js.map} +0 -0
- /package/dist/{registry-YCDZV3VW.js.map → registry-3AZWQTDT.js.map} +0 -0
- /package/dist/{registry-ZGYS33TE.js.map → registry-7UEBTHWG.js.map} +0 -0
- /package/dist/{request-withdrawal-Z5NC6TXT.js.map → registry-IVJ67NFQ.js.map} +0 -0
- /package/dist/{revoke-ZL62WC2D.js.map → request-withdrawal-UKVJZKX2.js.map} +0 -0
- /package/dist/{signer-MBZZMXTC.js.map → revoke-G2KXJQA5.js.map} +0 -0
- /package/dist/{stale-TJ3DYZMQ.js.map → signer-IUIYR3CX.js.map} +0 -0
- /package/dist/{storage-SVMKRGCW.js.map → stale-SKV33PBU.js.map} +0 -0
- /package/dist/{walk-HJ42EISE.js.map → storage-NPBYP2SM.js.map} +0 -0
- /package/dist/{withdraw-accessible-5NZUPAVA.js.map → walk-F4OLXI5H.js.map} +0 -0
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRecordAad,
|
|
3
|
+
buildRecordEnvelope,
|
|
4
|
+
encrypt,
|
|
3
5
|
openEnvelopeJson
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
buildRecordEnvelope
|
|
7
|
-
} from "./chunk-SEE332EV.js";
|
|
8
|
-
import {
|
|
9
|
-
encrypt
|
|
10
|
-
} from "./chunk-UYB234ZR.js";
|
|
6
|
+
} from "./chunk-4J3MNVVX.js";
|
|
11
7
|
import {
|
|
12
8
|
ConflictError,
|
|
13
9
|
USER_ENVELOPE_COLLECTION,
|
|
14
10
|
USER_ENVELOPE_MAX_BYTES,
|
|
15
11
|
UserEnvelopeOversizedError
|
|
16
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-VSUQI5Q6.js";
|
|
17
13
|
|
|
18
14
|
// src/with-party/directory/storage.ts
|
|
19
15
|
var META_COLLECTION = "_meta";
|
|
@@ -22,7 +18,7 @@ async function readDirectoryConfig(store, vault) {
|
|
|
22
18
|
const envelope = await store.get(vault, META_COLLECTION, DIRECTORY_RECORD_ID);
|
|
23
19
|
if (!envelope) return void 0;
|
|
24
20
|
try {
|
|
25
|
-
const parsed = JSON.parse(envelope._data);
|
|
21
|
+
const parsed = JSON.parse(envelope._data ?? "");
|
|
26
22
|
if (!isDirectoryConfig(parsed)) return void 0;
|
|
27
23
|
return parsed;
|
|
28
24
|
} catch {
|
|
@@ -51,7 +47,7 @@ async function readUserVisibility(store, vault, keyringId) {
|
|
|
51
47
|
const envelope = await store.get(vault, META_COLLECTION, visibilityRecordId(keyringId));
|
|
52
48
|
if (!envelope) return void 0;
|
|
53
49
|
try {
|
|
54
|
-
const parsed = JSON.parse(envelope._data);
|
|
50
|
+
const parsed = JSON.parse(envelope._data ?? "");
|
|
55
51
|
if (!isUserVisibility(parsed)) return void 0;
|
|
56
52
|
return parsed;
|
|
57
53
|
} catch {
|
|
@@ -142,4 +138,4 @@ export {
|
|
|
142
138
|
deleteUserEnvelope,
|
|
143
139
|
listUserEnvelopeIds
|
|
144
140
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-FZXRLNJO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-party/directory/storage.ts","../src/with-party/directory/visibility.ts","../src/with-party/directory/user-envelope/storage.ts"],"sourcesContent":["/**\n * Persistence helpers for the vault-level user-directory toggle\n * (`_meta/directory`). Mirrors the bypass-AES pattern used by\n * `_meta/policy` — the directory document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * @see https://github.com/noy-db/docs/blob/main/content/docs/services/user-envelope.md → Directory visibility\n * @see https://github.com/noy-db/docs/blob/main/content/docs/services/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../capsule/index.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { DirectoryConfig } from './types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level directory document. */\nexport const DIRECTORY_RECORD_ID = 'directory'\n\n/**\n * Read the directory toggle from `_meta/directory`. Returns `undefined`\n * when no document has been persisted — callers treat that as the\n * default-on case (`{ enabled: true }`).\n *\n * Tolerates corrupted documents the same way `_meta/policy` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so a\n * bad write never permanently breaks team enumeration.\n */\nexport async function readDirectoryConfig(\n store: NoydbStore,\n vault: string,\n): Promise<DirectoryConfig | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, DIRECTORY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data ?? '') as unknown\n if (!isDirectoryConfig(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the directory toggle at `_meta/directory`. Idempotent — call\n * on every `db.setDirectoryEnabled()` invocation. Owner-only at the\n * caller site; this primitive does not check roles.\n */\nexport async function persistDirectoryConfig(\n store: NoydbStore,\n vault: string,\n config: DirectoryConfig,\n): Promise<void> {\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: DIRECTORY_RECORD_ID, version: 1 },\n { iv: '', data: JSON.stringify({ enabled: config.enabled }) },\n )\n await store.put(vault, META_COLLECTION, DIRECTORY_RECORD_ID, envelope)\n}\n\nfunction isDirectoryConfig(x: unknown): x is DirectoryConfig {\n if (x === null || typeof x !== 'object') return false\n if (!('enabled' in x)) return false\n return typeof (x as { enabled: unknown }).enabled === 'boolean'\n}\n","/**\n * Persistence helpers for the per-user visibility flag\n * (`_meta/visibility/<keyringId>`). Mirrors the bypass-AES pattern used\n * by `_meta/policy` — the visibility document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * Stored alongside the keyring file rather than inside the encrypted\n * user envelope (`_users/<keyringId>`) because:\n *\n * - `UserEnvelope<T>.data` is opaque-to-hub by contract — hub does not\n * introspect or reserve any keys inside it. Adding `hidden` there\n * would violate that contract.\n * - `listUsersWithEnvelopes` filters by the flag, and the filter must\n * work even when decryption fails (legacy keyrings predating the\n * envelope feature, or a corrupted envelope).\n *\n * @see https://github.com/noy-db/docs/blob/main/content/docs/services/user-envelope.md → Directory visibility\n * @see https://github.com/noy-db/docs/blob/main/content/docs/services/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../capsule/index.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { UserVisibility } from './types.js'\nimport { META_COLLECTION } from './storage.js'\n\n/** Prefix for per-user visibility records inside `_meta`. */\nexport const VISIBILITY_RECORD_PREFIX = 'visibility/'\n\n/** Compose the `_meta` record id for a keyring's visibility doc. */\nexport function visibilityRecordId(keyringId: string): string {\n return VISIBILITY_RECORD_PREFIX + keyringId\n}\n\n/**\n * Read the visibility flag for `keyringId`. Returns `undefined` when no\n * document has been persisted — callers treat that as the default-visible\n * case (`{ hidden: false }`).\n */\nexport async function readUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<UserVisibility | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, visibilityRecordId(keyringId))\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data ?? '') as unknown\n if (!isUserVisibility(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the visibility flag for `keyringId` at\n * `_meta/visibility/<keyringId>`. Idempotent — call on every\n * `vault.user.setMyVisibility()` invocation. Own-only at the caller\n * site; this primitive does not enforce keyring ownership.\n */\nexport async function persistUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n visibility: UserVisibility,\n): Promise<void> {\n const recordId = visibilityRecordId(keyringId)\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: recordId, version: 1 },\n { iv: '', data: JSON.stringify({ hidden: visibility.hidden }) },\n )\n await store.put(vault, META_COLLECTION, recordId, envelope)\n}\n\n/**\n * Delete the visibility flag for `keyringId`. Called from `revoke()`\n * alongside `deleteUserEnvelope` so the sidecar does not leak to a\n * re-granted principal with the same `userId`. Idempotent — the store's\n * `delete()` is already a no-op when the record is absent.\n */\nexport async function deleteUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, META_COLLECTION, visibilityRecordId(keyringId))\n}\n\nfunction isUserVisibility(x: unknown): x is UserVisibility {\n if (x === null || typeof x !== 'object') return false\n if (!('hidden' in x)) return false\n return typeof (x as { hidden: unknown }).hidden === 'boolean'\n}\n","/**\n * Persistence helpers for per-principal user envelopes stored at\n * `_users/<keyringId>` (logically: `_meta/user/<keyringId>`).\n *\n * Unlike `_meta/policy` and `_meta/handle` which are plaintext, user\n * envelopes carry user data and are encrypted with a dedicated\n * {@link USER_ENVELOPE_COLLECTION} DEK (provisioned at vault open and\n * propagated to every keyring via the system-collection DEK path in\n * `team/keyring.ts`).\n *\n * This module is the **storage primitive** layer. The public API\n * (`vault.user.*`) sits on top of this; permission gates, own-only\n * write enforcement, and presence-channel propagation live there.\n *\n * @see design-history/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport type { NoydbStore, UserEnvelope } from '../../../kernel/types.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, openEnvelopeJson, type EnclaveKey } from '../../../capsule/index.js'\nimport { ConflictError, UserEnvelopeOversizedError } from '../../../kernel/errors.js'\nimport {\n USER_ENVELOPE_COLLECTION,\n USER_ENVELOPE_MAX_BYTES,\n} from '../../../kernel/constants.js'\n\n/**\n * Read and decrypt the user envelope for `keyringId`. Returns `null`\n * when no envelope has been persisted (either the principal has never\n * called `updateMe`, or the keyring predates this feature).\n *\n * Decryption errors propagate — a tampered or wrong-keyed envelope\n * surfaces as the underlying crypto error rather than masquerading as\n * \"not found\".\n */\nexport async function loadUserEnvelope<T = unknown>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n dek: EnclaveKey,\n): Promise<UserEnvelope<T> | null> {\n const envelope = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (!envelope) return null\n const plaintext = await openEnvelopeJson({ collection: USER_ENVELOPE_COLLECTION, id: keyringId }, envelope, dek)\n const data = JSON.parse(plaintext) as T\n return {\n keyringId,\n data,\n _v: envelope._v,\n _ts: envelope._ts,\n }\n}\n\n/**\n * Encrypt and persist the user envelope for `keyringId`. The new\n * version is `(prior._v ?? 0) + 1`. Pass `expectedVersion` to enable\n * optimistic-concurrency checks: a mismatch with the stored version\n * throws {@link ConflictError} with the actual stored version.\n *\n * `expectedVersion: 0` means \"expect no prior envelope\"; the write\n * succeeds only if no envelope exists yet.\n *\n * Soft-caps the JSON-serialized payload at {@link USER_ENVELOPE_MAX_BYTES};\n * larger payloads throw {@link UserEnvelopeOversizedError}.\n */\nexport async function saveUserEnvelope<T>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n payload: T,\n dek: EnclaveKey,\n expectedVersion?: number,\n): Promise<UserEnvelope<T>> {\n const json = JSON.stringify(payload)\n // TextEncoder counts bytes correctly for multi-byte UTF-8 (Thai text,\n // emoji, etc.) — JSON.stringify().length would undercount.\n const bytes = new TextEncoder().encode(json).byteLength\n if (bytes > USER_ENVELOPE_MAX_BYTES) {\n throw new UserEnvelopeOversizedError(bytes)\n }\n\n const prior = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (expectedVersion !== undefined) {\n const priorVersion = prior?._v ?? 0\n if (priorVersion !== expectedVersion) {\n throw new ConflictError(\n priorVersion,\n `User envelope for \"${keyringId}\" expected version ${expectedVersion}, ` +\n `actual ${priorVersion}`,\n )\n }\n }\n\n const nextVersion = (prior?._v ?? 0) + 1\n const ts = new Date().toISOString()\n const identity = { collection: USER_ENVELOPE_COLLECTION, id: keyringId, version: nextVersion }\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n\n const envelope = buildRecordEnvelope(\n identity,\n { ts, iv, data },\n )\n await store.put(vault, USER_ENVELOPE_COLLECTION, keyringId, envelope)\n\n return {\n keyringId,\n data: payload,\n _v: nextVersion,\n _ts: ts,\n }\n}\n\n/**\n * Delete the user envelope for `keyringId`. Idempotent — no error if\n * the envelope is already absent. Called from the keyring revoke path\n * (cascade-delete) and is a no-op for keyrings that never wrote.\n */\nexport async function deleteUserEnvelope(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, USER_ENVELOPE_COLLECTION, keyringId)\n}\n\n/**\n * List the keyring ids that have a user envelope persisted in `vault`.\n * Order is store-defined — callers that need a stable order should sort.\n */\nexport async function listUserEnvelopeIds(\n store: NoydbStore,\n vault: string,\n): Promise<string[]> {\n return store.list(vault, USER_ENVELOPE_COLLECTION)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,28 +7,28 @@ import {
|
|
|
7
7
|
deriveSchemaManifest,
|
|
8
8
|
loadSchemaManifestEntry,
|
|
9
9
|
saveSchemaManifest
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5LMCVBMY.js";
|
|
11
11
|
import {
|
|
12
12
|
MANIFEST_COLLECTION
|
|
13
13
|
} from "./chunk-EPFF7LOX.js";
|
|
14
14
|
import {
|
|
15
15
|
loadFence,
|
|
16
16
|
saveFence
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BMHQHH7Q.js";
|
|
18
18
|
import {
|
|
19
19
|
derivePersistedSchema
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-J4KAPKBJ.js";
|
|
21
21
|
import {
|
|
22
22
|
loadPersistedSchemaEntry,
|
|
23
23
|
savePersistedSchema
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-H3YM66EI.js";
|
|
25
25
|
import {
|
|
26
26
|
envelopePayloadHash
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2WJKJZSO.js";
|
|
28
28
|
import {
|
|
29
29
|
ManifestConflictError,
|
|
30
30
|
isConflictError
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-VSUQI5Q6.js";
|
|
32
32
|
|
|
33
33
|
// src/with-shape/persisted-schemas/field-ids.ts
|
|
34
34
|
var BASE32URL_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
@@ -257,4 +257,4 @@ export {
|
|
|
257
257
|
persistClassifiedMarker,
|
|
258
258
|
persistSatelliteMarker
|
|
259
259
|
};
|
|
260
|
-
//# sourceMappingURL=chunk-
|
|
260
|
+
//# sourceMappingURL=chunk-G46JSSVX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-shape/persisted-schemas/field-ids.ts","../src/with-shape/manifest/writer.ts","../src/with-shape/manifest/sync.ts","../src/with-shape/persisted-schemas/register.ts"],"sourcesContent":["/**\n * Stable field-identifier minting (#946).\n *\n * A field's `id` is opaque and permanent — minted once, from randomness\n * only, with no timestamp and no name-derivation. A name-derived id would\n * change when the field is renamed, defeating the whole point (identity\n * must survive a rename). See `PersistedSchemaEnvelope.fieldIds`.\n *\n * @module\n */\n\nconst BASE32URL_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567'\n\n/** Unpadded RFC 4648 §7 base32url encoding of an arbitrary byte string. */\nfunction encodeBase32Url(bytes: Uint8Array): string {\n let bits = 0\n let value = 0\n let out = ''\n for (const byte of bytes) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) {\n out += BASE32URL_ALPHABET[(value >>> (bits - 5)) & 31]\n bits -= 5\n }\n }\n if (bits > 0) {\n out += BASE32URL_ALPHABET[(value << (5 - bits)) & 31]\n }\n return out\n}\n\n/**\n * Mint a fresh opaque field id: 12 random bytes (96 bits) from\n * `crypto.getRandomValues`, encoded as a 20-character base32url token.\n * Not exported — Task 2/3 (#946) may need to export this later; keep it\n * module-internal until an external consumer exists.\n */\nfunction mintFieldId(): string {\n const bytes = new Uint8Array(12)\n crypto.getRandomValues(bytes)\n return encodeBase32Url(bytes)\n}\n\n/** Top-level property names of a Draft 2020-12 object JSON Schema, or `[]` when not an object schema. */\nfunction topLevelFieldNames(jsonSchema: object | null): readonly string[] {\n if (jsonSchema === null) return []\n const props = (jsonSchema as { properties?: unknown }).properties\n if (props === null || typeof props !== 'object') return []\n return Object.keys(props as Record<string, unknown>)\n}\n\n/**\n * Resolve the `fieldIds` map for a freshly-derived schema against the\n * previously-stored envelope's map (if any). Existing ids are preserved by\n * name — a field keeps its id across re-derivation as long as its name is\n * unchanged; a genuinely new field name mints a fresh id. Returns\n * `undefined` when the fresh schema has no derivable top-level field set\n * (e.g. a stub envelope, or a non-object root schema).\n *\n * `renamed` (#946 Task 2) — unambiguous rename pairs from\n * `computeSchemaDelta`'s `SchemaDelta.renamed`. When the fresh name is a\n * rename target (`to`), the id is looked up under its OLD name (`from`)\n * instead of the fresh name itself — the whole point of a stable field id\n * is that it survives exactly this case; without it, a rename would mint a\n * fresh id for the new name and silently drop the old one.\n */\nexport function resolveFieldIds(\n freshJsonSchema: object | null,\n priorFieldIds: Record<string, string> | undefined,\n renamed?: readonly { readonly from: string; readonly to: string }[],\n): Record<string, string> | undefined {\n const names = topLevelFieldNames(freshJsonSchema)\n if (names.length === 0) return undefined\n const priorNameFor = new Map<string, string>()\n if (renamed) for (const { from, to } of renamed) priorNameFor.set(to, from)\n const out: Record<string, string> = {}\n for (const name of names) {\n const priorName = priorNameFor.get(name) ?? name\n out[name] = priorFieldIds?.[priorName] ?? mintFieldId()\n }\n return out\n}\n","/**\n * Strict-CAS schema-manifest writer (#941, AC #1).\n *\n * Every other reserved-collection writer in the hub (`persisted-schemas`'s\n * `savePersistedSchema` callers, the classified/satellite marker writers)\n * retries on a CAS conflict: re-read, re-apply, try again. The schema\n * manifest deliberately does NOT — two concurrent *direct* edits to the\n * manifest must be refused and surfaced, never silently merged or\n * retry-resolved, because a manifest write encodes a specific point-in-time\n * claim about the pod's schema generation/content-hash set. A caller that\n * lost the race must re-derive against the fresh state and decide what to\n * do, not have its stale write quietly re-applied on top of someone else's.\n *\n * Ledger audit: this writer does not have a `LedgerStore` handle at this\n * seam (mirroring `persisted-schemas`, whose `_schemas/<collection>` writes\n * are likewise not ledger-audited today). Wiring `LedgerStore.append({ op:\n * 'migration', ... })` around a manifest write belongs at the call site\n * where a ledger handle is already in scope (the open/write integration —\n * #941 Task 3/4), not here.\n *\n * @module\n */\n\nimport { isConflictError, ManifestConflictError } from '../../kernel/errors.js'\nimport { saveSchemaManifest, type GetManifestDEK } from './storage.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { SchemaManifest } from './types.js'\n\n/**\n * Write the schema manifest as a strict CAS: `expectedVersion` must match\n * the stored envelope's `_v` (0 when no manifest exists yet). On a\n * conflict, throws {@link ManifestConflictError} — this function never\n * retries.\n */\nexport async function writeSchemaManifest(\n store: NoydbStore,\n vault: string,\n manifest: SchemaManifest,\n expectedVersion: number,\n getDEK: GetManifestDEK,\n): Promise<void> {\n try {\n await saveSchemaManifest(store, vault, manifest, expectedVersion, getDEK)\n } catch (err) {\n if (isConflictError(err)) {\n throw new ManifestConflictError(err.version, expectedVersion)\n }\n throw err\n }\n}\n","/**\n * Keep the persisted `_manifest/schema` record in sync with the\n * per-collection `_schemas/<collection>` source of truth (#941 Task 3).\n *\n * Called from `persisted-schemas/register.ts`'s `persistSchemaIfNeeded`\n * right after a `_schemas/<collection>` write succeeds.\n *\n * ## Reconciling strict-CAS-refuse with the derived nature of the manifest\n *\n * `writer.ts`'s `writeSchemaManifest` is deliberately strict-CAS: a\n * concurrent *direct* edit to the manifest is refused, never retried (AC\n * #1). But this call site is not a direct edit — it is a re-derivation\n * from the source of truth, triggered as a side effect of a `_schemas`\n * write that has ALREADY succeeded. When N collections are declared\n * together (one `openVault()` call registering several `persistJsonSchema:\n * true` collections), their `persistSchemaIfNeeded` calls run CONCURRENTLY\n * — each one's own `_schemas/<collection>` write lands independently, and\n * each then calls this function. A single derive-then-write with no\n * feedback loop is NOT enough here: a call whose `deriveSchemaManifest`\n * snapshot ran before a sibling's `_schemas` write landed would persist a\n * manifest that's missing that sibling forever (nothing re-triggers it) —\n * this was a REAL bug (not a test-isolation artifact — reproduced with 0\n * shared state, purely from concurrent `_schemas` writes racing the derive\n * snapshot; see #941 flake fix commit). `syncSchemaManifest` therefore\n * loops (bounded by {@link MAX_SYNC_ATTEMPTS}):\n *\n * 1. Derive fresh, compare to what's persisted. Already converged → done.\n * 2. Write via strict-CAS. `ManifestConflictError` (another writer moved\n * the manifest first) → loop: re-derive against the NEW state and\n * retry, exactly like a normal optimistic-CAS retry.\n * 3. On a successful write, derive ONE more time. If a sibling's\n * `_schemas` write landed while we were deriving/writing, the state\n * has already moved past what we just wrote — loop again to catch it\n * up. If the re-derive matches what we wrote, we're the last writer\n * and the manifest is provably converged: return.\n *\n * This guarantees that whichever collection's `_schemas` write is the LAST\n * to land (in real wall-clock order — there is always exactly one, even\n * though no single caller knows which) ends up running a sync whose own\n * derive sees every sibling's write already committed, and whose\n * recheck-after-write confirms nothing moved further — so it converges the\n * manifest deterministically, regardless of interleaving. If attempts are\n * exhausted under pathological contention, the loop gives up — with a\n * `console.warn` (see cap-exhaustion note below), not silently — leaving\n * the last successful write in place; the manifest is a derivable cache\n * (not a correctness invariant), so a next schema write or `open()`'s\n * re-derive still converges it, matching how `persistSchemaIfNeeded`\n * already treats its own write failures.\n * Any error OTHER than `ManifestConflictError` (store failure, DEK\n * resolution failure, etc.) is NOT swallowed — it propagates to the\n * caller immediately, matching how `persistSchemaIfNeeded` already treats\n * its own write failures as a visible (not silently discarded) fingerprint\n * failure.\n *\n * ## CRITICAL fix (#941 review): a scoped principal must never write a\n * partial manifest\n *\n * `deriveSchemaManifest` returns `undecodableCollections` for any\n * `_schemas/<collection>` entry this principal's `lookupDEK` couldn't\n * resolve — e.g. a member `grant()`-scoped to one collection can decrypt\n * `_manifest` itself (system-prefixed DEKs propagate to every role) but\n * NOT a sibling data collection's `_schemas/<sibling>` entry. Before this\n * fix, `deriveSchemaManifest` silently treated \"can't decrypt\" the same as\n * \"no derivable content\" and omitted the sibling — so a scoped member's\n * OWN schema declare would derive a manifest missing every collection they\n * can't see, load the real (complete) persisted manifest as a valid CAS\n * token, observe a hash mismatch, and OVERWRITE it — permanently dropping\n * the siblings, silently, ledger-audited as a legitimate migration. The\n * converging recheck loop above made this WORSE, not better: re-deriving\n * from the same restricted view just re-confirms the same partial result\n * and declares it \"stable.\"\n *\n * Fix: if `undecodableCollections` is non-empty, `syncSchemaManifest`\n * `SKIP`s entirely — no read-compare, no write, no audit, no loop. It does\n * NOT try to merge-preserve the undecodable entries from what's currently\n * persisted: an `aggregateHash` computed over entries this principal can't\n * verify would be a hash asserting something it never actually checked,\n * which is worse than not writing at all. This is safe because:\n *\n * - The source of truth, `_schemas/<their-collection>`, was ALREADY\n * updated by their declare — nothing about their own change is lost.\n * - The pod-wide `_manifest` cache simply isn't rewritten by a principal\n * who'd corrupt it by omission — it stays whatever a full-visibility\n * principal (owner/admin/custodian) last wrote.\n * - `open()` re-derives from `_schemas` directly (not from the cached\n * manifest) for whoever opens it, so a stale cache is never load-bearing.\n * - The NEXT full-visibility principal's declare (or any subsequent sync\n * by someone with complete access) naturally refreshes the cache,\n * including the scoped member's own just-landed change.\n *\n * ## Ledger audit (AC #5)\n *\n * Schema-generation transitions are not ledger-audited anywhere else today\n * (neither the `_schemas/<collection>` write in `register.ts` nor the fence\n * bump in `schema-update/fence-controller.ts#runCutover`). This is the\n * first ledger-audited signal for a schema change: every manifest write\n * that actually happens (i.e. every time the derived manifest differs from\n * what's stored) appends an `op: 'migration'` entry via the optional\n * `getLedgerOrNull` callback — `null`/absent when the history strategy\n * isn't opted in, matching every other optional-ledger call site\n * (`vault._getLedgerOrNull()?.append(...)`, see `liberate.ts`,\n * `with-audit/periods/vault-facade.ts`). No audit fires on the no-op path\n * (derived manifest unchanged), on a swallowed conflict (the winning\n * writer's own sync already audits its write), or on the scoped-principal\n * SKIP path above (nothing was written).\n *\n * @module\n */\n\nimport type { LedgerStore } from '../../with-commit/history/ledger/store.js'\nimport { envelopePayloadHash } from '../../with-commit/history/ledger/hash.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { ManifestConflictError } from '../../kernel/errors.js'\nimport { deriveSchemaManifest, type LookupDEK } from './derive.js'\nimport { loadSchemaManifestEntry, type GetManifestDEK } from './storage.js'\nimport { writeSchemaManifest } from './writer.js'\nimport { MANIFEST_COLLECTION } from './reserved-collections.js'\nimport { MANIFEST_SCHEMA_RECORD_ID, type SchemaManifest } from './types.js'\n\nexport interface ManifestSyncDeps {\n /** Resolver for `_manifest`'s OWN DEK — mints if absent (this principal's declare is about to write it). */\n readonly getDEK: GetManifestDEK\n /**\n * NON-MINTING lookup for every OTHER (sibling data) collection's DEK,\n * used by `deriveSchemaManifest` to build the index — see `derive.ts`'s\n * `LookupDEK` doc for why minting here would be actively harmful.\n */\n readonly lookupDEK: LookupDEK\n /** Optional ledger handle — absent when the history strategy isn't opted in (no-op audit). */\n readonly getLedgerOrNull?: () => LedgerStore | null\n}\n\n/**\n * Bound on the derive→write→recheck loop (see module doc). Headroom above\n * what any realistic `openVault()` batch (a handful of collections) needs —\n * a 40-way concurrent-declare stress repro converged within single digits.\n */\nconst MAX_SYNC_ATTEMPTS = 16\n\n/**\n * Re-derive the schema manifest and write it if it changed, looping until\n * the write is provably stable against concurrent sibling `_schemas`\n * writes (see module doc). SKIPS entirely (no write, no audit) when this\n * principal's view is incomplete (`undecodableCollections` non-empty) —\n * see the module doc's CRITICAL fix section. Swallows a\n * {@link ManifestConflictError} by retrying against the fresher state; any\n * other error propagates. Ledger-audits (`op: 'migration'`) once per\n * successful write.\n */\nexport async function syncSchemaManifest(\n store: NoydbStore,\n vault: string,\n deps: ManifestSyncDeps,\n): Promise<void> {\n for (let attempt = 0; attempt < MAX_SYNC_ATTEMPTS; attempt++) {\n const { manifest: derived, undecodableCollections } = await deriveSchemaManifest(store, vault, deps.lookupDEK)\n if (undecodableCollections.length > 0) {\n // Scoped principal — see module doc's CRITICAL fix. Never write a\n // manifest we know is missing collections we couldn't decrypt.\n return\n }\n\n const existing = await loadSchemaManifestEntry(store, vault, deps.getDEK)\n\n if (\n existing &&\n existing.manifest.generation === derived.generation &&\n existing.manifest.aggregateHash === derived.aggregateHash\n ) {\n return // already converged — no write, no audit\n }\n\n const expectedVersion = existing?.version ?? 0\n try {\n await writeSchemaManifest(store, vault, derived, expectedVersion, deps.getDEK)\n } catch (err) {\n if (err instanceof ManifestConflictError) continue // another writer moved it first — re-derive + retry\n throw err\n }\n\n await auditManifestWrite(store, vault, deps, derived, expectedVersion)\n\n // A sibling's `_schemas` write can land while WE were deriving/writing\n // (#941 flake fix — see module doc). Re-derive once more: if the truth\n // has already moved past what we just wrote, loop again to catch it up\n // instead of leaving a permanently-partial manifest. (A principal with\n // full visibility at the top of this iteration cannot lose that\n // visibility mid-call — the keyring doesn't change during one sync —\n // so this recheck is never itself subject to the undecodable-skip; the\n // `for` loop's own guard covers it defensively regardless.)\n const recheck = await deriveSchemaManifest(store, vault, deps.lookupDEK)\n if (recheck.undecodableCollections.length > 0) return // defensive — see comment above\n if (recheck.manifest.generation === derived.generation && recheck.manifest.aggregateHash === derived.aggregateHash) {\n return // stable — we were the last writer\n }\n }\n // Attempts exhausted under pathological contention — see module doc:\n // the manifest is a derivable cache, not a correctness invariant, so a\n // later schema write or open()'s re-derive still converges it. Warn\n // rather than fail silently — a silent give-up on a completeness\n // invariant is exactly what hid the original concurrent-declare race.\n console.warn(\n `[noy-db] schema-manifest sync did not converge after ${MAX_SYNC_ATTEMPTS} attempts; ` +\n 'the persisted manifest may be stale until the next schema write or open()\\'s re-derive.',\n )\n}\n\nasync function auditManifestWrite(\n store: NoydbStore,\n vault: string,\n deps: ManifestSyncDeps,\n derived: SchemaManifest,\n expectedVersion: number,\n): Promise<void> {\n const ledger = deps.getLedgerOrNull?.()\n if (!ledger) return\n // `_manifest/schema` has a real, individually-addressable envelope (unlike\n // a `lifecycle` entry's empty collection/id) — `verifyBackupIntegrity`'s\n // data cross-check (`with-pod/backup.ts`) treats any non-amendment,\n // non-lifecycle entry's `collection`/`id` as \"the latest write to this\n // record\" and recomputes+compares its ciphertext hash, so `payloadHash`\n // must be the real `envelopePayloadHash` of the just-written envelope\n // (ciphertext domain), NOT `derived.aggregateHash` (plaintext domain) —\n // using the wrong domain here trips a false-positive tamper failure on\n // restore.\n const envelope = await store.get(vault, MANIFEST_COLLECTION, MANIFEST_SCHEMA_RECORD_ID)\n await ledger.append({\n op: 'migration',\n collection: MANIFEST_COLLECTION,\n id: MANIFEST_SCHEMA_RECORD_ID,\n version: expectedVersion + 1,\n actor: '',\n payloadHash: await envelopePayloadHash(envelope),\n reason: `schema-manifest-sync:generation=${derived.generation}`,\n })\n}\n","/**\n * Orchestrate the derive → hash → skip-or-write cycle for a collection's\n * persisted JSON Schema. Called by the Vault at collection-registration\n * time when the developer opts in via `collection({ persistJsonSchema:\n * true })`.\n *\n * Skip semantics:\n *\n * - Zod validators: skip when the new hash equals the stored hash.\n * - Non-Zod (stub envelopes have hash=null): skip when the stored\n * envelope's `kind` matches the freshly-detected kind (since there's\n * no body to compare yet — a kind change is the only signal).\n *\n * @module\n */\n\nimport { derivePersistedSchema } from './derive.js'\nimport { loadPersistedSchemaEntry, savePersistedSchema } from './storage.js'\nimport { resolveFieldIds } from './field-ids.js'\nimport { computeSchemaDelta } from '../schema-update/delta.js'\nimport { evaluateStrategies } from '../schema-update/dispatch.js'\nimport { loadFence, saveFence } from '../schema-update/fence.js'\nimport { isConflictError } from '../../kernel/errors.js'\nimport { syncSchemaManifest, type ManifestSyncDeps } from '../manifest/sync.js'\nimport type { SchemaUpdateStrategy, UpdateDecision, SchemaDelta } from '../schema-update/types.js'\nimport type { NoydbStore, ClassifiedMarker } from '../../kernel/types.js'\nimport type { PersistedSchemaEnvelope } from './types.js'\nimport type { PairingMarker } from '../satellites/types.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\n\n/**\n * Max optimistic-CAS retries for the shared `_schemas/<collection>` record\n * (#583). Only two writers ever contend (the JSON-Schema writer and the\n * classified-marker writer), so one retry suffices; the headroom covers a\n * pathological re-order. Exhausting it rethrows the {@link ConflictError}\n * rather than silently losing a write.\n */\nconst MAX_SCHEMA_CAS_RETRIES = 5\n\nexport interface PersistSchemaResult {\n /** True when a fresh envelope was written to storage. */\n readonly written: boolean\n /** True when an existing envelope matched and the write was skipped. */\n readonly skipped: boolean\n /** The envelope that was either written or matched. */\n readonly envelope: PersistedSchemaEnvelope\n /** The update-strategy decision, present when strategies ran. */\n readonly decision?: UpdateDecision\n}\n\nexport async function persistSchemaIfNeeded(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly validator: unknown\n readonly dek: EnclaveKey\n readonly strategies?: readonly SchemaUpdateStrategy[]\n /** #941 Task 3: re-derive + sync the `_manifest/schema` INDEX after a write. Omitted → no sync (e.g. tests that don't care). */\n readonly manifestSync?: ManifestSyncDeps\n}): Promise<PersistSchemaResult> {\n const fresh = await derivePersistedSchema(opts.validator)\n\n // The `_schemas/<collection>` record is shared with the classified-marker\n // writer, so a plain load→save loses whichever writer put first (#583). Read\n // the version at load time and CAS on it; on conflict re-read (picking up the\n // other writer's field), re-evaluate, and retry.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n\n if (stored && isEquivalent(stored, fresh)) {\n return { written: false, skipped: true, envelope: stored, decision: { action: 'allow' } }\n }\n\n // Changed (or first registration). Compute the delta whenever we have a\n // comparable JSON-Schema baseline — independent of whether strategies\n // were registered (#946: the rename pairing it carries feeds the\n // fieldIds id-carry below even on a bare re-declare with no\n // `schemaUpdate` strategies configured). Strategies only run when\n // registered.\n let decision: UpdateDecision = { action: 'allow' }\n const strategies = opts.strategies ?? []\n let delta: SchemaDelta | undefined\n if (\n stored &&\n stored.kind === fresh.kind &&\n isPlainObject(stored.jsonSchema) &&\n isPlainObject(fresh.jsonSchema)\n ) {\n delta = computeSchemaDelta(stored.jsonSchema, fresh.jsonSchema, opts.collectionName)\n if (strategies.length > 0) {\n decision = await evaluateStrategies(delta, strategies, { collection: opts.collectionName })\n }\n }\n\n if (decision.action !== 'allow') {\n // reject (or cutover): do NOT overwrite the baseline — the\n // old schema stays the source of truth until the change is resolved.\n return { written: false, skipped: false, envelope: stored ?? fresh, decision }\n }\n\n // Preserve a previously-persisted classified marker (C-A / R10) and\n // satellite pairing marker (R-S9) — the schema derivation knows nothing\n // about them, so a naive overwrite here would drop the config-drift\n // guards' cross-session signals.\n let toSave: PersistedSchemaEnvelope = fresh\n if (stored?.classified !== undefined) toSave = { ...toSave, classified: stored.classified }\n if (stored?.satellite !== undefined) toSave = { ...toSave, satellite: stored.satellite }\n\n // #946: mint/preserve stable per-field ids (by name, carrying a\n // detected rename's id from its old name — see `delta.renamed`) and\n // stamp the vault-wide schema-fence generation this write happened at —\n // binds \"generation N\" to this envelope's content hash\n // (schemaFenceState() + loadPersistedSchema agree).\n const fieldIds = resolveFieldIds(fresh.jsonSchema, stored?.fieldIds, delta?.renamed)\n if (fieldIds !== undefined) toSave = { ...toSave, fieldIds }\n const fence = await loadFence(opts.store, opts.vault)\n toSave = { ...toSave, generation: fence.currentSchemaVersion }\n\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, toSave, version)\n if (toSave.hash !== null) {\n // This is the FIRST non-barrier writer to `_meta/schema-fence` —\n // previously only SchemaFenceController.#setState wrote it, and only\n // sequentially, under the drain barrier. There is no CAS on the fence\n // doc (out of scope to add here), so re-read it immediately before\n // writing rather than reusing the `fence` snapshot captured above\n // (before the potentially-slow `savePersistedSchema` call): spreading\n // a stale snapshot here would roll back `currentSchemaVersion`/\n // `fenceState` if a concurrent cutover (on a different collection)\n // advanced the fence in between — corrupting the vault-wide gate the\n // whole barrier + MigrationRequiredError check depends on. Re-reading\n // narrows (does not eliminate) that window to just this one overlay.\n const freshFence = await loadFence(opts.store, opts.vault)\n await saveFence(opts.store, opts.vault, { ...freshFence, schemaHash: toSave.hash })\n }\n // #941 Task 3: keep the `_manifest/schema` INDEX in sync with the\n // `_schemas/<collection>` write that just landed. Opt-in via\n // `opts.manifestSync` (threaded from vault.ts, which is the only\n // caller with a general-purpose `getDEK` resolver + ledger handle).\n if (opts.manifestSync) {\n await syncSchemaManifest(opts.store, opts.vault, opts.manifestSync)\n }\n return { written: true, skipped: false, envelope: toSave, decision }\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n/**\n * Persist (or refresh) the C-A / R10 classified marker into the collection's\n * `_schemas/<collection>` record, preserving any existing derived JSON-Schema\n * body. Idempotent — a no-op when an equivalent marker is already stored.\n * Independent of `persistJsonSchema`: called on the first classified write so a\n * later naive handle can detect the drift cross-session.\n */\nexport async function persistClassifiedMarker(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly dek: EnclaveKey\n readonly marker: ClassifiedMarker\n}): Promise<void> {\n // Shares the `_schemas/<collection>` record with the JSON-Schema writer;\n // CAS on the loaded version so a concurrent schema (re)registration can't\n // silently drop the marker (and vice-versa) — see #583.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n if (stored?.classified !== undefined && markersEqual(stored.classified, opts.marker)) return\n const payload: PersistedSchemaEnvelope =\n stored !== undefined\n ? { ...stored, classified: opts.marker }\n : {\n _noydb_schema: 1,\n kind: 'Unknown',\n jsonSchema: null,\n hash: null,\n derivedAt: new Date().toISOString(),\n classified: opts.marker,\n }\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, payload, version)\n return\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n/**\n * Persist (or refresh) the R-S9 satellite pairing marker into the collection's\n * `_schemas/<collection>` record, preserving any existing derived JSON-Schema\n * body. Idempotent — a no-op when an equivalent marker is already stored.\n * Independent of `persistJsonSchema`: called on satellite declaration so a\n * later divergent re-declaration can be detected cross-session.\n */\nexport async function persistSatelliteMarker(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly dek: EnclaveKey\n readonly marker: PairingMarker\n}): Promise<void> {\n // Shares the `_schemas/<collection>` record with the JSON-Schema writer;\n // CAS on the loaded version so a concurrent schema (re)registration can't\n // silently drop the marker (and vice-versa) — see #583.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n if (stored?.satellite !== undefined && satelliteMarkersEqual(stored.satellite, opts.marker)) return\n const payload: PersistedSchemaEnvelope =\n stored !== undefined\n ? { ...stored, satellite: opts.marker }\n : {\n _noydb_schema: 1,\n kind: 'Unknown',\n jsonSchema: null,\n hash: null,\n derivedAt: new Date().toISOString(),\n satellite: opts.marker,\n }\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, payload, version)\n return\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n// #597: both equality checks below deliberately do NOT compare `epoch`. A\n// live collection re-opening itself computes a fresh epoch candidate on\n// every declare (marker.ts / config-drift.ts) that must still be treated as\n// \"the same marker\" so the no-op fast path fires and the already-persisted\n// epoch is left untouched — that's what makes epoch stable across re-opens.\n// Comparing epoch here would break that fast path today, with no\n// corresponding benefit: there is no delete-collection API yet, so a\n// mismatched epoch can't (yet) mean \"this name was reused for a genuinely\n// new collection.\" Wiring an epoch-mismatch REJECTION is a deferred\n// follow-up for whenever collection deletion ships.\n\nfunction satelliteMarkersEqual(a: PairingMarker, b: PairingMarker): boolean {\n return a.base === b.base && a.fieldsHash === b.fieldsHash && (a.joined ?? null) === (b.joined ?? null)\n}\n\nfunction markersEqual(a: ClassifiedMarker, b: ClassifiedMarker): boolean {\n return sameSet(a.digestOnly, b.digestOnly) && sameSet(a.equatable, b.equatable)\n}\n\nfunction sameSet(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n const s = new Set(a)\n return b.every((x) => s.has(x))\n}\n\nfunction isPlainObject(v: unknown): v is object {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\n\nfunction isEquivalent(a: PersistedSchemaEnvelope, b: PersistedSchemaEnvelope): boolean {\n if (a.kind !== b.kind) return false\n // Zod path: real hashes — compare directly.\n if (a.hash && b.hash) return a.hash === b.hash\n // Stub path: both have hash=null. Kind equality is the only signal we have.\n if (a.hash === null && b.hash === null) return true\n // Mixed (one has a hash, the other doesn't) — treat as changed.\n return false\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/with-shape/persisted-schemas/field-ids.ts","../src/with-shape/manifest/writer.ts","../src/with-shape/manifest/sync.ts","../src/with-shape/persisted-schemas/register.ts"],"sourcesContent":["/**\n * Stable field-identifier minting (#946).\n *\n * A field's `id` is opaque and permanent — minted once, from randomness\n * only, with no timestamp and no name-derivation. A name-derived id would\n * change when the field is renamed, defeating the whole point (identity\n * must survive a rename). See `PersistedSchemaEnvelope.fieldIds`.\n *\n * @module\n */\n\nconst BASE32URL_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567'\n\n/** Unpadded RFC 4648 §7 base32url encoding of an arbitrary byte string. */\nfunction encodeBase32Url(bytes: Uint8Array): string {\n let bits = 0\n let value = 0\n let out = ''\n for (const byte of bytes) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) {\n out += BASE32URL_ALPHABET[(value >>> (bits - 5)) & 31]\n bits -= 5\n }\n }\n if (bits > 0) {\n out += BASE32URL_ALPHABET[(value << (5 - bits)) & 31]\n }\n return out\n}\n\n/**\n * Mint a fresh opaque field id: 12 random bytes (96 bits) from\n * `crypto.getRandomValues`, encoded as a 20-character base32url token.\n * Not exported — Task 2/3 (#946) may need to export this later; keep it\n * module-internal until an external consumer exists.\n */\nfunction mintFieldId(): string {\n const bytes = new Uint8Array(12)\n crypto.getRandomValues(bytes)\n return encodeBase32Url(bytes)\n}\n\n/** Top-level property names of a Draft 2020-12 object JSON Schema, or `[]` when not an object schema. */\nfunction topLevelFieldNames(jsonSchema: object | null): readonly string[] {\n if (jsonSchema === null) return []\n const props = (jsonSchema as { properties?: unknown }).properties\n if (props === null || typeof props !== 'object') return []\n return Object.keys(props as Record<string, unknown>)\n}\n\n/**\n * Resolve the `fieldIds` map for a freshly-derived schema against the\n * previously-stored envelope's map (if any). Existing ids are preserved by\n * name — a field keeps its id across re-derivation as long as its name is\n * unchanged; a genuinely new field name mints a fresh id. Returns\n * `undefined` when the fresh schema has no derivable top-level field set\n * (e.g. a stub envelope, or a non-object root schema).\n *\n * `renamed` (#946 Task 2) — unambiguous rename pairs from\n * `computeSchemaDelta`'s `SchemaDelta.renamed`. When the fresh name is a\n * rename target (`to`), the id is looked up under its OLD name (`from`)\n * instead of the fresh name itself — the whole point of a stable field id\n * is that it survives exactly this case; without it, a rename would mint a\n * fresh id for the new name and silently drop the old one.\n */\nexport function resolveFieldIds(\n freshJsonSchema: object | null,\n priorFieldIds: Record<string, string> | undefined,\n renamed?: readonly { readonly from: string; readonly to: string }[],\n): Record<string, string> | undefined {\n const names = topLevelFieldNames(freshJsonSchema)\n if (names.length === 0) return undefined\n const priorNameFor = new Map<string, string>()\n if (renamed) for (const { from, to } of renamed) priorNameFor.set(to, from)\n const out: Record<string, string> = {}\n for (const name of names) {\n const priorName = priorNameFor.get(name) ?? name\n out[name] = priorFieldIds?.[priorName] ?? mintFieldId()\n }\n return out\n}\n","/**\n * Strict-CAS schema-manifest writer (#941, AC #1).\n *\n * Every other reserved-collection writer in the hub (`persisted-schemas`'s\n * `savePersistedSchema` callers, the classified/satellite marker writers)\n * retries on a CAS conflict: re-read, re-apply, try again. The schema\n * manifest deliberately does NOT — two concurrent *direct* edits to the\n * manifest must be refused and surfaced, never silently merged or\n * retry-resolved, because a manifest write encodes a specific point-in-time\n * claim about the pod's schema generation/content-hash set. A caller that\n * lost the race must re-derive against the fresh state and decide what to\n * do, not have its stale write quietly re-applied on top of someone else's.\n *\n * Ledger audit: this writer does not have a `LedgerStore` handle at this\n * seam (mirroring `persisted-schemas`, whose `_schemas/<collection>` writes\n * are likewise not ledger-audited today). Wiring `LedgerStore.append({ op:\n * 'migration', ... })` around a manifest write belongs at the call site\n * where a ledger handle is already in scope (the open/write integration —\n * #941 Task 3/4), not here.\n *\n * @module\n */\n\nimport { isConflictError, ManifestConflictError } from '../../kernel/errors.js'\nimport { saveSchemaManifest, type GetManifestDEK } from './storage.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { SchemaManifest } from './types.js'\n\n/**\n * Write the schema manifest as a strict CAS: `expectedVersion` must match\n * the stored envelope's `_v` (0 when no manifest exists yet). On a\n * conflict, throws {@link ManifestConflictError} — this function never\n * retries.\n */\nexport async function writeSchemaManifest(\n store: NoydbStore,\n vault: string,\n manifest: SchemaManifest,\n expectedVersion: number,\n getDEK: GetManifestDEK,\n): Promise<void> {\n try {\n await saveSchemaManifest(store, vault, manifest, expectedVersion, getDEK)\n } catch (err) {\n if (isConflictError(err)) {\n throw new ManifestConflictError(err.version, expectedVersion)\n }\n throw err\n }\n}\n","/**\n * Keep the persisted `_manifest/schema` record in sync with the\n * per-collection `_schemas/<collection>` source of truth (#941 Task 3).\n *\n * Called from `persisted-schemas/register.ts`'s `persistSchemaIfNeeded`\n * right after a `_schemas/<collection>` write succeeds.\n *\n * ## Reconciling strict-CAS-refuse with the derived nature of the manifest\n *\n * `writer.ts`'s `writeSchemaManifest` is deliberately strict-CAS: a\n * concurrent *direct* edit to the manifest is refused, never retried (AC\n * #1). But this call site is not a direct edit — it is a re-derivation\n * from the source of truth, triggered as a side effect of a `_schemas`\n * write that has ALREADY succeeded. When N collections are declared\n * together (one `openVault()` call registering several `persistJsonSchema:\n * true` collections), their `persistSchemaIfNeeded` calls run CONCURRENTLY\n * — each one's own `_schemas/<collection>` write lands independently, and\n * each then calls this function. A single derive-then-write with no\n * feedback loop is NOT enough here: a call whose `deriveSchemaManifest`\n * snapshot ran before a sibling's `_schemas` write landed would persist a\n * manifest that's missing that sibling forever (nothing re-triggers it) —\n * this was a REAL bug (not a test-isolation artifact — reproduced with 0\n * shared state, purely from concurrent `_schemas` writes racing the derive\n * snapshot; see #941 flake fix commit). `syncSchemaManifest` therefore\n * loops (bounded by {@link MAX_SYNC_ATTEMPTS}):\n *\n * 1. Derive fresh, compare to what's persisted. Already converged → done.\n * 2. Write via strict-CAS. `ManifestConflictError` (another writer moved\n * the manifest first) → loop: re-derive against the NEW state and\n * retry, exactly like a normal optimistic-CAS retry.\n * 3. On a successful write, derive ONE more time. If a sibling's\n * `_schemas` write landed while we were deriving/writing, the state\n * has already moved past what we just wrote — loop again to catch it\n * up. If the re-derive matches what we wrote, we're the last writer\n * and the manifest is provably converged: return.\n *\n * This guarantees that whichever collection's `_schemas` write is the LAST\n * to land (in real wall-clock order — there is always exactly one, even\n * though no single caller knows which) ends up running a sync whose own\n * derive sees every sibling's write already committed, and whose\n * recheck-after-write confirms nothing moved further — so it converges the\n * manifest deterministically, regardless of interleaving. If attempts are\n * exhausted under pathological contention, the loop gives up — with a\n * `console.warn` (see cap-exhaustion note below), not silently — leaving\n * the last successful write in place; the manifest is a derivable cache\n * (not a correctness invariant), so a next schema write or `open()`'s\n * re-derive still converges it, matching how `persistSchemaIfNeeded`\n * already treats its own write failures.\n * Any error OTHER than `ManifestConflictError` (store failure, DEK\n * resolution failure, etc.) is NOT swallowed — it propagates to the\n * caller immediately, matching how `persistSchemaIfNeeded` already treats\n * its own write failures as a visible (not silently discarded) fingerprint\n * failure.\n *\n * ## CRITICAL fix (#941 review): a scoped principal must never write a\n * partial manifest\n *\n * `deriveSchemaManifest` returns `undecodableCollections` for any\n * `_schemas/<collection>` entry this principal's `lookupDEK` couldn't\n * resolve — e.g. a member `grant()`-scoped to one collection can decrypt\n * `_manifest` itself (system-prefixed DEKs propagate to every role) but\n * NOT a sibling data collection's `_schemas/<sibling>` entry. Before this\n * fix, `deriveSchemaManifest` silently treated \"can't decrypt\" the same as\n * \"no derivable content\" and omitted the sibling — so a scoped member's\n * OWN schema declare would derive a manifest missing every collection they\n * can't see, load the real (complete) persisted manifest as a valid CAS\n * token, observe a hash mismatch, and OVERWRITE it — permanently dropping\n * the siblings, silently, ledger-audited as a legitimate migration. The\n * converging recheck loop above made this WORSE, not better: re-deriving\n * from the same restricted view just re-confirms the same partial result\n * and declares it \"stable.\"\n *\n * Fix: if `undecodableCollections` is non-empty, `syncSchemaManifest`\n * `SKIP`s entirely — no read-compare, no write, no audit, no loop. It does\n * NOT try to merge-preserve the undecodable entries from what's currently\n * persisted: an `aggregateHash` computed over entries this principal can't\n * verify would be a hash asserting something it never actually checked,\n * which is worse than not writing at all. This is safe because:\n *\n * - The source of truth, `_schemas/<their-collection>`, was ALREADY\n * updated by their declare — nothing about their own change is lost.\n * - The pod-wide `_manifest` cache simply isn't rewritten by a principal\n * who'd corrupt it by omission — it stays whatever a full-visibility\n * principal (owner/admin/custodian) last wrote.\n * - `open()` re-derives from `_schemas` directly (not from the cached\n * manifest) for whoever opens it, so a stale cache is never load-bearing.\n * - The NEXT full-visibility principal's declare (or any subsequent sync\n * by someone with complete access) naturally refreshes the cache,\n * including the scoped member's own just-landed change.\n *\n * ## Ledger audit (AC #5)\n *\n * Schema-generation transitions are not ledger-audited anywhere else today\n * (neither the `_schemas/<collection>` write in `register.ts` nor the fence\n * bump in `schema-update/fence-controller.ts#runCutover`). This is the\n * first ledger-audited signal for a schema change: every manifest write\n * that actually happens (i.e. every time the derived manifest differs from\n * what's stored) appends an `op: 'migration'` entry via the optional\n * `getLedgerOrNull` callback — `null`/absent when the history strategy\n * isn't opted in, matching every other optional-ledger call site\n * (`vault._getLedgerOrNull()?.append(...)`, see `liberate.ts`,\n * `with-audit/periods/vault-facade.ts`). No audit fires on the no-op path\n * (derived manifest unchanged), on a swallowed conflict (the winning\n * writer's own sync already audits its write), or on the scoped-principal\n * SKIP path above (nothing was written).\n *\n * @module\n */\n\nimport type { LedgerStore } from '../../with-commit/history/ledger/store.js'\nimport { envelopePayloadHash } from '../../with-commit/history/ledger/hash.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { ManifestConflictError } from '../../kernel/errors.js'\nimport { deriveSchemaManifest, type LookupDEK } from './derive.js'\nimport { loadSchemaManifestEntry, type GetManifestDEK } from './storage.js'\nimport { writeSchemaManifest } from './writer.js'\nimport { MANIFEST_COLLECTION } from './reserved-collections.js'\nimport { MANIFEST_SCHEMA_RECORD_ID, type SchemaManifest } from './types.js'\n\nexport interface ManifestSyncDeps {\n /** Resolver for `_manifest`'s OWN DEK — mints if absent (this principal's declare is about to write it). */\n readonly getDEK: GetManifestDEK\n /**\n * NON-MINTING lookup for every OTHER (sibling data) collection's DEK,\n * used by `deriveSchemaManifest` to build the index — see `derive.ts`'s\n * `LookupDEK` doc for why minting here would be actively harmful.\n */\n readonly lookupDEK: LookupDEK\n /** Optional ledger handle — absent when the history strategy isn't opted in (no-op audit). */\n readonly getLedgerOrNull?: () => LedgerStore | null\n}\n\n/**\n * Bound on the derive→write→recheck loop (see module doc). Headroom above\n * what any realistic `openVault()` batch (a handful of collections) needs —\n * a 40-way concurrent-declare stress repro converged within single digits.\n */\nconst MAX_SYNC_ATTEMPTS = 16\n\n/**\n * Re-derive the schema manifest and write it if it changed, looping until\n * the write is provably stable against concurrent sibling `_schemas`\n * writes (see module doc). SKIPS entirely (no write, no audit) when this\n * principal's view is incomplete (`undecodableCollections` non-empty) —\n * see the module doc's CRITICAL fix section. Swallows a\n * {@link ManifestConflictError} by retrying against the fresher state; any\n * other error propagates. Ledger-audits (`op: 'migration'`) once per\n * successful write.\n */\nexport async function syncSchemaManifest(\n store: NoydbStore,\n vault: string,\n deps: ManifestSyncDeps,\n): Promise<void> {\n for (let attempt = 0; attempt < MAX_SYNC_ATTEMPTS; attempt++) {\n const { manifest: derived, undecodableCollections } = await deriveSchemaManifest(store, vault, deps.lookupDEK)\n if (undecodableCollections.length > 0) {\n // Scoped principal — see module doc's CRITICAL fix. Never write a\n // manifest we know is missing collections we couldn't decrypt.\n return\n }\n\n const existing = await loadSchemaManifestEntry(store, vault, deps.getDEK)\n\n if (\n existing &&\n existing.manifest.generation === derived.generation &&\n existing.manifest.aggregateHash === derived.aggregateHash\n ) {\n return // already converged — no write, no audit\n }\n\n const expectedVersion = existing?.version ?? 0\n try {\n await writeSchemaManifest(store, vault, derived, expectedVersion, deps.getDEK)\n } catch (err) {\n if (err instanceof ManifestConflictError) continue // another writer moved it first — re-derive + retry\n throw err\n }\n\n await auditManifestWrite(store, vault, deps, derived, expectedVersion)\n\n // A sibling's `_schemas` write can land while WE were deriving/writing\n // (#941 flake fix — see module doc). Re-derive once more: if the truth\n // has already moved past what we just wrote, loop again to catch it up\n // instead of leaving a permanently-partial manifest. (A principal with\n // full visibility at the top of this iteration cannot lose that\n // visibility mid-call — the keyring doesn't change during one sync —\n // so this recheck is never itself subject to the undecodable-skip; the\n // `for` loop's own guard covers it defensively regardless.)\n const recheck = await deriveSchemaManifest(store, vault, deps.lookupDEK)\n if (recheck.undecodableCollections.length > 0) return // defensive — see comment above\n if (recheck.manifest.generation === derived.generation && recheck.manifest.aggregateHash === derived.aggregateHash) {\n return // stable — we were the last writer\n }\n }\n // Attempts exhausted under pathological contention — see module doc:\n // the manifest is a derivable cache, not a correctness invariant, so a\n // later schema write or open()'s re-derive still converges it. Warn\n // rather than fail silently — a silent give-up on a completeness\n // invariant is exactly what hid the original concurrent-declare race.\n console.warn(\n `[noy-db] schema-manifest sync did not converge after ${MAX_SYNC_ATTEMPTS} attempts; ` +\n 'the persisted manifest may be stale until the next schema write or open()\\'s re-derive.',\n )\n}\n\nasync function auditManifestWrite(\n store: NoydbStore,\n vault: string,\n deps: ManifestSyncDeps,\n derived: SchemaManifest,\n expectedVersion: number,\n): Promise<void> {\n const ledger = deps.getLedgerOrNull?.()\n if (!ledger) return\n // `_manifest/schema` has a real, individually-addressable envelope (unlike\n // a `lifecycle` entry's empty collection/id) — `verifyBackupIntegrity`'s\n // data cross-check (`with-pod/backup.ts`) treats any non-amendment,\n // non-lifecycle entry's `collection`/`id` as \"the latest write to this\n // record\" and recomputes+compares its ciphertext hash, so `payloadHash`\n // must be the real `envelopePayloadHash` of the just-written envelope\n // (ciphertext domain), NOT `derived.aggregateHash` (plaintext domain) —\n // using the wrong domain here trips a false-positive tamper failure on\n // restore.\n const envelope = await store.get(vault, MANIFEST_COLLECTION, MANIFEST_SCHEMA_RECORD_ID)\n await ledger.append({\n op: 'migration',\n collection: MANIFEST_COLLECTION,\n id: MANIFEST_SCHEMA_RECORD_ID,\n version: expectedVersion + 1,\n actor: '',\n payloadHash: await envelopePayloadHash(envelope),\n reason: `schema-manifest-sync:generation=${derived.generation}`,\n })\n}\n","/**\n * Orchestrate the derive → hash → skip-or-write cycle for a collection's\n * persisted JSON Schema. Called by the Vault at collection-registration\n * time when the developer opts in via `collection({ persistJsonSchema:\n * true })`.\n *\n * Skip semantics:\n *\n * - Zod validators: skip when the new hash equals the stored hash.\n * - Non-Zod (stub envelopes have hash=null): skip when the stored\n * envelope's `kind` matches the freshly-detected kind (since there's\n * no body to compare yet — a kind change is the only signal).\n *\n * @module\n */\n\nimport { derivePersistedSchema } from './derive.js'\nimport { loadPersistedSchemaEntry, savePersistedSchema } from './storage.js'\nimport { resolveFieldIds } from './field-ids.js'\nimport { computeSchemaDelta } from '../schema-update/delta.js'\nimport { evaluateStrategies } from '../schema-update/dispatch.js'\nimport { loadFence, saveFence } from '../schema-update/fence.js'\nimport { isConflictError } from '../../kernel/errors.js'\nimport { syncSchemaManifest, type ManifestSyncDeps } from '../manifest/sync.js'\nimport type { SchemaUpdateStrategy, UpdateDecision, SchemaDelta } from '../schema-update/types.js'\nimport type { NoydbStore, ClassifiedMarker } from '../../kernel/types.js'\nimport type { PersistedSchemaEnvelope } from './types.js'\nimport type { PairingMarker } from '../satellites/types.js'\nimport type { EnclaveKey } from '../../capsule/index.js'\n\n/**\n * Max optimistic-CAS retries for the shared `_schemas/<collection>` record\n * (#583). Only two writers ever contend (the JSON-Schema writer and the\n * classified-marker writer), so one retry suffices; the headroom covers a\n * pathological re-order. Exhausting it rethrows the {@link ConflictError}\n * rather than silently losing a write.\n */\nconst MAX_SCHEMA_CAS_RETRIES = 5\n\nexport interface PersistSchemaResult {\n /** True when a fresh envelope was written to storage. */\n readonly written: boolean\n /** True when an existing envelope matched and the write was skipped. */\n readonly skipped: boolean\n /** The envelope that was either written or matched. */\n readonly envelope: PersistedSchemaEnvelope\n /** The update-strategy decision, present when strategies ran. */\n readonly decision?: UpdateDecision\n}\n\nexport async function persistSchemaIfNeeded(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly validator: unknown\n readonly dek: EnclaveKey\n readonly strategies?: readonly SchemaUpdateStrategy[]\n /** #941 Task 3: re-derive + sync the `_manifest/schema` INDEX after a write. Omitted → no sync (e.g. tests that don't care). */\n readonly manifestSync?: ManifestSyncDeps\n}): Promise<PersistSchemaResult> {\n const fresh = await derivePersistedSchema(opts.validator)\n\n // The `_schemas/<collection>` record is shared with the classified-marker\n // writer, so a plain load→save loses whichever writer put first (#583). Read\n // the version at load time and CAS on it; on conflict re-read (picking up the\n // other writer's field), re-evaluate, and retry.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n\n if (stored && isEquivalent(stored, fresh)) {\n return { written: false, skipped: true, envelope: stored, decision: { action: 'allow' } }\n }\n\n // Changed (or first registration). Compute the delta whenever we have a\n // comparable JSON-Schema baseline — independent of whether strategies\n // were registered (#946: the rename pairing it carries feeds the\n // fieldIds id-carry below even on a bare re-declare with no\n // `schemaUpdate` strategies configured). Strategies only run when\n // registered.\n let decision: UpdateDecision = { action: 'allow' }\n const strategies = opts.strategies ?? []\n let delta: SchemaDelta | undefined\n if (\n stored &&\n stored.kind === fresh.kind &&\n isPlainObject(stored.jsonSchema) &&\n isPlainObject(fresh.jsonSchema)\n ) {\n delta = computeSchemaDelta(stored.jsonSchema, fresh.jsonSchema, opts.collectionName)\n if (strategies.length > 0) {\n decision = await evaluateStrategies(delta, strategies, { collection: opts.collectionName })\n }\n }\n\n if (decision.action !== 'allow') {\n // reject (or cutover): do NOT overwrite the baseline — the\n // old schema stays the source of truth until the change is resolved.\n return { written: false, skipped: false, envelope: stored ?? fresh, decision }\n }\n\n // Preserve a previously-persisted classified marker (C-A / R10) and\n // satellite pairing marker (R-S9) — the schema derivation knows nothing\n // about them, so a naive overwrite here would drop the config-drift\n // guards' cross-session signals.\n let toSave: PersistedSchemaEnvelope = fresh\n if (stored?.classified !== undefined) toSave = { ...toSave, classified: stored.classified }\n if (stored?.satellite !== undefined) toSave = { ...toSave, satellite: stored.satellite }\n\n // #946: mint/preserve stable per-field ids (by name, carrying a\n // detected rename's id from its old name — see `delta.renamed`) and\n // stamp the vault-wide schema-fence generation this write happened at —\n // binds \"generation N\" to this envelope's content hash\n // (schemaFenceState() + loadPersistedSchema agree).\n const fieldIds = resolveFieldIds(fresh.jsonSchema, stored?.fieldIds, delta?.renamed)\n if (fieldIds !== undefined) toSave = { ...toSave, fieldIds }\n const fence = await loadFence(opts.store, opts.vault)\n toSave = { ...toSave, generation: fence.currentSchemaVersion }\n\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, toSave, version)\n if (toSave.hash !== null) {\n // This is the FIRST non-barrier writer to `_meta/schema-fence` —\n // previously only SchemaFenceController.#setState wrote it, and only\n // sequentially, under the drain barrier. There is no CAS on the fence\n // doc (out of scope to add here), so re-read it immediately before\n // writing rather than reusing the `fence` snapshot captured above\n // (before the potentially-slow `savePersistedSchema` call): spreading\n // a stale snapshot here would roll back `currentSchemaVersion`/\n // `fenceState` if a concurrent cutover (on a different collection)\n // advanced the fence in between — corrupting the vault-wide gate the\n // whole barrier + MigrationRequiredError check depends on. Re-reading\n // narrows (does not eliminate) that window to just this one overlay.\n const freshFence = await loadFence(opts.store, opts.vault)\n await saveFence(opts.store, opts.vault, { ...freshFence, schemaHash: toSave.hash })\n }\n // #941 Task 3: keep the `_manifest/schema` INDEX in sync with the\n // `_schemas/<collection>` write that just landed. Opt-in via\n // `opts.manifestSync` (threaded from vault.ts, which is the only\n // caller with a general-purpose `getDEK` resolver + ledger handle).\n if (opts.manifestSync) {\n await syncSchemaManifest(opts.store, opts.vault, opts.manifestSync)\n }\n return { written: true, skipped: false, envelope: toSave, decision }\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n/**\n * Persist (or refresh) the C-A / R10 classified marker into the collection's\n * `_schemas/<collection>` record, preserving any existing derived JSON-Schema\n * body. Idempotent — a no-op when an equivalent marker is already stored.\n * Independent of `persistJsonSchema`: called on the first classified write so a\n * later naive handle can detect the drift cross-session.\n */\nexport async function persistClassifiedMarker(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly dek: EnclaveKey\n readonly marker: ClassifiedMarker\n}): Promise<void> {\n // Shares the `_schemas/<collection>` record with the JSON-Schema writer;\n // CAS on the loaded version so a concurrent schema (re)registration can't\n // silently drop the marker (and vice-versa) — see #583.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n if (stored?.classified !== undefined && markersEqual(stored.classified, opts.marker)) return\n const payload: PersistedSchemaEnvelope =\n stored !== undefined\n ? { ...stored, classified: opts.marker }\n : {\n _noydb_schema: 1,\n kind: 'Unknown',\n jsonSchema: null,\n hash: null,\n derivedAt: new Date().toISOString(),\n classified: opts.marker,\n }\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, payload, version)\n return\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n/**\n * Persist (or refresh) the R-S9 satellite pairing marker into the collection's\n * `_schemas/<collection>` record, preserving any existing derived JSON-Schema\n * body. Idempotent — a no-op when an equivalent marker is already stored.\n * Independent of `persistJsonSchema`: called on satellite declaration so a\n * later divergent re-declaration can be detected cross-session.\n */\nexport async function persistSatelliteMarker(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly dek: EnclaveKey\n readonly marker: PairingMarker\n}): Promise<void> {\n // Shares the `_schemas/<collection>` record with the JSON-Schema writer;\n // CAS on the loaded version so a concurrent schema (re)registration can't\n // silently drop the marker (and vice-versa) — see #583.\n for (let attempt = 0; ; attempt++) {\n const { version, payload: stored } = await loadPersistedSchemaEntry(\n opts.store, opts.vault, opts.collectionName, opts.dek,\n )\n if (stored?.satellite !== undefined && satelliteMarkersEqual(stored.satellite, opts.marker)) return\n const payload: PersistedSchemaEnvelope =\n stored !== undefined\n ? { ...stored, satellite: opts.marker }\n : {\n _noydb_schema: 1,\n kind: 'Unknown',\n jsonSchema: null,\n hash: null,\n derivedAt: new Date().toISOString(),\n satellite: opts.marker,\n }\n try {\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, payload, version)\n return\n } catch (err) {\n if (isConflictError(err) && attempt < MAX_SCHEMA_CAS_RETRIES) continue\n throw err\n }\n }\n}\n\n// #597: both equality checks below deliberately do NOT compare `epoch`. A\n// live collection re-opening itself computes a fresh epoch candidate on\n// every declare (marker.ts / config-drift.ts) that must still be treated as\n// \"the same marker\" so the no-op fast path fires and the already-persisted\n// epoch is left untouched — that's what makes epoch stable across re-opens.\n// Comparing epoch here would break that fast path today, with no\n// corresponding benefit: there is no delete-collection API yet, so a\n// mismatched epoch can't (yet) mean \"this name was reused for a genuinely\n// new collection.\" Wiring an epoch-mismatch REJECTION is a deferred\n// follow-up for whenever collection deletion ships.\n\nfunction satelliteMarkersEqual(a: PairingMarker, b: PairingMarker): boolean {\n return a.base === b.base && a.fieldsHash === b.fieldsHash && (a.joined ?? null) === (b.joined ?? null)\n}\n\nfunction markersEqual(a: ClassifiedMarker, b: ClassifiedMarker): boolean {\n return sameSet(a.digestOnly, b.digestOnly) && sameSet(a.equatable, b.equatable)\n}\n\nfunction sameSet(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n const s = new Set(a)\n return b.every((x) => s.has(x))\n}\n\nfunction isPlainObject(v: unknown): v is object {\n return typeof v === 'object' && v !== null && !Array.isArray(v)\n}\n\nfunction isEquivalent(a: PersistedSchemaEnvelope, b: PersistedSchemaEnvelope): boolean {\n if (a.kind !== b.kind) return false\n // Zod path: real hashes — compare directly.\n if (a.hash && b.hash) return a.hash === b.hash\n // Stub path: both have hash=null. Kind equality is the only signal we have.\n if (a.hash === null && b.hash === null) return true\n // Mixed (one has a hash, the other doesn't) — treat as changed.\n return false\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,24 +2,20 @@ import {
|
|
|
2
2
|
BLOB_CHUNKS_COLLECTION,
|
|
3
3
|
BLOB_COLLECTION,
|
|
4
4
|
BLOB_INDEX_COLLECTION
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QXJXG7DN.js";
|
|
6
6
|
import {
|
|
7
7
|
installViaBinder
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PTJY5R3O.js";
|
|
9
9
|
import {
|
|
10
10
|
buildRecordAad,
|
|
11
|
+
buildRecordEnvelope,
|
|
12
|
+
encrypt,
|
|
11
13
|
openEnvelopeJson
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
buildRecordEnvelope
|
|
15
|
-
} from "./chunk-SEE332EV.js";
|
|
16
|
-
import {
|
|
17
|
-
encrypt
|
|
18
|
-
} from "./chunk-UYB234ZR.js";
|
|
14
|
+
} from "./chunk-4J3MNVVX.js";
|
|
19
15
|
import {
|
|
20
16
|
TamperedError,
|
|
21
17
|
ValidationError
|
|
22
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VSUQI5Q6.js";
|
|
23
19
|
|
|
24
20
|
// src/via/blob/binding.ts
|
|
25
21
|
async function eraseBlobs(ctx, cfg) {
|
|
@@ -219,6 +215,7 @@ async function reportOrphanBlobChunks(adapter, vault) {
|
|
|
219
215
|
// src/with-shape/blobs/legacy-sweep.ts
|
|
220
216
|
async function sweepUnreferencedLegacyBlobs(ctx, reclaim) {
|
|
221
217
|
let blobs = 0, chunks = 0, reclaimed = 0;
|
|
218
|
+
const reclaimedETags = [];
|
|
222
219
|
const eTags = await ctx.adapter.list(ctx.vault, BLOB_INDEX_COLLECTION);
|
|
223
220
|
const dek = ctx.encrypted ? await ctx.getDEK(BLOB_COLLECTION) : null;
|
|
224
221
|
for (const eTag of eTags) {
|
|
@@ -226,7 +223,7 @@ async function sweepUnreferencedLegacyBlobs(ctx, reclaim) {
|
|
|
226
223
|
if (!env) continue;
|
|
227
224
|
let blob;
|
|
228
225
|
try {
|
|
229
|
-
const json = dek ? await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, env, dek) : env
|
|
226
|
+
const json = dek ? await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, env, dek) : env._data ?? "";
|
|
230
227
|
blob = JSON.parse(json);
|
|
231
228
|
} catch (err) {
|
|
232
229
|
if (err instanceof TamperedError) continue;
|
|
@@ -241,8 +238,9 @@ async function sweepUnreferencedLegacyBlobs(ctx, reclaim) {
|
|
|
241
238
|
}
|
|
242
239
|
await ctx.adapter.delete(ctx.vault, BLOB_INDEX_COLLECTION, eTag);
|
|
243
240
|
reclaimed += 1;
|
|
241
|
+
reclaimedETags.push(eTag);
|
|
244
242
|
}
|
|
245
|
-
return { blobs, chunks, reclaimed };
|
|
243
|
+
return { blobs, chunks, reclaimed, reclaimedETags };
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
// src/with-shape/blobs/blob-compaction.ts
|
|
@@ -474,4 +472,4 @@ export {
|
|
|
474
472
|
BLOB_EVICTION_AUDIT_COLLECTION,
|
|
475
473
|
runCompaction
|
|
476
474
|
};
|
|
477
|
-
//# sourceMappingURL=chunk-
|
|
475
|
+
//# sourceMappingURL=chunk-G5EDYVSE.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/orphan-report.ts","../src/with-shape/blobs/legacy-sweep.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * The blob `NoydbVia` (#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 `compileVias` 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 { NoydbVia, 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 `compileVias` (#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 `NoydbVia.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 blobVia(cfg: BlobViaConfig): NoydbVia {\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 coveredFields: Object.keys(cfg.fields), // #1447\n erase: (ctx) => eraseBlobs(ctx, cfg),\n describeFragment: () => buildBlobDescribeFragment(cfg.fields),\n }\n}\n\nexport function linkBlobVia(): void {\n installViaBinder('blob', (c) => blobVia(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 '../../capsule/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 `compileVias` 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 * #1452 — refuse a content-changing blob verb (`put`/`delete`/`publish`/\n * `deleteVersion`/`adoptExternal`) when the OWNING RECORD is not writable:\n * the collection runs its `beforePut` gate bus (closed periods, record\n * guards) with `incoming = existing`, so an attachment change is gated\n * exactly as an update of the record would be. Absent → ungated (direct\n * `BlobSet` construction in tests).\n */\n readonly assertOwnerWritable?: () => Promise<void>\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 * Orphan blob-chunk REPORT (#1133) — the read-only half of the residue #1127\n * stopped producing.\n *\n * Before #1127, `releaseRef` deleted a blob's index row BEFORE its chunks, so a\n * crash in between stranded chunk bodies nothing can ever reach: `loadBlobObject`\n * returns null so no reader addresses them, and `rekeyBlobSet` derives its chunk\n * ids from each index entry's `chunkCount` so a rotation never visits them. For a\n * LEGACY blob (bytes under the shared `_blob` DEK rather than a per-blob content\n * CEK) those bodies then stay openable under the retired DEK indefinitely.\n *\n * #1127 reversed the order, which removes the class at the source. It does\n * nothing for orphans created before the fix, and this module does not delete\n * them either. It counts them.\n *\n * ## Why this REPORTS and does not RECLAIM\n *\n * Deciding \"orphaned\" requires `store.list(_blob_index)`, and the store is\n * untrusted — that is the premise of the product, not a caveat on it. A store\n * that withholds a single index row makes a LIVE blob's chunks look orphaned. A\n * reclaim pass acting on that would convert **withholding**, which is reversible\n * the moment the store stops lying, into **permanent destruction** — handing an\n * untrusted store a data-loss primitive it does not currently have, in order to\n * tidy up residue.\n *\n * Reporting inverts the risk to something harmless: a lying store can only\n * inflate this count. Nothing is deleted on its word, so the worst outcome is an\n * operator investigating residue that turns out not to exist.\n *\n * Re-keying is not an alternative either: the chunk AAD is\n * `{eTag}:{chunkIndex}:{chunkCount}`, and `chunkCount` lives only in the index\n * row that is already gone — an orphan's AAD is unreconstructable, so it cannot\n * be decrypted-and-re-encrypted at all.\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { BLOB_INDEX_COLLECTION, BLOB_CHUNKS_COLLECTION } from './blob-set.js'\n\n/** How many distinct orphaned eTags to name in the report. */\nconst SAMPLE_LIMIT = 10\n\nexport interface OrphanChunkReport {\n /** Chunk rows whose eTag has no `_blob_index` entry. */\n readonly chunks: number\n /** Distinct eTags those chunks belong to. */\n readonly eTags: number\n /**\n * Up to {@link SAMPLE_LIMIT} of those eTags, so an operator can inspect a real\n * one rather than only a count. Deterministic (store list order), not random.\n */\n readonly sampleETags: readonly string[]\n /**\n * Chunk ids that do not match the `{eTag}_{index}` grammar at all. Counted\n * rather than skipped: an id this module cannot parse is a thing it cannot\n * classify either way, and reporting zero orphans while silently dropping\n * such ids would be the reassuring-but-wrong answer.\n */\n readonly unparseable: number\n}\n\n/**\n * Split a chunk id into `{eTag, index}`, or `null` if it does not fit the\n * grammar `BlobSet` writes (`${eTag}_${i}`).\n *\n * Splits on the LAST underscore and requires a digits-only suffix. eTags are\n * HMAC-SHA-256 hex today and contain no underscore, so a simpler split would\n * work — but it would break silently if that ever changed, and this rule holds\n * either way.\n */\nfunction parseChunkId(id: string): { eTag: string; index: number } | null {\n const cut = id.lastIndexOf('_')\n if (cut <= 0 || cut === id.length - 1) return null\n const suffix = id.slice(cut + 1)\n if (!/^\\d+$/.test(suffix)) return null\n return { eTag: id.slice(0, cut), index: Number(suffix) }\n}\n\n/**\n * Count chunk rows with no surviving index row. Read-only: two `list` calls and\n * no `get`, no `delete`, and no decryption — an orphan cannot be opened anyway.\n *\n * A store that cannot list either collection yields an empty report rather than\n * throwing: this is a diagnostic riding along with a maintenance pass, and it\n * must not be able to fail one.\n */\nexport async function reportOrphanBlobChunks(\n adapter: NoydbStore,\n vault: string,\n): Promise<OrphanChunkReport> {\n const empty: OrphanChunkReport = { chunks: 0, eTags: 0, sampleETags: [], unparseable: 0 }\n let liveETags: Set<string>\n let chunkIds: string[]\n try {\n liveETags = new Set(await adapter.list(vault, BLOB_INDEX_COLLECTION))\n chunkIds = await adapter.list(vault, BLOB_CHUNKS_COLLECTION)\n } catch {\n return empty\n }\n\n let chunks = 0\n let unparseable = 0\n const orphanETags = new Set<string>()\n for (const id of chunkIds) {\n const parsed = parseChunkId(id)\n if (parsed === null) { unparseable += 1; continue }\n if (liveETags.has(parsed.eTag)) continue\n chunks += 1\n orphanETags.add(parsed.eTag)\n }\n\n return {\n chunks,\n eTags: orphanETags.size,\n sampleETags: [...orphanETags].slice(0, SAMPLE_LIMIT),\n unparseable,\n }\n}\n","/**\n * #1453 — count, and optionally reclaim, legacy blobs nothing references.\n *\n * A legacy blob (no per-blob `_cek`) is left at refCount 0 \"for deferred GC\"\n * by `releaseRef`; before this the deferred GC had no verb outside\n * `forget()` (which needs `withForget`), and `orphanBlobChunks` could not see\n * these rows because they keep their index entry — so `vault.compact()`\n * reported clean over a vault whose blob storage grew on every overwrite.\n *\n * Reclaiming here is SAFE where reclaiming orphans is not (see\n * `orphan-report.ts`): the decision reads `refCount` off an AAD-bound index\n * row the hub decrypted itself, never the store's `list()`. A withholding\n * store cannot make a live blob look unreferenced; it can only hide a row from\n * this sweep, which then does nothing to it.\n *\n * Rows sealed at an elevated tier do not open under the flat `_blob` DEK and\n * are skipped, not counted: this is a tier-0 pass.\n */\nimport type { NoydbStore, BlobObject } from '../../kernel/types.js'\nimport { openEnvelopeJson, type EnclaveKey } from '../../capsule/index.js'\nimport { TamperedError } from '../../kernel/errors.js'\nimport { BLOB_COLLECTION, BLOB_INDEX_COLLECTION, BLOB_CHUNKS_COLLECTION } from './blob-set.js'\n\n/** What `vault.compact()` reports (and, on request, reclaims) about legacy blobs. */\nexport interface UnreferencedLegacyBlobReport {\n /** `_blob_index` rows with no `_cek` (legacy) and `refCount <= 0`. */\n readonly blobs: number\n /** Chunk rows those blobs still hold. */\n readonly chunks: number\n /** How many of those blobs this run deleted (chunks first, then the index row). */\n readonly reclaimed: number\n /**\n * #28 — the eTags this run actually deleted, so a deferred erasure set can be\n * reconciled against the reclaim that finished it:\n *\n * ```ts\n * const still = before.blobResidueETags.filter((e) => !report.reclaimedETags.includes(e))\n * ```\n *\n * ⛔ DO NOT PERSIST. An eTag is a content hash — see\n * `ForgetResult.blobResidueETags` for why a stored receipt of them is a\n * re-identification oracle. Reconcile in memory, persist the verdict.\n *\n * Empty on a `dryRun` (nothing was deleted), even though `blobs`/`chunks`\n * still count what WOULD be.\n */\n readonly reclaimedETags: readonly string[]\n}\n\nexport async function sweepUnreferencedLegacyBlobs(\n ctx: { adapter: NoydbStore; vault: string; getDEK: (name: string) => Promise<EnclaveKey>; encrypted: boolean },\n reclaim: boolean,\n): Promise<UnreferencedLegacyBlobReport> {\n let blobs = 0, chunks = 0, reclaimed = 0\n const reclaimedETags: string[] = []\n const eTags = await ctx.adapter.list(ctx.vault, BLOB_INDEX_COLLECTION)\n const dek = ctx.encrypted ? await ctx.getDEK(BLOB_COLLECTION) : null\n for (const eTag of eTags) {\n const env = await ctx.adapter.get(ctx.vault, BLOB_INDEX_COLLECTION, eTag)\n if (!env) continue\n let blob: BlobObject\n try {\n // `encrypt: false` stores the body as plaintext JSON; read it as the\n // opaque string it is (no enclave field is interpreted here).\n const json = dek ? await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, env, dek) : (env._data ?? '')\n blob = JSON.parse(json) as BlobObject\n } catch (err) {\n if (err instanceof TamperedError) continue // elevated-tier row: not this pass's\n throw err\n }\n // A BlobObject with a wrapped content key is erasable and shreds eagerly at\n // refCount 0; only the key-less legacy shape ever lingers.\n if (Object.hasOwn(blob, '_cek') || blob.refCount > 0) continue\n blobs += 1\n chunks += blob.chunkCount\n if (!reclaim) continue\n // CHUNKS BEFORE INDEX — same ordering rule as `deleteChunksThenIndex` (#1127).\n for (let i = 0; i < blob.chunkCount; i++) {\n await ctx.adapter.delete(ctx.vault, BLOB_CHUNKS_COLLECTION, `${eTag}_${i}`)\n }\n await ctx.adapter.delete(ctx.vault, BLOB_INDEX_COLLECTION, eTag)\n reclaimed += 1\n reclaimedETags.push(eTag)\n }\n return { blobs, chunks, reclaimed, reclaimedETags }\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 '../../capsule/index.js'\nimport { reportOrphanBlobChunks, type OrphanChunkReport } from './orphan-report.js'\nimport { sweepUnreferencedLegacyBlobs, type UnreferencedLegacyBlobReport } from './legacy-sweep.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 * #1133 — chunk rows in `_blob_chunks` with no surviving `_blob_index` entry.\n * Pre-#1127 residue: unreachable by every reader AND by rotation, so for a\n * legacy blob the bytes stay openable under a retired `_blob` DEK.\n *\n * REPORTED, never reclaimed — deciding \"orphaned\" means trusting the store's\n * `list`, and a store that withholds one index row would have us destroy a\n * LIVE blob's chunks. See `orphan-report.ts` for the full argument. A vault\n * that has run `migrate()` has no legacy chunks to strand, and `chunks: 0`\n * is the expected reading everywhere else.\n */\n readonly orphanBlobChunks: OrphanChunkReport\n /**\n * #1453 — legacy (no `_cek`) blobs at `refCount <= 0`: released by every\n * reference-drop path but never reclaimed, because a legacy chunk has no\n * eager shred. Counted on every run; deleted only with\n * `reclaimLegacyBlobs: true`. Not an orphan in the `orphanBlobChunks`\n * sense — these rows keep their index entry, which is exactly why that\n * report could not see them.\n */\n readonly unreferencedLegacyBlobs: UnreferencedLegacyBlobReport\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 * #1453 — reclaim unreferenced legacy blobs (chunks first, then the index\n * row). Off by default: reclaiming is irreversible and the default posture\n * for a maintenance pass is to report. Ignored under `dryRun`.\n */\n readonly reclaimLegacyBlobs?: boolean\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 // #1133 — two `list` calls, no decryption, nothing written. Always on rather\n // than opt-in: residue an operator has to know to ask about is residue nobody\n // finds. The cost is negligible beside this pass's own per-record `getRecord`.\n const orphanBlobChunks = await reportOrphanBlobChunks(ctx.adapter, ctx.vault)\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n // #1453 — always counted, reclaimed only on request and never under dryRun.\n const unreferencedLegacyBlobs = await sweepUnreferencedLegacyBlobs(ctx, options.reclaimLegacyBlobs === true && !dryRun)\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 orphanBlobChunks,\n unreferencedLegacyBlobs,\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":[]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertKeyringNotExpired,
|
|
3
|
+
loadKeyring,
|
|
4
|
+
readKeyringFile,
|
|
5
|
+
resolveEchoReveal,
|
|
6
|
+
verifyPrompt,
|
|
7
|
+
verifyTypedEcho
|
|
8
|
+
} from "./chunk-45VHWE4C.js";
|
|
9
|
+
import {
|
|
10
|
+
NoAccessError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
WrongEchoError,
|
|
13
|
+
WrongPromptError
|
|
14
|
+
} from "./chunk-VSUQI5Q6.js";
|
|
15
|
+
|
|
16
|
+
// src/with-party/team/echo-ceremony.ts
|
|
17
|
+
async function beginEchoUnlock(store, vault, opts) {
|
|
18
|
+
const { userId, prompt, deviceSeal } = opts;
|
|
19
|
+
const found = await readKeyringFile(store, vault, userId);
|
|
20
|
+
if (!found) {
|
|
21
|
+
throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}"`);
|
|
22
|
+
}
|
|
23
|
+
const { file: keyringFile } = found;
|
|
24
|
+
assertKeyringNotExpired(keyringFile);
|
|
25
|
+
const block = keyringFile.echo;
|
|
26
|
+
if (block === void 0) {
|
|
27
|
+
throw new ValidationError("not an echo keyring");
|
|
28
|
+
}
|
|
29
|
+
if (!await verifyPrompt(block, prompt)) {
|
|
30
|
+
throw new WrongPromptError();
|
|
31
|
+
}
|
|
32
|
+
const reveal = await resolveEchoReveal(block, prompt, deviceSeal);
|
|
33
|
+
return {
|
|
34
|
+
reveal,
|
|
35
|
+
maskHint: block.mask_hint,
|
|
36
|
+
async complete(input) {
|
|
37
|
+
let echo;
|
|
38
|
+
if (reveal !== null) {
|
|
39
|
+
if (input.echo !== void 0 && input.echo !== reveal) {
|
|
40
|
+
throw new WrongEchoError();
|
|
41
|
+
}
|
|
42
|
+
echo = reveal;
|
|
43
|
+
} else {
|
|
44
|
+
if (input.echo === void 0) {
|
|
45
|
+
throw new ValidationError("beginEchoUnlock: echo is required to complete a degraded (typed-echo) ceremony.");
|
|
46
|
+
}
|
|
47
|
+
if (!await verifyTypedEcho(block, input.echo)) {
|
|
48
|
+
throw new WrongEchoError();
|
|
49
|
+
}
|
|
50
|
+
echo = input.echo;
|
|
51
|
+
}
|
|
52
|
+
return loadKeyring(store, vault, { userId, secret: { prompt, echo, key: input.key } });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
beginEchoUnlock
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-G66YXUTY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-party/team/echo-ceremony.ts"],"sourcesContent":["/**\n * `beginEchoUnlock` — the interactive ceremony API (spec\n * design-history/2026-08-02-echo-secret-design.md, #940, Task 6).\n *\n * Split out of `echo-secret.ts` (which owns only the block builders +\n * verifiers) because this module orchestrates BOTH `echo-secret.ts`\n * (`verifyPrompt`/`resolveEchoReveal`/`verifyTypedEcho`) AND `keyring.ts`\n * (`loadKeyring`) — and `keyring.ts` itself imports `buildEchoBlock` from\n * `echo-secret.ts`. Keeping the ceremony here instead of inside\n * `echo-secret.ts` yields a clean DAG (`echo-ceremony.ts` → {`echo-secret.ts`,\n * `keyring.ts`}; `keyring.ts` → `echo-secret.ts`) instead of a two-file\n * cycle, and restores `echo-secret.ts`'s documented charter (builders +\n * verifiers only).\n *\n * @module\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { NoydbDeviceSeal } from './device-seal.js'\nimport { WrongPromptError, WrongEchoError, ValidationError, NoAccessError } from '../../kernel/errors.js'\nimport { verifyPrompt, resolveEchoReveal, verifyTypedEcho } from './echo-secret.js'\nimport { loadKeyring, readKeyringFile, assertKeyringNotExpired, type UnlockedKeyring } from './keyring.js'\n\n/** Options for {@link beginEchoUnlock}. */\nexport interface BeginEchoUnlockOptions {\n readonly userId: string\n readonly prompt: string\n readonly deviceSeal?: NoydbDeviceSeal\n}\n\n/**\n * A prompt-verified, in-progress echo unlock. Stateless w.r.t. attempts —\n * `complete()` may be called more than once (e.g. retry after a wrong key).\n */\nexport interface EchoCeremony {\n /**\n * Echo to display for owner recognition; null ⇒ degraded: ask the owner to\n * TYPE it.\n *\n * Conforming-player contract (spec\n * design-history/2026-08-02-echo-secret-design.md, \"Ceremony\"):\n * when the owner does NOT recognize the revealed echo, the player MUST show\n * a hard phishing warning — do not continue, open the official app, rotate\n * the secret immediately — MUST NOT offer any skip or\n * \"verification unavailable\" fallback, and MUST simply abandon the ceremony\n * by never calling {@link EchoCeremony.complete}. The kernel cannot enforce\n * this terminal state; it is the player's obligation.\n */\n readonly reveal: string | null\n readonly maskHint: string | undefined\n /** Complete the ceremony. `echo` is REQUIRED when `reveal` is null. */\n complete(input: { readonly echo?: string; readonly key: string }): Promise<UnlockedKeyring>\n}\n\n/**\n * Begin the interactive anti-phishing unlock ceremony (spec decision 4):\n * prompt → echo reveal → key. Fetches and parses the `_keyring/{userId}`\n * row (same store path as `loadKeyring`), verifies the prompt, and resolves\n * the echo for display (or degrades to the typed-echo path when the reveal\n * cannot be resolved — sealed reveal under a foreign device).\n *\n * Conforming-player contract (spec\n * design-history/2026-08-02-echo-secret-design.md): an owner who does\n * not recognize `ceremony.reveal` is looking at a phish. The player MUST then\n * show a hard warning (stop, open the official app, rotate the secret now),\n * MUST NOT offer a skip / \"verification unavailable\" fallback or any\n * single-field secret entry, and MUST abandon the ceremony without calling\n * `complete()`.\n *\n * @throws NoAccessError when no keyring exists for `userId` (mirrors\n * `loadKeyring`'s missing-row error).\n * @throws KeyringExpiredError when the slot is past its `expires_at` —\n * checked before the prompt is verified, so an expired slot never reveals.\n * @throws ValidationError when the keyring is not an echo keyring.\n * @throws WrongPromptError when the prompt fails its verifier.\n */\nexport async function beginEchoUnlock(\n store: NoydbStore,\n vault: string,\n opts: BeginEchoUnlockOptions,\n): Promise<EchoCeremony> {\n const { userId, prompt, deviceSeal } = opts\n\n // Same store path + missing-row error as loadKeyring, now shared via\n // `readKeyringFile` (#951).\n const found = await readKeyringFile(store, vault, userId)\n if (!found) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\"`)\n }\n const { file: keyringFile } = found\n\n // Same expiry gate as loadKeyring, applied BEFORE the prompt is verified so\n // an expired slot never reveals its echo (and leaks no timing on the\n // prompt). Now shared via `assertKeyringNotExpired` (#951).\n assertKeyringNotExpired(keyringFile)\n\n const block = keyringFile.echo\n if (block === undefined) {\n throw new ValidationError('not an echo keyring')\n }\n\n if (!(await verifyPrompt(block, prompt))) {\n throw new WrongPromptError()\n }\n\n const reveal = await resolveEchoReveal(block, prompt, deviceSeal)\n\n return {\n reveal,\n maskHint: block.mask_hint,\n async complete(input: { readonly echo?: string; readonly key: string }): Promise<UnlockedKeyring> {\n let echo: string\n if (reveal !== null) {\n // A supplied input.echo must agree with the revealed echo — defensive:\n // a player that collects a typed echo despite a reveal must not\n // silently unlock with a mismatched pair.\n if (input.echo !== undefined && input.echo !== reveal) {\n throw new WrongEchoError()\n }\n echo = reveal\n } else {\n if (input.echo === undefined) {\n throw new ValidationError('beginEchoUnlock: echo is required to complete a degraded (typed-echo) ceremony.')\n }\n if (!(await verifyTypedEcho(block, input.echo))) {\n throw new WrongEchoError()\n }\n echo = input.echo\n }\n return loadKeyring(store, vault, { userId, secret: { prompt, echo, key: input.key } })\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4EA,eAAsB,gBACpB,OACA,OACA,MACuB;AACvB,QAAM,EAAE,QAAQ,QAAQ,WAAW,IAAI;AAIvC,QAAM,QAAQ,MAAM,gBAAgB,OAAO,OAAO,MAAM;AACxD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,cAAc,8BAA8B,MAAM,eAAe,KAAK,GAAG;AAAA,EACrF;AACA,QAAM,EAAE,MAAM,YAAY,IAAI;AAK9B,0BAAwB,WAAW;AAEnC,QAAM,QAAQ,YAAY;AAC1B,MAAI,UAAU,QAAW;AACvB,UAAM,IAAI,gBAAgB,qBAAqB;AAAA,EACjD;AAEA,MAAI,CAAE,MAAM,aAAa,OAAO,MAAM,GAAI;AACxC,UAAM,IAAI,iBAAiB;AAAA,EAC7B;AAEA,QAAM,SAAS,MAAM,kBAAkB,OAAO,QAAQ,UAAU;AAEhE,SAAO;AAAA,IACL;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,MAAM,SAAS,OAAmF;AAChG,UAAI;AACJ,UAAI,WAAW,MAAM;AAInB,YAAI,MAAM,SAAS,UAAa,MAAM,SAAS,QAAQ;AACrD,gBAAM,IAAI,eAAe;AAAA,QAC3B;AACA,eAAO;AAAA,MACT,OAAO;AACL,YAAI,MAAM,SAAS,QAAW;AAC5B,gBAAM,IAAI,gBAAgB,iFAAiF;AAAA,QAC7G;AACA,YAAI,CAAE,MAAM,gBAAgB,OAAO,MAAM,IAAI,GAAI;AAC/C,gBAAM,IAAI,eAAe;AAAA,QAC3B;AACA,eAAO,MAAM;AAAA,MACf;AACA,aAAO,YAAY,OAAO,OAAO,EAAE,QAAQ,QAAQ,EAAE,QAAQ,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC;AAAA,IACvF;AAAA,EACF;AACF;","names":[]}
|