@noy-db/hub 0.2.0-pre.3 → 0.2.0-pre.31
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/README.md +126 -0
- package/dist/adapter/index.d.ts +9 -0
- package/dist/adapter/index.js +14 -0
- package/dist/aggregate/index.d.ts +3 -2
- package/dist/aggregate/index.js +10 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.d.ts +7 -5
- package/dist/attestation/index.js +7 -6
- package/dist/attestation/index.js.map +1 -1
- package/dist/blobs/index.d.ts +9 -7
- package/dist/blobs/index.js +12 -6
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +29 -18
- package/dist/bundle/index.js +53 -192
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-75QDHSE4.js → chunk-2QDWRK3B.js} +5 -5
- package/dist/{chunk-74JEQFMT.js → chunk-2UT7HJJR.js} +5 -5
- package/dist/chunk-2UT7HJJR.js.map +1 -0
- package/dist/{chunk-FCDO7UAO.js → chunk-2ZYIN3BL.js} +2 -2
- package/dist/{chunk-BFI3RS42.js → chunk-33EQCZXE.js} +2 -2
- package/dist/chunk-33EQCZXE.js.map +1 -0
- package/dist/{chunk-WRLHNG6H.js → chunk-4K4QKYK4.js} +4 -4
- package/dist/chunk-4K4QKYK4.js.map +1 -0
- package/dist/{chunk-HGZ7DC5H.js → chunk-4L6N56B4.js} +2 -2
- package/dist/chunk-4L6N56B4.js.map +1 -0
- package/dist/{chunk-YMYK7US4.js → chunk-4YD7VZH4.js} +2 -2
- package/dist/chunk-4YD7VZH4.js.map +1 -0
- package/dist/{chunk-ZUMGGHRB.js → chunk-5PTX7PKD.js} +4 -4
- package/dist/{chunk-QAU5HM6Q.js → chunk-6EKRQ7QF.js} +3 -3
- package/dist/{chunk-ZC2AAE6J.js → chunk-7GPQPLGO.js} +19 -4
- package/dist/chunk-7GPQPLGO.js.map +1 -0
- package/dist/{chunk-EPK6A3WJ.js → chunk-A3JMGXPG.js} +2 -2
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/{chunk-FXQYZNOW.js → chunk-A7DNZVAV.js} +1 -1
- package/dist/chunk-A7DNZVAV.js.map +1 -0
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-YL2DR3HY.js → chunk-ASIGWYRA.js} +2 -2
- package/dist/chunk-ASIGWYRA.js.map +1 -0
- package/dist/{chunk-2EYC3WDT.js → chunk-AWU4JF7C.js} +93 -93
- package/dist/chunk-AWU4JF7C.js.map +1 -0
- package/dist/chunk-BEZ2EV43.js +129 -0
- package/dist/chunk-BEZ2EV43.js.map +1 -0
- package/dist/{chunk-IS5HWQO7.js → chunk-CIZDUAHI.js} +30 -4
- package/dist/chunk-CIZDUAHI.js.map +1 -0
- package/dist/{chunk-4OQWR46B.js → chunk-CUPTCF4R.js} +5 -5
- package/dist/chunk-CUPTCF4R.js.map +1 -0
- package/dist/{chunk-5ZGZ6HIZ.js → chunk-DEE4CRYN.js} +30 -7
- package/dist/chunk-DEE4CRYN.js.map +1 -0
- package/dist/{chunk-UOF74WQY.js → chunk-E2MDPPOC.js} +37 -2
- package/dist/chunk-E2MDPPOC.js.map +1 -0
- package/dist/{chunk-SAVQ6E2O.js → chunk-FFKQ65YX.js} +10 -3
- package/dist/chunk-FFKQ65YX.js.map +1 -0
- package/dist/{chunk-2XLVPKXG.js → chunk-G7ALNCQH.js} +10 -2
- package/dist/chunk-G7ALNCQH.js.map +1 -0
- package/dist/chunk-GGXN73NM.js +252 -0
- package/dist/chunk-GGXN73NM.js.map +1 -0
- package/dist/{chunk-UVPGJXVO.js → chunk-JOJPBZSZ.js} +5 -5
- package/dist/{chunk-YDLAFP36.js → chunk-JZ5DS4DP.js} +346 -3
- package/dist/chunk-JZ5DS4DP.js.map +1 -0
- package/dist/{chunk-KMI2NBBF.js → chunk-K4JID2ZE.js} +44 -8
- package/dist/chunk-K4JID2ZE.js.map +1 -0
- package/dist/{aggregate/index.cjs → chunk-K6NVSHAH.js} +241 -210
- package/dist/chunk-K6NVSHAH.js.map +1 -0
- package/dist/chunk-KJ3K5VZM.js +524 -0
- package/dist/chunk-KJ3K5VZM.js.map +1 -0
- package/dist/chunk-L5HHBOMS.js +232 -0
- package/dist/chunk-L5HHBOMS.js.map +1 -0
- package/dist/{chunk-YK72A4IT.js → chunk-LBILABKZ.js} +4 -4
- package/dist/chunk-LXKBG52H.js +151 -0
- package/dist/chunk-LXKBG52H.js.map +1 -0
- package/dist/{chunk-NGSPBLLE.js → chunk-MPIZQGFA.js} +50 -20
- package/dist/chunk-MPIZQGFA.js.map +1 -0
- package/dist/{chunk-4X2S7PBF.js → chunk-MRJPSXPE.js} +84 -70
- package/dist/chunk-MRJPSXPE.js.map +1 -0
- package/dist/{chunk-6S3LLAQ5.js → chunk-MSMDDDDJ.js} +3 -3
- package/dist/{chunk-6S3LLAQ5.js.map → chunk-MSMDDDDJ.js.map} +1 -1
- package/dist/{chunk-GAUBWHAF.js → chunk-OCYFFBZS.js} +4 -4
- package/dist/{chunk-NCO2JGKK.js → chunk-PDVP3C2I.js} +1 -1
- package/dist/chunk-PDVP3C2I.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/{chunk-P6256WTJ.js → chunk-QBLPY44S.js} +3 -3
- package/dist/chunk-QBLPY44S.js.map +1 -0
- package/dist/{query/index.cjs → chunk-QGBBQKDS.js} +706 -831
- package/dist/chunk-QGBBQKDS.js.map +1 -0
- package/dist/{chunk-FS7A4XNF.js → chunk-QWJCNXH2.js} +3 -3
- package/dist/chunk-QXWJQ7UU.js +380 -0
- package/dist/chunk-QXWJQ7UU.js.map +1 -0
- package/dist/{chunk-GDTCGIPX.js → chunk-RCB6GWTC.js} +27 -5
- package/dist/chunk-RCB6GWTC.js.map +1 -0
- package/dist/{chunk-K5PVGKE4.js → chunk-SAAJYSVV.js} +2 -2
- package/dist/{chunk-G7PAZ3TD.js → chunk-SHJ3BRTZ.js} +46 -10
- package/dist/chunk-SHJ3BRTZ.js.map +1 -0
- package/dist/chunk-SQXTKZL7.js +125 -0
- package/dist/chunk-SQXTKZL7.js.map +1 -0
- package/dist/chunk-STNPB3UM.js +9 -0
- package/dist/chunk-STNPB3UM.js.map +1 -0
- package/dist/{chunk-NSLTPGEN.js → chunk-TMUMMAWV.js} +12 -2
- package/dist/{chunk-NSLTPGEN.js.map → chunk-TMUMMAWV.js.map} +1 -1
- package/dist/{chunk-LS3JLEIB.js → chunk-TZGWI5AX.js} +4 -4
- package/dist/{chunk-GD3BGKAR.js → chunk-UBF5JEOJ.js} +44 -5
- package/dist/chunk-UBF5JEOJ.js.map +1 -0
- package/dist/{chunk-T6HQMVML.js → chunk-W5NVNJDX.js} +5250 -801
- package/dist/chunk-W5NVNJDX.js.map +1 -0
- package/dist/chunk-XAYDHD2O.js +123 -0
- package/dist/chunk-XAYDHD2O.js.map +1 -0
- package/dist/{chunk-LOL725S4.js → chunk-XYW2CSCI.js} +31 -3
- package/dist/chunk-XYW2CSCI.js.map +1 -0
- package/dist/{chunk-TLFUDXVV.js → chunk-ZCMOS4H2.js} +31 -10
- package/dist/chunk-ZCMOS4H2.js.map +1 -0
- package/dist/{chunk-FBMXWVGP.js → chunk-ZK66B5EY.js} +450 -30
- package/dist/chunk-ZK66B5EY.js.map +1 -0
- package/dist/{chunk-GVXBHCZ2.js → chunk-ZPJSQPQH.js} +66 -7
- package/dist/chunk-ZPJSQPQH.js.map +1 -0
- package/dist/consent/index.d.ts +8 -6
- package/dist/consent/index.js +4 -3
- package/dist/consent/index.js.map +1 -1
- package/dist/crdt/index.js +1 -0
- package/dist/crdt/index.js.map +1 -1
- package/dist/{crypto-H2Y3DDFW.js → crypto-OSIV2IIW.js} +8 -3
- package/dist/{ulid-CiM2OAeM.d.ts → decrypt-partition-CXS5KopB.d.ts} +37 -82
- package/dist/{delegation-QSC7G5QC.js → delegation-BQTRJATI.js} +6 -5
- package/dist/derivations/index.d.ts +11 -9
- package/dist/derivations/index.js +9 -6
- package/dist/{dev-unlock-Cf2B7Kih.d.ts → dev-unlock-CnHTTVea.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/errors-BAWO5Z5a.d.ts +1500 -0
- package/dist/executor-KYSKFJ2R.js +9 -0
- package/dist/executor-M7W3NEKR.js +9 -0
- package/dist/executor-VDTDGWO6.js +13 -0
- package/dist/{fanout-sidecar-NRBWSLRK.js → fanout-sidecar-AZALISHB.js} +3 -2
- package/dist/fanout-sidecar-AZALISHB.js.map +1 -0
- package/dist/forget/index.d.ts +1 -0
- package/dist/forget/index.js +15 -0
- package/dist/guards/index.d.ts +16 -8
- package/dist/guards/index.js +12 -5
- package/dist/{hash-gVn_uKhp.d.ts → hash-oc5paYl0.d.ts} +1 -1
- package/dist/history/index.d.ts +9 -7
- package/dist/history/index.js +10 -7
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.d.ts +8 -6
- package/dist/i18n/index.js +116 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index-BMmajblo.d.ts +362 -0
- package/dist/index-BmhGztUi.d.ts +93 -0
- package/dist/{types-D9eB0Rvh.d.ts → index-DHn9lEP0.d.ts} +4305 -1523
- package/dist/{index-BF1B2HB9.d.ts → index-Ddm8D3Oo.d.ts} +186 -1151
- package/dist/index.d.ts +342 -70
- package/dist/index.js +420 -112
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +5 -4
- package/dist/indexing/index.js.map +1 -1
- package/dist/issue-DPHRF2TI.js +13 -0
- package/dist/kernel/index.d.ts +11 -0
- package/dist/kernel/index.js +50 -0
- package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-ChSqcF5t.d.ts} +2 -2
- package/dist/{ledger-WOEJUYTP.js → ledger-CI7GSMLB.js} +7 -6
- package/dist/materialized-views/index.d.ts +23 -20
- package/dist/materialized-views/index.js +9 -7
- package/dist/mime-magic-JeLKHRng.d.ts +103 -0
- package/dist/noydb-NAU2DTFP.js +40 -0
- package/dist/overlay-views/index.d.ts +32 -12
- package/dist/overlay-views/index.js +7 -6
- package/dist/periods/index.d.ts +8 -6
- package/dist/periods/index.js +7 -6
- package/dist/{predicate-Dnu81tsS.d.cts → predicate-BmhBSPCH.d.ts} +87 -5
- package/dist/{public-envelope-OHQ5UZFM.js → public-envelope-MFMBFJLZ.js} +5 -4
- package/dist/query/index.d.ts +4 -3
- package/dist/query/index.js +14 -6
- package/dist/read-only-facade-3OQRZ3VS.js +8 -0
- package/dist/registry-IX5WKK4F.js +8 -0
- package/dist/registry-LMHO5265.js +11 -0
- package/dist/registry-TXCKWH26.js +9 -0
- package/dist/registry-VFEDQSYV.js +9 -0
- package/dist/revoke-5PUOZPJJ.js +18 -0
- package/dist/revoke-5PUOZPJJ.js.map +1 -0
- package/dist/sealed-record/index.d.ts +123 -0
- package/dist/sealed-record/index.js +43 -0
- package/dist/sealed-record/index.js.map +1 -0
- package/dist/session/index.d.ts +9 -7
- package/dist/session/index.js +4 -3
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.d.ts +8 -6
- package/dist/shadow/index.js +3 -2
- package/dist/shadow/index.js.map +1 -1
- package/dist/{signer-M4K5HBLD.js → signer-UB5TQOZ6.js} +6 -5
- package/dist/signer-UB5TQOZ6.js.map +1 -0
- package/dist/snapshots/index.d.ts +30 -0
- package/dist/snapshots/index.js +153 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/{stale-PAGCS4K5.js → stale-J3TUF5C5.js} +3 -2
- package/dist/stale-J3TUF5C5.js.map +1 -0
- package/dist/store/index.d.ts +15 -6
- package/dist/store/index.js +3 -2
- package/dist/{strategy-DSTrsZ8t.d.ts → strategy-8QGs5KQE.d.ts} +475 -7
- package/dist/sync/index.d.ts +7 -5
- package/dist/sync/index.js +5 -4
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.d.ts +8 -6
- package/dist/team/index.js +9 -8
- package/dist/transition-guard-Dy3NioQr.d.ts +165 -0
- package/dist/tx/index.d.ts +26 -8
- package/dist/tx/index.js +10 -5
- package/dist/tx/index.js.map +1 -1
- package/dist/ulid-DRH25k3y.d.ts +66 -0
- package/dist/{ulid-COREQ2RQ.js → ulid-KEPZMD2N.js} +2 -1
- package/dist/ulid-KEPZMD2N.js.map +1 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +6 -1
- package/dist/util/index.js.map +1 -1
- package/dist/{with-materialized-view-qtoJ3xKJ.d.ts → with-materialized-view-DIVeez0W.d.ts} +2 -2
- package/dist/{with-overlayed-view-DFaRfgMr.d.ts → with-overlayed-view-DVZrc5Hb.d.ts} +2 -2
- package/dist/with-rollup-CbU-O4wP.d.ts +47 -0
- package/dist/zod-HAJM7LMS.js +14763 -0
- package/dist/zod-HAJM7LMS.js.map +1 -0
- package/package.json +73 -191
- package/dist/aggregate/index.cjs.map +0 -1
- package/dist/aggregate/index.d.cts +0 -38
- package/dist/attestation/index.cjs +0 -305
- package/dist/attestation/index.cjs.map +0 -1
- package/dist/attestation/index.d.cts +0 -52
- package/dist/blobs/index.cjs +0 -1480
- package/dist/blobs/index.cjs.map +0 -1
- package/dist/blobs/index.d.cts +0 -46
- package/dist/bundle/index.cjs +0 -18561
- package/dist/bundle/index.cjs.map +0 -1
- package/dist/bundle/index.d.cts +0 -176
- package/dist/chunk-2EYC3WDT.js.map +0 -1
- package/dist/chunk-2XLVPKXG.js.map +0 -1
- package/dist/chunk-4OQWR46B.js.map +0 -1
- package/dist/chunk-4UBOTYP5.js +0 -1367
- package/dist/chunk-4UBOTYP5.js.map +0 -1
- package/dist/chunk-4X2S7PBF.js.map +0 -1
- package/dist/chunk-5YHWBPOT.js +0 -19
- package/dist/chunk-5YHWBPOT.js.map +0 -1
- package/dist/chunk-5ZGZ6HIZ.js.map +0 -1
- package/dist/chunk-74JEQFMT.js.map +0 -1
- package/dist/chunk-A6SWRXUQ.js +0 -118
- package/dist/chunk-A6SWRXUQ.js.map +0 -1
- package/dist/chunk-BFI3RS42.js.map +0 -1
- package/dist/chunk-EMEX37ZN.js +0 -51
- package/dist/chunk-EMEX37ZN.js.map +0 -1
- package/dist/chunk-EPK6A3WJ.js.map +0 -1
- package/dist/chunk-FBMXWVGP.js.map +0 -1
- package/dist/chunk-FXQYZNOW.js.map +0 -1
- package/dist/chunk-G7PAZ3TD.js.map +0 -1
- package/dist/chunk-GD3BGKAR.js.map +0 -1
- package/dist/chunk-GDTCGIPX.js.map +0 -1
- package/dist/chunk-GVXBHCZ2.js.map +0 -1
- package/dist/chunk-HGZ7DC5H.js.map +0 -1
- package/dist/chunk-IS5HWQO7.js.map +0 -1
- package/dist/chunk-KMI2NBBF.js.map +0 -1
- package/dist/chunk-KYKMKLJ6.js +0 -340
- package/dist/chunk-KYKMKLJ6.js.map +0 -1
- package/dist/chunk-LOL725S4.js.map +0 -1
- package/dist/chunk-MRIBLZL3.js +0 -86
- package/dist/chunk-MRIBLZL3.js.map +0 -1
- package/dist/chunk-NCO2JGKK.js.map +0 -1
- package/dist/chunk-NGSPBLLE.js.map +0 -1
- package/dist/chunk-P6256WTJ.js.map +0 -1
- package/dist/chunk-SAVQ6E2O.js.map +0 -1
- package/dist/chunk-T6HQMVML.js.map +0 -1
- package/dist/chunk-TLFUDXVV.js.map +0 -1
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-UOF74WQY.js.map +0 -1
- package/dist/chunk-WRLHNG6H.js.map +0 -1
- package/dist/chunk-YDLAFP36.js.map +0 -1
- package/dist/chunk-YL2DR3HY.js.map +0 -1
- package/dist/chunk-YMYK7US4.js.map +0 -1
- package/dist/chunk-ZC2AAE6J.js.map +0 -1
- package/dist/consent/index.cjs +0 -204
- package/dist/consent/index.cjs.map +0 -1
- package/dist/consent/index.d.cts +0 -25
- package/dist/crdt/index.cjs +0 -152
- package/dist/crdt/index.cjs.map +0 -1
- package/dist/crdt/index.d.cts +0 -30
- package/dist/derivations/index.cjs +0 -351
- package/dist/derivations/index.cjs.map +0 -1
- package/dist/derivations/index.d.cts +0 -72
- package/dist/dev-unlock-De3mjQWv.d.cts +0 -263
- package/dist/executor-BZKFZVRC.js +0 -8
- package/dist/executor-GFZFDQXV.js +0 -8
- package/dist/executor-KT2IOZVP.js +0 -11
- package/dist/fanout-sidecar-NRBWSLRK.js.map +0 -1
- package/dist/guards/index.cjs +0 -322
- package/dist/guards/index.cjs.map +0 -1
- package/dist/guards/index.d.cts +0 -31
- package/dist/hash-vBCB0-Ps.d.cts +0 -63
- package/dist/history/index.cjs +0 -1222
- package/dist/history/index.cjs.map +0 -1
- package/dist/history/index.d.cts +0 -63
- package/dist/i18n/index.cjs +0 -840
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/index.d.cts +0 -39
- package/dist/index-DVkvrgpm.d.cts +0 -2290
- package/dist/index.cjs +0 -23590
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -778
- package/dist/indexing/index.cjs +0 -738
- package/dist/indexing/index.cjs.map +0 -1
- package/dist/indexing/index.d.cts +0 -36
- package/dist/issue-BAJ7ZB4S.js +0 -12
- package/dist/lazy-builder-C-rPfWG0.d.cts +0 -304
- package/dist/materialized-views/index.cjs +0 -837
- package/dist/materialized-views/index.cjs.map +0 -1
- package/dist/materialized-views/index.d.cts +0 -184
- package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
- package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
- package/dist/noydb-XNQSKXGO.js +0 -34
- package/dist/overlay-views/index.cjs +0 -359
- package/dist/overlay-views/index.cjs.map +0 -1
- package/dist/overlay-views/index.d.cts +0 -82
- package/dist/periods/index.cjs +0 -1041
- package/dist/periods/index.cjs.map +0 -1
- package/dist/periods/index.d.cts +0 -22
- package/dist/predicate-Dnu81tsS.d.ts +0 -185
- package/dist/query/index.cjs.map +0 -1
- package/dist/query/index.d.cts +0 -3
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-2IEARCGT.js +0 -7
- package/dist/registry-CDHASH73.js +0 -10
- package/dist/registry-EMGLZGR6.js +0 -8
- package/dist/registry-NQALYR77.js +0 -8
- package/dist/revoke-7JOVLZFD.js +0 -17
- package/dist/session/index.cjs +0 -495
- package/dist/session/index.cjs.map +0 -1
- package/dist/session/index.d.cts +0 -46
- package/dist/shadow/index.cjs +0 -133
- package/dist/shadow/index.cjs.map +0 -1
- package/dist/shadow/index.d.cts +0 -17
- package/dist/store/index.cjs +0 -1083
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -492
- package/dist/strategy-BSxFXGzb.d.cts +0 -110
- package/dist/strategy-DSTrsZ8t.d.cts +0 -601
- package/dist/sync/index.cjs +0 -1062
- package/dist/sync/index.cjs.map +0 -1
- package/dist/sync/index.d.cts +0 -43
- package/dist/team/index.cjs +0 -2798
- package/dist/team/index.cjs.map +0 -1
- package/dist/team/index.d.cts +0 -118
- package/dist/tx/index.cjs +0 -544
- package/dist/tx/index.cjs.map +0 -1
- package/dist/tx/index.d.cts +0 -21
- package/dist/types-CSLcfytP.d.cts +0 -12493
- package/dist/ulid-CG2YvAbg.d.cts +0 -603
- package/dist/util/index.cjs +0 -230
- package/dist/util/index.cjs.map +0 -1
- package/dist/util/index.d.cts +0 -77
- package/dist/with-derivation-Bzpj6UTv.d.ts +0 -13
- package/dist/with-derivation-DWajFh4K.d.cts +0 -13
- package/dist/with-guard-DF_Ul3DT.d.cts +0 -18
- package/dist/with-guard-DR7U-l4v.d.ts +0 -18
- package/dist/with-materialized-view-_piodoIz.d.cts +0 -27
- package/dist/with-overlayed-view-DwzCKxn2.d.cts +0 -13
- /package/dist/{crypto-H2Y3DDFW.js.map → adapter/index.js.map} +0 -0
- /package/dist/{chunk-75QDHSE4.js.map → chunk-2QDWRK3B.js.map} +0 -0
- /package/dist/{chunk-FCDO7UAO.js.map → chunk-2ZYIN3BL.js.map} +0 -0
- /package/dist/{chunk-ZUMGGHRB.js.map → chunk-5PTX7PKD.js.map} +0 -0
- /package/dist/{chunk-QAU5HM6Q.js.map → chunk-6EKRQ7QF.js.map} +0 -0
- /package/dist/{chunk-UVPGJXVO.js.map → chunk-JOJPBZSZ.js.map} +0 -0
- /package/dist/{chunk-YK72A4IT.js.map → chunk-LBILABKZ.js.map} +0 -0
- /package/dist/{chunk-GAUBWHAF.js.map → chunk-OCYFFBZS.js.map} +0 -0
- /package/dist/{delegation-QSC7G5QC.js.map → chunk-PZ5AY32C.js.map} +0 -0
- /package/dist/{chunk-FS7A4XNF.js.map → chunk-QWJCNXH2.js.map} +0 -0
- /package/dist/{chunk-K5PVGKE4.js.map → chunk-SAAJYSVV.js.map} +0 -0
- /package/dist/{chunk-LS3JLEIB.js.map → chunk-TZGWI5AX.js.map} +0 -0
- /package/dist/{executor-BZKFZVRC.js.map → crypto-OSIV2IIW.js.map} +0 -0
- /package/dist/{executor-GFZFDQXV.js.map → delegation-BQTRJATI.js.map} +0 -0
- /package/dist/{executor-KT2IOZVP.js.map → executor-KYSKFJ2R.js.map} +0 -0
- /package/dist/{issue-BAJ7ZB4S.js.map → executor-M7W3NEKR.js.map} +0 -0
- /package/dist/{ledger-WOEJUYTP.js.map → executor-VDTDGWO6.js.map} +0 -0
- /package/dist/{noydb-XNQSKXGO.js.map → forget/index.js.map} +0 -0
- /package/dist/{public-envelope-OHQ5UZFM.js.map → issue-DPHRF2TI.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → kernel/index.js.map} +0 -0
- /package/dist/{registry-2IEARCGT.js.map → ledger-CI7GSMLB.js.map} +0 -0
- /package/dist/{registry-CDHASH73.js.map → noydb-NAU2DTFP.js.map} +0 -0
- /package/dist/{registry-EMGLZGR6.js.map → public-envelope-MFMBFJLZ.js.map} +0 -0
- /package/dist/{registry-NQALYR77.js.map → read-only-facade-3OQRZ3VS.js.map} +0 -0
- /package/dist/{revoke-7JOVLZFD.js.map → registry-IX5WKK4F.js.map} +0 -0
- /package/dist/{signer-M4K5HBLD.js.map → registry-LMHO5265.js.map} +0 -0
- /package/dist/{stale-PAGCS4K5.js.map → registry-TXCKWH26.js.map} +0 -0
- /package/dist/{ulid-COREQ2RQ.js.map → registry-VFEDQSYV.js.map} +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IllegalTransitionError,
|
|
3
|
+
RecordLockedError,
|
|
4
|
+
ValidationError
|
|
5
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
6
|
+
|
|
7
|
+
// src/guards/with-guard.ts
|
|
8
|
+
function withGuard(strategy) {
|
|
9
|
+
if (!strategy.collection || strategy.collection.length === 0) {
|
|
10
|
+
throw new ValidationError("withGuard: collection name is required");
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
__noydb_strategy: "guard",
|
|
14
|
+
spec: strategy
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/guards/immutable-guard.ts
|
|
19
|
+
function recordId(record) {
|
|
20
|
+
const id = record?.id;
|
|
21
|
+
return typeof id === "string" ? id : "";
|
|
22
|
+
}
|
|
23
|
+
function immutableGuard(config) {
|
|
24
|
+
const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config;
|
|
25
|
+
if (appendOnly && after !== void 0) {
|
|
26
|
+
throw new ValidationError("immutableGuard: `after` and `appendOnly` are mutually exclusive");
|
|
27
|
+
}
|
|
28
|
+
if (!appendOnly && after === void 0) {
|
|
29
|
+
throw new ValidationError("immutableGuard: provide `after` or `appendOnly: true`");
|
|
30
|
+
}
|
|
31
|
+
const isImmutable = appendOnly ? () => true : after;
|
|
32
|
+
const reason = appendOnly ? "append-only collection" : "record is immutable after issue";
|
|
33
|
+
const spec = {
|
|
34
|
+
collection,
|
|
35
|
+
// Block updates to an already-immutable record. Inserts (existing
|
|
36
|
+
// null) and the transition write that first makes the record
|
|
37
|
+
// immutable are allowed — `after` reads the prior state.
|
|
38
|
+
check: (incoming, ctx) => {
|
|
39
|
+
if (ctx.existing !== null && isImmutable(ctx.existing)) {
|
|
40
|
+
throw new RecordLockedError(collection, recordId(incoming), reason);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
// Block deletes of an immutable record.
|
|
44
|
+
onDelete: (existing) => {
|
|
45
|
+
if (isImmutable(existing)) {
|
|
46
|
+
throw new RecordLockedError(collection, recordId(existing), reason);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
// The authorized override: inside an amendment transaction the
|
|
50
|
+
// check/onDelete are skipped and the change is ledgered. By default
|
|
51
|
+
// there is no extra invariant — the amendment itself is the
|
|
52
|
+
// sanctioned exception. Callers may supply `amendmentInvariant` to
|
|
53
|
+
// keep a constraint inviolable even under amendment (e.g. forbid
|
|
54
|
+
// deletes, or preserve a cross-record sum); a throw reverts the
|
|
55
|
+
// amendment and surfaces as `InvariantError`.
|
|
56
|
+
amendment: {
|
|
57
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
58
|
+
invariant: amendmentInvariant ?? (() => {
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return withGuard(spec);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/guards/transition-guard.ts
|
|
66
|
+
function recordId2(record) {
|
|
67
|
+
const id = record?.id;
|
|
68
|
+
return typeof id === "string" ? id : "";
|
|
69
|
+
}
|
|
70
|
+
function stateOf(record, field) {
|
|
71
|
+
const v = record[field];
|
|
72
|
+
return typeof v === "string" ? v : String(v);
|
|
73
|
+
}
|
|
74
|
+
function transitionGuard(config) {
|
|
75
|
+
const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config;
|
|
76
|
+
const allowIdempotent = config.allowIdempotent ?? true;
|
|
77
|
+
if (!field) {
|
|
78
|
+
throw new ValidationError("transitionGuard: `field` is required");
|
|
79
|
+
}
|
|
80
|
+
if (transitions === void 0 || typeof transitions !== "object") {
|
|
81
|
+
throw new ValidationError("transitionGuard: `transitions` must be a state\u2192states map");
|
|
82
|
+
}
|
|
83
|
+
const spec = {
|
|
84
|
+
collection,
|
|
85
|
+
check: (incoming, ctx) => {
|
|
86
|
+
const rec = incoming;
|
|
87
|
+
const to = stateOf(rec, field);
|
|
88
|
+
if (ctx.existing === null) {
|
|
89
|
+
if (initial !== void 0 && !initial.includes(to)) {
|
|
90
|
+
throw new IllegalTransitionError(collection, recordId2(rec), "(none)", to);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const from = stateOf(ctx.existing, field);
|
|
95
|
+
if (from === to) {
|
|
96
|
+
if (allowIdempotent) return;
|
|
97
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
98
|
+
}
|
|
99
|
+
const allowed = transitions[from] ?? [];
|
|
100
|
+
if (!allowed.includes(to)) {
|
|
101
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
// The authorized override: inside an amendment transaction the check
|
|
105
|
+
// is skipped and the change is ledgered. By default no extra invariant
|
|
106
|
+
// — the amendment itself is the sanctioned exception. Callers may
|
|
107
|
+
// supply `amendmentInvariant` to keep a constraint inviolable even
|
|
108
|
+
// under amendment; a throw reverts the amendment as `InvariantError`.
|
|
109
|
+
amendment: {
|
|
110
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
111
|
+
invariant: amendmentInvariant ?? (() => {
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return withGuard(spec);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
withGuard,
|
|
120
|
+
immutableGuard,
|
|
121
|
+
transitionGuard
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-XAYDHD2O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards/with-guard.ts","../src/guards/immutable-guard.ts","../src/guards/transition-guard.ts"],"sourcesContent":["import { ValidationError } from '../errors.js'\nimport type { GuardStrategy, GuardStrategyHandle } from './types.js'\n\n/**\n * Register a guard for a collection. Guards run on every `put()` /\n * `delete()` for the named collection (after permissions, before\n * encryption) and may:\n *\n * - `check` — block writes by throwing (typically `RecordLockedError`)\n * - `frozenFields` — freeze specific fields once a condition is true\n * - `amendment` — declare an authorized-override path with invariant\n *\n * Pass the returned handle to `createNoydb({ strategies: [...] })`.\n *\n * @see docs/superpowers/specs/2026-05-18-guards-design.md\n */\nexport function withGuard<T extends Record<string, unknown>>(\n strategy: GuardStrategy<T>,\n): GuardStrategyHandle<T> {\n if (!strategy.collection || strategy.collection.length === 0) {\n throw new ValidationError('withGuard: collection name is required')\n }\n return {\n __noydb_strategy: 'guard',\n spec: strategy,\n }\n}\n","/**\n * `immutableGuard` — declarative WORM / append-only sugar over the guard\n * subsystem.\n *\n * Issued fiscal documents (invoices, DDTs) must be immutable after issue.\n * That is expressible today with a hand-rolled `withGuard` (block on\n * `check`/`onDelete`, allow an admin `amendment`), but the boilerplate is\n * repetitive and easy to get subtly wrong. `immutableGuard` generates\n * exactly that guard from a declarative config, reusing the guard\n * machinery wholesale — `check`/`onDelete` rejection, the ledgered\n * `amendment` override, and composition with `periods`/`history`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * immutableGuard({\n * collection: 'invoices',\n * after: (r) => r.status === 'issued', // immutable once issued\n * }),\n * ] })\n * ```\n *\n * A record is mutable until `after(record)` holds; from then on, updates\n * and deletes throw `RecordLockedError` unless performed inside an\n * `amendment` transaction by an authorized role (the override is\n * ledgered by the guard amendment mechanism). `appendOnly: true` is\n * shorthand for `after: () => true` — immutable from creation.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { RecordLockedError, ValidationError } from '../errors.js'\n\nexport interface ImmutableGuardConfig<T extends Record<string, unknown>> {\n /** The collection to make WORM. */\n collection: string\n /**\n * A record becomes immutable once this predicate holds. Evaluated on\n * the *existing* (already-persisted) record, so the write that first\n * makes it true is still allowed; subsequent writes are blocked.\n * Mutually exclusive with `appendOnly`.\n */\n after?: (record: T) => boolean\n /** Shorthand for `after: () => true` — immutable from creation. */\n appendOnly?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly: it receives every {before, after} pair touching this\n * collection in the amendment plus the guard context; throwing reverts\n * the whole amendment and surfaces as `InvariantError`.\n *\n * Use this to keep a constraint inviolable EVEN under amendment — e.g.\n * forbid deleting an issued document by re-throwing on any\n * `before !== null && after === null` change, or assert a cross-record\n * sum is preserved. When omitted the amendment is unconditionally\n * allowed (the amendment itself is the sanctioned, ledgered override) —\n * this is the backward-compatible default.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\n/**\n * Build an immutability guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function immutableGuard<T extends Record<string, unknown>>(\n config: ImmutableGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config\n if (appendOnly && after !== undefined) {\n throw new ValidationError('immutableGuard: `after` and `appendOnly` are mutually exclusive')\n }\n if (!appendOnly && after === undefined) {\n throw new ValidationError('immutableGuard: provide `after` or `appendOnly: true`')\n }\n\n const isImmutable: (record: T) => boolean = appendOnly ? () => true : after!\n const reason = appendOnly ? 'append-only collection' : 'record is immutable after issue'\n\n const spec: GuardStrategy<T> = {\n collection,\n // Block updates to an already-immutable record. Inserts (existing\n // null) and the transition write that first makes the record\n // immutable are allowed — `after` reads the prior state.\n check: (incoming: T, ctx: GuardContext<T>) => {\n if (ctx.existing !== null && isImmutable(ctx.existing)) {\n throw new RecordLockedError(collection, recordId(incoming as Record<string, unknown>), reason)\n }\n },\n // Block deletes of an immutable record.\n onDelete: (existing: T) => {\n if (isImmutable(existing)) {\n throw new RecordLockedError(collection, recordId(existing as Record<string, unknown>), reason)\n }\n },\n // The authorized override: inside an amendment transaction the\n // check/onDelete are skipped and the change is ledgered. By default\n // there is no extra invariant — the amendment itself is the\n // sanctioned exception. Callers may supply `amendmentInvariant` to\n // keep a constraint inviolable even under amendment (e.g. forbid\n // deletes, or preserve a cross-record sum); a throw reverts the\n // amendment and surfaces as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n","/**\n * `transitionGuard` — declarative state-machine sugar over the guard\n * subsystem.\n *\n * Any record with a lifecycle field (invoice `status`, order state,\n * ticket workflow, subscription phase) needs transition validation: a\n * write may only move the field along a declared arc. That is expressible\n * with a hand-rolled `withGuard({ check })`, but every consumer\n * re-implements the same graph lookup + error. `transitionGuard`\n * generates exactly that guard from a state graph, reusing the guard\n * machinery wholesale — `check` rejection, the ledgered `amendment`\n * override, and composition with `periods`/`history`.\n *\n * It generalizes {@link immutableGuard}: WORM is the special case \"every\n * state has no outgoing arcs\", i.e. `transitions` mapping each state to `[]`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * transitionGuard<Sale>({\n * collection: 'sales', field: 'status',\n * transitions: { // absence of an arc = forbidden\n * draft: ['to_verify', 'cancelled'],\n * to_verify: ['proforma', 'draft', 'cancelled'],\n * proforma: ['invoiced', 'cancelled'],\n * invoiced: ['paid'], paid: [], cancelled: [],\n * },\n * initial: ['draft', 'to_verify'], // allowed status on insert\n * }),\n * ] })\n * ```\n *\n * Semantics:\n * - **Insert** (`ctx.existing === null`): `incoming[field]` must be in\n * `initial`. When `initial` is omitted, any value is allowed on insert.\n * - **Update**: the arc `(existing[field] → incoming[field])` must be\n * listed in `transitions[from]`, else `IllegalTransitionError`. A\n * same-value write (`from === to`) is allowed when `allowIdempotent`\n * (default `true`) — so writes that touch other fields without moving\n * state pass.\n * - **Override**: inside an `amendment` transaction by an authorized role\n * the check is skipped and the change is ledgered (mirrors every guard).\n *\n * The status graph is caller-supplied data — no UI, no domain logic.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { IllegalTransitionError, ValidationError } from '../errors.js'\n\nexport interface TransitionGuardConfig<T extends Record<string, unknown>> {\n /** The collection whose state field is governed. */\n collection: string\n /** The state field on the record (e.g. `'status'`). */\n field: keyof T & string\n /**\n * The transition graph: each state maps to the states it may move to.\n * A state absent from the map (or mapped to `[]`) is terminal — no\n * outgoing arc, so any non-idempotent write from it is rejected.\n */\n transitions: Readonly<Record<string, readonly string[]>>\n /**\n * States allowed as the initial value on insert (`existing === null`).\n * Omit to allow any value on insert.\n */\n initial?: readonly string[]\n /**\n * Allow a same-value write (`from === to`) on update. Default `true` —\n * lets a put that changes other fields, but not the state, through.\n */\n allowIdempotent?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly. When omitted the amendment is unconditionally allowed (the\n * amendment itself is the sanctioned, ledgered override) — the\n * backward-compatible default. Mirrors {@link immutableGuard}.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\nfunction stateOf(record: Record<string, unknown>, field: string): string {\n const v = record[field]\n return typeof v === 'string' ? v : String(v)\n}\n\n/**\n * Build a state-machine transition guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function transitionGuard<T extends Record<string, unknown>>(\n config: TransitionGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config\n const allowIdempotent = config.allowIdempotent ?? true\n\n if (!field) {\n throw new ValidationError('transitionGuard: `field` is required')\n }\n if (transitions === undefined || typeof transitions !== 'object') {\n throw new ValidationError('transitionGuard: `transitions` must be a state→states map')\n }\n\n const spec: GuardStrategy<T> = {\n collection,\n check: (incoming: T, ctx: GuardContext<T>) => {\n const rec = incoming as Record<string, unknown>\n const to = stateOf(rec, field)\n\n // Insert — gate on the allowed initial set (any value if unset).\n if (ctx.existing === null) {\n if (initial !== undefined && !initial.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), '(none)', to)\n }\n return\n }\n\n // Update — the arc (from → to) must be a declared edge.\n const from = stateOf(ctx.existing as Record<string, unknown>, field)\n if (from === to) {\n if (allowIdempotent) return\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n const allowed = transitions[from] ?? []\n if (!allowed.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n },\n // The authorized override: inside an amendment transaction the check\n // is skipped and the change is ledgered. By default no extra invariant\n // — the amendment itself is the sanctioned exception. Callers may\n // supply `amendmentInvariant` to keep a constraint inviolable even\n // under amendment; a throw reverts the amendment as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n"],"mappings":"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","names":["recordId"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_FORMAT_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-A7DNZVAV.js";
|
|
4
4
|
import {
|
|
5
5
|
encrypt
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-E2MDPPOC.js";
|
|
7
7
|
|
|
8
8
|
// src/blobs/export-blobs.ts
|
|
9
9
|
var ExportBlobsAbortedError = class extends Error {
|
|
@@ -118,6 +118,7 @@ async function runCompaction(ctx, options = {}) {
|
|
|
118
118
|
let evicted = 0;
|
|
119
119
|
let records = 0;
|
|
120
120
|
let auditEntries = 0;
|
|
121
|
+
let held = 0;
|
|
121
122
|
let collectionsWithPolicy = 0;
|
|
122
123
|
outer: for (const collectionName of allCollections) {
|
|
123
124
|
if (collectionName.startsWith("_")) continue;
|
|
@@ -141,6 +142,10 @@ async function runCompaction(ctx, options = {}) {
|
|
|
141
142
|
if (!policy) continue;
|
|
142
143
|
const reason = evaluatePolicy(policy, record, slot, now);
|
|
143
144
|
if (!reason) continue;
|
|
145
|
+
if (isHeld(policy, record, now)) {
|
|
146
|
+
held += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
144
149
|
if (!dryRun) {
|
|
145
150
|
await ctx.deleteSlot(collectionName, recordId, slot.name);
|
|
146
151
|
await writeAuditEntry(ctx, {
|
|
@@ -165,9 +170,32 @@ async function runCompaction(ctx, options = {}) {
|
|
|
165
170
|
records,
|
|
166
171
|
collections: collectionsWithPolicy,
|
|
167
172
|
auditEntries,
|
|
173
|
+
held,
|
|
168
174
|
byCollection
|
|
169
175
|
};
|
|
170
176
|
}
|
|
177
|
+
function isHeld(policy, record, now) {
|
|
178
|
+
if (policy.legalHold) {
|
|
179
|
+
try {
|
|
180
|
+
if (policy.legalHold(record)) return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (policy.retainUntil) {
|
|
186
|
+
try {
|
|
187
|
+
const until = policy.retainUntil(record);
|
|
188
|
+
if (until !== null && until !== void 0) {
|
|
189
|
+
const t = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Date.parse(String(until));
|
|
190
|
+
if (!Number.isFinite(t)) return true;
|
|
191
|
+
if (t > now.getTime()) return true;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
171
199
|
function evaluatePolicy(policy, record, slot, now) {
|
|
172
200
|
let ttlTriggered = false;
|
|
173
201
|
let predicateTriggered = false;
|
|
@@ -230,4 +258,4 @@ export {
|
|
|
230
258
|
BLOB_EVICTION_AUDIT_COLLECTION,
|
|
231
259
|
runCompaction
|
|
232
260
|
};
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
261
|
+
//# sourceMappingURL=chunk-XYW2CSCI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blobs/export-blobs.ts","../src/blobs/blob-compaction.ts"],"sourcesContent":["/**\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 '../collection.js'\nimport type { SlotInfo } from '../types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt } from '../crypto.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\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 readonly reason: 'ttl' | 'predicate' | 'both'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted 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 /** Number of audit entries written. Equal to `evicted`. */\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 /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n}\n\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<CryptoKey>\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\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n\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 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 // 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 return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n byCollection,\n }\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 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)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_FORMAT_VERSION,
|
|
3
3
|
NOYDB_KEYRING_VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-A7DNZVAV.js";
|
|
5
5
|
import {
|
|
6
6
|
base64ToBuffer,
|
|
7
7
|
bufferToBase64,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
generateSalt,
|
|
13
13
|
unwrapKey,
|
|
14
14
|
wrapKey
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-E2MDPPOC.js";
|
|
16
16
|
import {
|
|
17
17
|
ConflictError,
|
|
18
18
|
DirectoryDisabledError,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
PermissionDeniedError,
|
|
25
25
|
PrivilegeEscalationError,
|
|
26
26
|
ValidationError
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
28
28
|
|
|
29
29
|
// src/directory/storage.ts
|
|
30
30
|
var META_COLLECTION = "_meta";
|
|
@@ -238,12 +238,14 @@ async function listUserEnvelopeIds(store, vault) {
|
|
|
238
238
|
var ADMIN_GRANTABLE_TARGETS = ["operator", "viewer", "client", "admin"];
|
|
239
239
|
function canGrant(callerRole, targetRole) {
|
|
240
240
|
if (callerRole === "owner") return true;
|
|
241
|
+
if (callerRole === "custodian") return false;
|
|
241
242
|
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
242
243
|
return false;
|
|
243
244
|
}
|
|
244
245
|
function canRevoke(callerRole, targetRole) {
|
|
245
246
|
if (targetRole === "owner") return false;
|
|
246
247
|
if (callerRole === "owner") return true;
|
|
248
|
+
if (callerRole === "custodian") return false;
|
|
247
249
|
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
248
250
|
return false;
|
|
249
251
|
}
|
|
@@ -340,6 +342,18 @@ async function loadKeyring(adapter, vault, userId, passphrase) {
|
|
|
340
342
|
...keyringFile.policy !== void 0 && { policy: keyringFile.policy }
|
|
341
343
|
};
|
|
342
344
|
}
|
|
345
|
+
async function assertKeyringOpenAllowed(store, vault, userId, create) {
|
|
346
|
+
const keyringUsers = await store.list(vault, "_keyring");
|
|
347
|
+
if (keyringUsers.includes(userId)) return;
|
|
348
|
+
if (!create) {
|
|
349
|
+
throw new NoAccessError(`Vault "${vault}" not opened: create disabled and no keyring for "${userId}".`);
|
|
350
|
+
}
|
|
351
|
+
if (keyringUsers.length > 0) {
|
|
352
|
+
throw new NoAccessError(
|
|
353
|
+
`No keyring for user "${userId}" in vault "${vault}" (held by other principals) \u2014 refusing to self-provision.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
343
357
|
async function createOwnerKeyring(adapter, vault, userId, passphrase, passphraseOpts) {
|
|
344
358
|
if (passphraseOpts?.validate && !passphraseOpts.allowWeakPassphrase) {
|
|
345
359
|
assertStrongPassphrase(passphrase, passphraseOpts);
|
|
@@ -397,7 +411,7 @@ async function grant(adapter, vault, callerKeyring, options) {
|
|
|
397
411
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
398
412
|
}
|
|
399
413
|
}
|
|
400
|
-
if (options.role === "owner" || options.role === "admin" || options.role === "viewer") {
|
|
414
|
+
if (options.role === "owner" || options.role === "admin" || options.role === "custodian" || options.role === "viewer") {
|
|
401
415
|
for (const [collName, dek] of callerKeyring.deks) {
|
|
402
416
|
if (!(collName in wrappedDeks)) {
|
|
403
417
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
@@ -545,6 +559,11 @@ async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
|
|
|
545
559
|
await writeKeyringFile(adapter, vault, options.userId, next);
|
|
546
560
|
}
|
|
547
561
|
async function rotateKeys(adapter, vault, callerKeyring, collections) {
|
|
562
|
+
if (callerKeyring.role === "custodian") {
|
|
563
|
+
throw new PermissionDeniedError(
|
|
564
|
+
"custodian cannot rotate keys (FR-6: re-key is an owner-only meta-capability; use the Deed owner)"
|
|
565
|
+
);
|
|
566
|
+
}
|
|
548
567
|
const newDeks = /* @__PURE__ */ new Map();
|
|
549
568
|
for (const collName of collections) {
|
|
550
569
|
newDeks.set(collName, await generateDEK());
|
|
@@ -631,7 +650,7 @@ async function changeSecret(adapter, vault, keyring, newPassphrase, passphraseOp
|
|
|
631
650
|
// Tier-2 slots are NOT preserved through `changeSecret` —
|
|
632
651
|
// each slot wraps the OLD KEK, so the new keyring has no
|
|
633
652
|
// authenticator slots until the user re-enrolls. The higher-level
|
|
634
|
-
// `db.rotatePassphrase()`
|
|
653
|
+
// `db.rotatePassphrase()` preserves slots by rewrapping the
|
|
635
654
|
// KEK reference, not the KEK itself.
|
|
636
655
|
authenticators: [],
|
|
637
656
|
...keyring.policy !== void 0 && { policy: keyring.policy }
|
|
@@ -654,7 +673,7 @@ async function buildRecipientKeyringFile(callerKeyring, recipient) {
|
|
|
654
673
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
655
674
|
}
|
|
656
675
|
}
|
|
657
|
-
if (role === "owner" || role === "admin" || role === "viewer") {
|
|
676
|
+
if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") {
|
|
658
677
|
for (const [collName, dek] of callerKeyring.deks) {
|
|
659
678
|
if (!(collName in wrappedDeks)) {
|
|
660
679
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
@@ -756,12 +775,13 @@ async function ensureCollectionDEK(adapter, vault, keyring) {
|
|
|
756
775
|
};
|
|
757
776
|
}
|
|
758
777
|
function hasWritePermission(keyring, collectionName) {
|
|
759
|
-
if (keyring.role === "owner" || keyring.role === "admin") return true;
|
|
778
|
+
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return true;
|
|
760
779
|
if (keyring.role === "viewer" || keyring.role === "client") return false;
|
|
761
780
|
return keyring.permissions[collectionName] === "rw";
|
|
762
781
|
}
|
|
763
782
|
function hasAccess(keyring, collectionName) {
|
|
764
|
-
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "viewer")
|
|
783
|
+
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian" || keyring.role === "viewer")
|
|
784
|
+
return true;
|
|
765
785
|
return collectionName in keyring.permissions;
|
|
766
786
|
}
|
|
767
787
|
async function persistKeyring(adapter, vault, keyring) {
|
|
@@ -827,7 +847,7 @@ function evaluateImportCapability(capability, _role, tier, format) {
|
|
|
827
847
|
return capability?.bundle === true;
|
|
828
848
|
}
|
|
829
849
|
function resolvePermissions(role, explicit) {
|
|
830
|
-
if (role === "owner" || role === "admin" || role === "viewer") return {};
|
|
850
|
+
if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") return {};
|
|
831
851
|
return explicit ?? {};
|
|
832
852
|
}
|
|
833
853
|
async function writeKeyringFile(adapter, vault, userId, keyringFile) {
|
|
@@ -863,6 +883,7 @@ export {
|
|
|
863
883
|
listUserEnvelopeIds,
|
|
864
884
|
mintKeyringCanary,
|
|
865
885
|
loadKeyring,
|
|
886
|
+
assertKeyringOpenAllowed,
|
|
866
887
|
createOwnerKeyring,
|
|
867
888
|
grant,
|
|
868
889
|
revoke,
|
|
@@ -881,4 +902,4 @@ export {
|
|
|
881
902
|
hasImportCapability,
|
|
882
903
|
evaluateImportCapability
|
|
883
904
|
};
|
|
884
|
-
//# sourceMappingURL=chunk-
|
|
905
|
+
//# sourceMappingURL=chunk-ZCMOS4H2.js.map
|