@noy-db/hub 0.2.0-pre.23 → 0.2.0-pre.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +3 -3
- package/dist/aggregate/index.d.ts +3 -3
- package/dist/aggregate/index.js +5 -5
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +4 -4
- package/dist/attestation/index.d.ts +4 -4
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +6 -6
- package/dist/blobs/index.d.ts +6 -6
- package/dist/blobs/index.js +6 -6
- package/dist/bundle/index.cjs +421 -1209
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +15 -6
- package/dist/bundle/index.d.ts +15 -6
- package/dist/bundle/index.js +42 -193
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-SQOK5UM6.js → chunk-2KA3PDUR.js} +2 -2
- package/dist/{chunk-HYJMAV53.js → chunk-2RHBFCWQ.js} +93 -93
- package/dist/chunk-2RHBFCWQ.js.map +1 -0
- package/dist/{chunk-U2XSUCDF.js → chunk-3BANVNDH.js} +2 -2
- package/dist/{chunk-P65YMN5V.js → chunk-56ENKU46.js} +397 -165
- package/dist/chunk-56ENKU46.js.map +1 -0
- package/dist/{chunk-37VGJM3T.js → chunk-7JSP3E67.js} +2 -2
- package/dist/{chunk-F5ILTHMU.js → chunk-ANLOD6IS.js} +5 -5
- package/dist/{chunk-JYNH4FIM.js → chunk-C7UIT5XY.js} +4 -4
- package/dist/{chunk-OTWT6BAJ.js → chunk-DDOYOMAD.js} +2 -2
- package/dist/chunk-DDOYOMAD.js.map +1 -0
- package/dist/{chunk-TGIJTNM3.js → chunk-E5TJAQS7.js} +2 -2
- package/dist/{chunk-IY24WS2P.js → chunk-EJJTUDNI.js} +4 -4
- package/dist/{chunk-IY24WS2P.js.map → chunk-EJJTUDNI.js.map} +1 -1
- package/dist/{chunk-GJTKMME7.js → chunk-EW3H5Y7N.js} +2 -2
- package/dist/{chunk-JDCPRJVS.js → chunk-EYZJULEN.js} +4 -4
- package/dist/{chunk-I3IYTUUI.js → chunk-FCIZXX56.js} +3 -3
- package/dist/{chunk-C2RJVZZL.js → chunk-FJ3C3ELF.js} +2 -2
- package/dist/{chunk-ZONKSLF2.js → chunk-FO5WEDKF.js} +2 -2
- package/dist/{chunk-SQKAECUL.js → chunk-FUDVHE2U.js} +2 -2
- package/dist/{chunk-IVZWHIEK.js → chunk-GHXOVGTX.js} +5 -5
- package/dist/{chunk-UU6M64HI.js → chunk-GPZHHTJU.js} +4 -4
- package/dist/{chunk-3HNKR65T.js → chunk-H4XFA2LM.js} +3 -3
- package/dist/{chunk-JOK73NDT.js → chunk-HUXDQIVU.js} +3 -3
- package/dist/{chunk-F5GWNSE2.js → chunk-J73KU4AE.js} +3 -3
- package/dist/{chunk-F5GWNSE2.js.map → chunk-J73KU4AE.js.map} +1 -1
- package/dist/{chunk-O5XKZCUD.js → chunk-JJKXJAH2.js} +5 -5
- package/dist/{chunk-TNH5SLCD.js → chunk-KD253AI5.js} +2 -2
- package/dist/{chunk-WWVJXBOT.js → chunk-KJ37E3R5.js} +5 -5
- package/dist/{chunk-S45MDEEF.js → chunk-KNJ7MK4B.js} +2 -2
- package/dist/{chunk-TA6HPKWQ.js → chunk-LR7CODVN.js} +1 -1
- package/dist/chunk-LR7CODVN.js.map +1 -0
- package/dist/{chunk-J6RGRZOY.js → chunk-LX4CPLU6.js} +2 -2
- package/dist/{chunk-WE2BUQD2.js → chunk-N4EXCKWP.js} +3 -3
- package/dist/{chunk-EYK72OTL.js → chunk-OCRDV3NU.js} +5 -5
- package/dist/chunk-OCRDV3NU.js.map +1 -0
- package/dist/{chunk-JBBWALNI.js → chunk-OMBPGXCL.js} +2 -2
- package/dist/{chunk-NV4IHBZS.js → chunk-PS6PSEZL.js} +5 -5
- package/dist/{chunk-6QE4DUYC.js → chunk-Q7P4WHTL.js} +2 -2
- package/dist/{chunk-TAMRU7A2.js → chunk-QYQRAOEF.js} +4 -4
- package/dist/{chunk-6QAZ5O6X.js → chunk-RHVYFAVQ.js} +2 -2
- package/dist/chunk-RZOGD7IF.js +232 -0
- package/dist/chunk-RZOGD7IF.js.map +1 -0
- package/dist/{chunk-YPIOFSN3.js → chunk-SKYBEGHB.js} +2 -2
- package/dist/{chunk-7MRT7EPB.js → chunk-TESFHBOW.js} +3 -3
- package/dist/{chunk-CQYEDODS.js → chunk-TSUICI5N.js} +3 -3
- package/dist/{chunk-FRRJIUSI.js → chunk-UNBX2HMA.js} +17 -9
- package/dist/chunk-UNBX2HMA.js.map +1 -0
- package/dist/{chunk-TYMDCIQM.js → chunk-VGAN5RLD.js} +4 -4
- package/dist/{chunk-5YTXYPES.js → chunk-VJNV2GRF.js} +5 -5
- package/dist/{chunk-NSXNXLYM.js → chunk-VUUQYWF5.js} +2 -2
- package/dist/{chunk-IW4L4X65.js → chunk-WVYL6HM7.js} +2 -2
- package/dist/{chunk-BZW5IL43.js → chunk-Y5CTT6K5.js} +4 -4
- package/dist/{chunk-C6W5KVDV.js → chunk-YP2AYE5W.js} +35 -35
- package/dist/chunk-YP2AYE5W.js.map +1 -0
- package/dist/{chunk-KOAJ3TZM.js → chunk-YRQPI67X.js} +2 -2
- package/dist/{chunk-MBXKRHSS.js → chunk-YYTM4U4J.js} +2 -2
- package/dist/{chunk-2XA2ZML4.js → chunk-ZCBJIDT4.js} +3 -3
- package/dist/{chunk-AI4USDRI.js → chunk-ZW2YSN6G.js} +4 -4
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +5 -5
- package/dist/consent/index.d.ts +5 -5
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-456N7UVX.js → crypto-YBKBNPVM.js} +3 -3
- package/dist/{ulid-Dwt3JEcy.d.ts → decrypt-partition-C71vhnND.d.cts} +19 -64
- package/dist/{ulid-Bg-IBJyA.d.cts → decrypt-partition-CyyJUWLR.d.ts} +19 -64
- package/dist/{delegation-DP4COTXB.js → delegation-4JSMM6BB.js} +5 -5
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +6 -6
- package/dist/derivations/index.d.ts +6 -6
- package/dist/derivations/index.js +4 -4
- package/dist/{dev-unlock-Bw7iBD1D.d.cts → dev-unlock-BdrE0kbS.d.cts} +1 -1
- package/dist/{dev-unlock-DzDzLTdZ.d.ts → dev-unlock-ByBkl99-.d.ts} +1 -1
- package/dist/{errors-Dkc_fi-S.d.cts → errors-Dwk2k1xY.d.cts} +14 -5
- package/dist/{errors-Dkc_fi-S.d.ts → errors-Dwk2k1xY.d.ts} +14 -5
- package/dist/executor-3SVNESQ3.js +8 -0
- package/dist/executor-BIW4FT5R.js +12 -0
- package/dist/executor-VEZUBJNQ.js +8 -0
- package/dist/{fanout-sidecar-YXNAEZ33.js → fanout-sidecar-ZQT4Y7PF.js} +2 -2
- package/dist/forget/index.js +4 -4
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +6 -6
- package/dist/guards/index.d.ts +6 -6
- package/dist/guards/index.js +6 -6
- package/dist/{hash-C52X_-m5.d.cts → hash-BUkDp_8Q.d.cts} +1 -1
- package/dist/{hash-DepR-xVc.d.ts → hash-CZxVv8RH.d.ts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +6 -6
- package/dist/history/index.d.ts +6 -6
- package/dist/history/index.js +5 -5
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +5 -5
- package/dist/i18n/index.d.ts +5 -5
- package/dist/i18n/index.js +6 -6
- package/dist/index-CBUhOmrM.d.cts +70 -0
- package/dist/index-DFhKV-6A.d.ts +70 -0
- package/dist/{index-tZqVB9g5.d.cts → index-DoxKSsMj.d.cts} +2 -2
- package/dist/{index-Bm9hIY7t.d.ts → index-LaexBi3v.d.ts} +2 -2
- package/dist/index.cjs +25660 -25495
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -80
- package/dist/index.d.ts +135 -80
- package/dist/index.js +70 -51
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.js +4 -4
- package/dist/issue-LEBPVF3Y.js +12 -0
- package/dist/kernel/index.cjs +657 -0
- package/dist/kernel/index.cjs.map +1 -0
- package/dist/kernel/index.d.cts +11 -0
- package/dist/kernel/index.d.ts +11 -0
- package/dist/kernel/index.js +40 -0
- package/dist/{ledger-I7JUYP4L.js → ledger-FLRTSOYH.js} +5 -5
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +6 -6
- package/dist/materialized-views/index.d.ts +6 -6
- package/dist/materialized-views/index.js +8 -8
- package/dist/{mime-magic-Cxf9B_Dm.d.cts → mime-magic-BAhLjkHw.d.cts} +1 -1
- package/dist/{mime-magic-Dejetix_.d.ts → mime-magic-C1UbcBxP.d.ts} +1 -1
- package/dist/noydb-6FA46A4M.js +38 -0
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +6 -6
- package/dist/overlay-views/index.d.ts +6 -6
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +5 -5
- package/dist/periods/index.d.ts +5 -5
- package/dist/periods/index.js +5 -5
- package/dist/{public-envelope-5XRTUNKF.js → public-envelope-DBKJEBBF.js} +4 -4
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +7 -7
- package/dist/registry-CMEVTOCN.js +8 -0
- package/dist/{registry-NWHOLD5M.js → registry-OUZ3VBZA.js} +3 -3
- package/dist/registry-XUBRO5JJ.js +8 -0
- package/dist/{revoke-5IEK22KT.js → revoke-P5D3UTRX.js} +6 -6
- package/dist/sealed-record/index.cjs.map +1 -1
- package/dist/sealed-record/index.d.cts +1 -1
- package/dist/sealed-record/index.d.ts +1 -1
- package/dist/sealed-record/index.js +2 -2
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +6 -6
- package/dist/session/index.d.ts +6 -6
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +5 -5
- package/dist/shadow/index.d.ts +5 -5
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-I6YARZQA.js → signer-NEQPCHMW.js} +5 -5
- package/dist/snapshots/index.cjs.map +1 -1
- package/dist/snapshots/index.d.cts +5 -5
- package/dist/snapshots/index.d.ts +5 -5
- package/dist/snapshots/index.js +4 -4
- package/dist/{stale-CPESGAPL.js → stale-KKCHF2VB.js} +2 -2
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +5 -5
- package/dist/store/index.d.ts +5 -5
- package/dist/store/index.js +2 -2
- package/dist/{strategy-WtB-jXYv.d.cts → strategy-D1zjEV3n.d.cts} +1 -1
- package/dist/{strategy-54eIwox5.d.ts → strategy-YQ1qJWyq.d.ts} +1 -1
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +4 -4
- package/dist/sync/index.d.ts +4 -4
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +10 -3
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +5 -5
- package/dist/team/index.d.ts +5 -5
- package/dist/team/index.js +8 -8
- package/dist/{transition-guard-Ctxapq1b.d.ts → transition-guard-BSLdikC_.d.ts} +1 -1
- package/dist/{transition-guard-BcLyTGYq.d.cts → transition-guard-DPs6al8h.d.cts} +1 -1
- package/dist/tx/index.cjs +1 -1
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +5 -5
- package/dist/tx/index.d.ts +5 -5
- package/dist/tx/index.js +3 -3
- package/dist/{types-Bhs2i_Ll.d.cts → types-BCYvhKzr.d.cts} +282 -578
- package/dist/{types-DONgts0n.d.ts → types-CCq0WHh9.d.ts} +282 -578
- package/dist/ulid-DRH25k3y.d.cts +66 -0
- package/dist/ulid-DRH25k3y.d.ts +66 -0
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/{with-materialized-view-BYb3p9wT.d.cts → with-materialized-view-CTHe6uh9.d.cts} +1 -1
- package/dist/{with-materialized-view-CyVLOr09.d.ts → with-materialized-view-DiD41wQp.d.ts} +1 -1
- package/dist/{with-overlayed-view-BhLRxqwI.d.ts → with-overlayed-view-DlbsJMhF.d.ts} +1 -1
- package/dist/{with-overlayed-view-LGrQ984e.d.cts → with-overlayed-view-Dlz5hcM8.d.cts} +1 -1
- package/dist/{with-rollup-Bj8c7ttB.d.cts → with-rollup-BBWdrCvu.d.cts} +1 -1
- package/dist/{with-rollup-CO8ibRcK.d.ts → with-rollup-mT4_CWaU.d.ts} +1 -1
- package/package.json +13 -3
- package/dist/chunk-C6W5KVDV.js.map +0 -1
- package/dist/chunk-EYK72OTL.js.map +0 -1
- package/dist/chunk-FRRJIUSI.js.map +0 -1
- package/dist/chunk-HYJMAV53.js.map +0 -1
- package/dist/chunk-JTI57WRT.js +0 -164
- package/dist/chunk-JTI57WRT.js.map +0 -1
- package/dist/chunk-OTWT6BAJ.js.map +0 -1
- package/dist/chunk-P65YMN5V.js.map +0 -1
- package/dist/chunk-TA6HPKWQ.js.map +0 -1
- package/dist/chunk-ZC7J6ZYV.js +0 -7
- package/dist/chunk-ZC7J6ZYV.js.map +0 -1
- package/dist/executor-4IEW4KG5.js +0 -8
- package/dist/executor-KYJCJCIN.js +0 -12
- package/dist/executor-W7VIBOBZ.js +0 -8
- package/dist/issue-JXC6T2QR.js +0 -12
- package/dist/noydb-VGR2HLDB.js +0 -39
- package/dist/registry-ATRHOG5B.js +0 -8
- package/dist/registry-LEHB26TY.js +0 -8
- package/dist/state-vault-JR3CFGNP.js +0 -14
- package/dist/vault-group-BB246VIM.js +0 -804
- package/dist/vault-group-BB246VIM.js.map +0 -1
- /package/dist/{chunk-SQOK5UM6.js.map → chunk-2KA3PDUR.js.map} +0 -0
- /package/dist/{chunk-U2XSUCDF.js.map → chunk-3BANVNDH.js.map} +0 -0
- /package/dist/{chunk-37VGJM3T.js.map → chunk-7JSP3E67.js.map} +0 -0
- /package/dist/{chunk-F5ILTHMU.js.map → chunk-ANLOD6IS.js.map} +0 -0
- /package/dist/{chunk-JYNH4FIM.js.map → chunk-C7UIT5XY.js.map} +0 -0
- /package/dist/{chunk-TGIJTNM3.js.map → chunk-E5TJAQS7.js.map} +0 -0
- /package/dist/{chunk-GJTKMME7.js.map → chunk-EW3H5Y7N.js.map} +0 -0
- /package/dist/{chunk-JDCPRJVS.js.map → chunk-EYZJULEN.js.map} +0 -0
- /package/dist/{chunk-I3IYTUUI.js.map → chunk-FCIZXX56.js.map} +0 -0
- /package/dist/{chunk-C2RJVZZL.js.map → chunk-FJ3C3ELF.js.map} +0 -0
- /package/dist/{chunk-ZONKSLF2.js.map → chunk-FO5WEDKF.js.map} +0 -0
- /package/dist/{chunk-SQKAECUL.js.map → chunk-FUDVHE2U.js.map} +0 -0
- /package/dist/{chunk-IVZWHIEK.js.map → chunk-GHXOVGTX.js.map} +0 -0
- /package/dist/{chunk-UU6M64HI.js.map → chunk-GPZHHTJU.js.map} +0 -0
- /package/dist/{chunk-3HNKR65T.js.map → chunk-H4XFA2LM.js.map} +0 -0
- /package/dist/{chunk-JOK73NDT.js.map → chunk-HUXDQIVU.js.map} +0 -0
- /package/dist/{chunk-O5XKZCUD.js.map → chunk-JJKXJAH2.js.map} +0 -0
- /package/dist/{chunk-TNH5SLCD.js.map → chunk-KD253AI5.js.map} +0 -0
- /package/dist/{chunk-WWVJXBOT.js.map → chunk-KJ37E3R5.js.map} +0 -0
- /package/dist/{chunk-S45MDEEF.js.map → chunk-KNJ7MK4B.js.map} +0 -0
- /package/dist/{chunk-J6RGRZOY.js.map → chunk-LX4CPLU6.js.map} +0 -0
- /package/dist/{chunk-WE2BUQD2.js.map → chunk-N4EXCKWP.js.map} +0 -0
- /package/dist/{chunk-JBBWALNI.js.map → chunk-OMBPGXCL.js.map} +0 -0
- /package/dist/{chunk-NV4IHBZS.js.map → chunk-PS6PSEZL.js.map} +0 -0
- /package/dist/{chunk-6QE4DUYC.js.map → chunk-Q7P4WHTL.js.map} +0 -0
- /package/dist/{chunk-TAMRU7A2.js.map → chunk-QYQRAOEF.js.map} +0 -0
- /package/dist/{chunk-6QAZ5O6X.js.map → chunk-RHVYFAVQ.js.map} +0 -0
- /package/dist/{chunk-YPIOFSN3.js.map → chunk-SKYBEGHB.js.map} +0 -0
- /package/dist/{chunk-7MRT7EPB.js.map → chunk-TESFHBOW.js.map} +0 -0
- /package/dist/{chunk-CQYEDODS.js.map → chunk-TSUICI5N.js.map} +0 -0
- /package/dist/{chunk-TYMDCIQM.js.map → chunk-VGAN5RLD.js.map} +0 -0
- /package/dist/{chunk-5YTXYPES.js.map → chunk-VJNV2GRF.js.map} +0 -0
- /package/dist/{chunk-NSXNXLYM.js.map → chunk-VUUQYWF5.js.map} +0 -0
- /package/dist/{chunk-IW4L4X65.js.map → chunk-WVYL6HM7.js.map} +0 -0
- /package/dist/{chunk-BZW5IL43.js.map → chunk-Y5CTT6K5.js.map} +0 -0
- /package/dist/{chunk-KOAJ3TZM.js.map → chunk-YRQPI67X.js.map} +0 -0
- /package/dist/{chunk-MBXKRHSS.js.map → chunk-YYTM4U4J.js.map} +0 -0
- /package/dist/{chunk-2XA2ZML4.js.map → chunk-ZCBJIDT4.js.map} +0 -0
- /package/dist/{chunk-AI4USDRI.js.map → chunk-ZW2YSN6G.js.map} +0 -0
- /package/dist/{crypto-456N7UVX.js.map → crypto-YBKBNPVM.js.map} +0 -0
- /package/dist/{delegation-DP4COTXB.js.map → delegation-4JSMM6BB.js.map} +0 -0
- /package/dist/{executor-4IEW4KG5.js.map → executor-3SVNESQ3.js.map} +0 -0
- /package/dist/{executor-KYJCJCIN.js.map → executor-BIW4FT5R.js.map} +0 -0
- /package/dist/{executor-W7VIBOBZ.js.map → executor-VEZUBJNQ.js.map} +0 -0
- /package/dist/{fanout-sidecar-YXNAEZ33.js.map → fanout-sidecar-ZQT4Y7PF.js.map} +0 -0
- /package/dist/{issue-JXC6T2QR.js.map → issue-LEBPVF3Y.js.map} +0 -0
- /package/dist/{ledger-I7JUYP4L.js.map → kernel/index.js.map} +0 -0
- /package/dist/{noydb-VGR2HLDB.js.map → ledger-FLRTSOYH.js.map} +0 -0
- /package/dist/{public-envelope-5XRTUNKF.js.map → noydb-6FA46A4M.js.map} +0 -0
- /package/dist/{registry-ATRHOG5B.js.map → public-envelope-DBKJEBBF.js.map} +0 -0
- /package/dist/{registry-LEHB26TY.js.map → registry-CMEVTOCN.js.map} +0 -0
- /package/dist/{registry-NWHOLD5M.js.map → registry-OUZ3VBZA.js.map} +0 -0
- /package/dist/{revoke-5IEK22KT.js.map → registry-XUBRO5JJ.js.map} +0 -0
- /package/dist/{signer-I6YARZQA.js.map → revoke-P5D3UTRX.js.map} +0 -0
- /package/dist/{stale-CPESGAPL.js.map → signer-NEQPCHMW.js.map} +0 -0
- /package/dist/{state-vault-JR3CFGNP.js.map → stale-KKCHF2VB.js.map} +0 -0
package/dist/bundle/index.cjs
CHANGED
|
@@ -203,7 +203,7 @@ var init_format = __esm({
|
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
// src/errors.ts
|
|
206
|
-
var NoydbError, DebugPlaintextError, DebugReservedFieldError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError,
|
|
206
|
+
var NoydbError, DebugPlaintextError, DebugReservedFieldError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, FieldFrozenError, InvariantError, AmendmentForbiddenError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, ValidationError, SchemaValidationError, SchemaUpdateError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, LocaleNotSpecifiedError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, PartitionExtractionError, TransferSealError, AdoptionStateError, AttestationError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, DerivationCycleError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, ForgetStrategyNotConfiguredError, RecordCekNotFoundError;
|
|
207
207
|
var init_errors = __esm({
|
|
208
208
|
"src/errors.ts"() {
|
|
209
209
|
"use strict";
|
|
@@ -342,18 +342,6 @@ var init_errors = __esm({
|
|
|
342
342
|
this.offendingCollection = offendingCollection;
|
|
343
343
|
}
|
|
344
344
|
};
|
|
345
|
-
ReservedVaultNameError = class extends NoydbError {
|
|
346
|
-
/** The rejected vault name. */
|
|
347
|
-
vaultName;
|
|
348
|
-
constructor(vaultName) {
|
|
349
|
-
super(
|
|
350
|
-
"RESERVED_VAULT_NAME",
|
|
351
|
-
`"${vaultName}" is a reserved internal vault name and cannot be used as a group name or partition key`
|
|
352
|
-
);
|
|
353
|
-
this.name = "ReservedVaultNameError";
|
|
354
|
-
this.vaultName = vaultName;
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
345
|
FieldFrozenError = class extends NoydbError {
|
|
358
346
|
collection;
|
|
359
347
|
id;
|
|
@@ -941,60 +929,6 @@ Resolutions:
|
|
|
941
929
|
this.expected = expected;
|
|
942
930
|
}
|
|
943
931
|
};
|
|
944
|
-
UnknownShardError = class extends NoydbError {
|
|
945
|
-
partitionKey;
|
|
946
|
-
constructor(partitionKey, groupName) {
|
|
947
|
-
super(
|
|
948
|
-
"SHARD_UNKNOWN",
|
|
949
|
-
`No shard for partition key "${partitionKey}" in vault group "${groupName}" and autoCreate is disabled. Call group.createShard(${JSON.stringify(partitionKey)}) first, or enable sharding.autoCreate.`
|
|
950
|
-
);
|
|
951
|
-
this.name = "UnknownShardError";
|
|
952
|
-
this.partitionKey = partitionKey;
|
|
953
|
-
}
|
|
954
|
-
};
|
|
955
|
-
ShardProvisioningError = class extends NoydbError {
|
|
956
|
-
vaultId;
|
|
957
|
-
constructor(vaultId, partitionKey) {
|
|
958
|
-
super(
|
|
959
|
-
"SHARD_PROVISIONING",
|
|
960
|
-
`Registry has a row for partition "${partitionKey}" (vault "${vaultId}") but that vault is not provisioned in the store. Refusing to recreate it \u2014 the registry and store have diverged. Investigate before retrying.`
|
|
961
|
-
);
|
|
962
|
-
this.name = "ShardProvisioningError";
|
|
963
|
-
this.vaultId = vaultId;
|
|
964
|
-
}
|
|
965
|
-
};
|
|
966
|
-
DataResidencyError = class extends NoydbError {
|
|
967
|
-
vaultId;
|
|
968
|
-
requiredRegion;
|
|
969
|
-
backendRegion;
|
|
970
|
-
constructor(vaultId, requiredRegion, backendRegion) {
|
|
971
|
-
super(
|
|
972
|
-
"DATA_RESIDENCY",
|
|
973
|
-
`Shard "${vaultId}" requires region "${requiredRegion}" but its placement backend declares region ${backendRegion === void 0 ? "(none)" : `"${backendRegion}"`}. Refusing to provision \u2014 route this shard to a region-correct backend via routeStore({ vaultRoutes }) (e.g. a region-encoded partition key) before retrying.`
|
|
974
|
-
);
|
|
975
|
-
this.name = "DataResidencyError";
|
|
976
|
-
this.vaultId = vaultId;
|
|
977
|
-
this.requiredRegion = requiredRegion;
|
|
978
|
-
this.backendRegion = backendRegion;
|
|
979
|
-
}
|
|
980
|
-
};
|
|
981
|
-
CrossShardJoinError = class extends NoydbError {
|
|
982
|
-
constructor(message) {
|
|
983
|
-
super("CROSS_SHARD_JOIN", message);
|
|
984
|
-
this.name = "CrossShardJoinError";
|
|
985
|
-
}
|
|
986
|
-
};
|
|
987
|
-
VaultTemplateNotFoundError = class extends NoydbError {
|
|
988
|
-
templateName;
|
|
989
|
-
constructor(templateName) {
|
|
990
|
-
super(
|
|
991
|
-
"VAULT_TEMPLATE_NOT_FOUND",
|
|
992
|
-
`No vault template registered under "${templateName}". Register it with db.withVaultTemplate(${JSON.stringify(templateName)}, { version, configure }) before opening the vault group.`
|
|
993
|
-
);
|
|
994
|
-
this.name = "VaultTemplateNotFoundError";
|
|
995
|
-
this.templateName = templateName;
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
932
|
ForgetStrategyNotConfiguredError = class extends NoydbError {
|
|
999
933
|
constructor(message = 'vault.forget() requires a forget strategy. Pass `forgetStrategy: withForgetCascade({ subjects: { <collection>: <subjectField> } })` from "@noy-db/hub/forget" to createNoydb().') {
|
|
1000
934
|
super("FORGET_NOT_CONFIGURED", message);
|
|
@@ -2518,12 +2452,14 @@ var init_user_envelope = __esm({
|
|
|
2518
2452
|
// src/team/keyring.ts
|
|
2519
2453
|
function canGrant(callerRole, targetRole) {
|
|
2520
2454
|
if (callerRole === "owner") return true;
|
|
2455
|
+
if (callerRole === "custodian") return false;
|
|
2521
2456
|
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
2522
2457
|
return false;
|
|
2523
2458
|
}
|
|
2524
2459
|
function canRevoke(callerRole, targetRole) {
|
|
2525
2460
|
if (targetRole === "owner") return false;
|
|
2526
2461
|
if (callerRole === "owner") return true;
|
|
2462
|
+
if (callerRole === "custodian") return false;
|
|
2527
2463
|
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
2528
2464
|
return false;
|
|
2529
2465
|
}
|
|
@@ -2687,7 +2623,7 @@ async function grant(adapter, vault, callerKeyring, options) {
|
|
|
2687
2623
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
2688
2624
|
}
|
|
2689
2625
|
}
|
|
2690
|
-
if (options.role === "owner" || options.role === "admin" || options.role === "viewer") {
|
|
2626
|
+
if (options.role === "owner" || options.role === "admin" || options.role === "custodian" || options.role === "viewer") {
|
|
2691
2627
|
for (const [collName, dek] of callerKeyring.deks) {
|
|
2692
2628
|
if (!(collName in wrappedDeks)) {
|
|
2693
2629
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
@@ -2835,6 +2771,11 @@ async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
|
|
|
2835
2771
|
await writeKeyringFile(adapter, vault, options.userId, next);
|
|
2836
2772
|
}
|
|
2837
2773
|
async function rotateKeys(adapter, vault, callerKeyring, collections) {
|
|
2774
|
+
if (callerKeyring.role === "custodian") {
|
|
2775
|
+
throw new PermissionDeniedError(
|
|
2776
|
+
"custodian cannot rotate keys (FR-6: re-key is an owner-only meta-capability; use the Deed owner)"
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2838
2779
|
const newDeks = /* @__PURE__ */ new Map();
|
|
2839
2780
|
for (const collName of collections) {
|
|
2840
2781
|
newDeks.set(collName, await generateDEK());
|
|
@@ -2944,7 +2885,7 @@ async function buildRecipientKeyringFile(callerKeyring, recipient) {
|
|
|
2944
2885
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
2945
2886
|
}
|
|
2946
2887
|
}
|
|
2947
|
-
if (role === "owner" || role === "admin" || role === "viewer") {
|
|
2888
|
+
if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") {
|
|
2948
2889
|
for (const [collName, dek] of callerKeyring.deks) {
|
|
2949
2890
|
if (!(collName in wrappedDeks)) {
|
|
2950
2891
|
wrappedDeks[collName] = await wrapKey(dek, newKek);
|
|
@@ -3018,12 +2959,13 @@ async function ensureCollectionDEK(adapter, vault, keyring) {
|
|
|
3018
2959
|
};
|
|
3019
2960
|
}
|
|
3020
2961
|
function hasWritePermission(keyring, collectionName) {
|
|
3021
|
-
if (keyring.role === "owner" || keyring.role === "admin") return true;
|
|
2962
|
+
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return true;
|
|
3022
2963
|
if (keyring.role === "viewer" || keyring.role === "client") return false;
|
|
3023
2964
|
return keyring.permissions[collectionName] === "rw";
|
|
3024
2965
|
}
|
|
3025
2966
|
function hasAccess(keyring, collectionName) {
|
|
3026
|
-
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "viewer")
|
|
2967
|
+
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian" || keyring.role === "viewer")
|
|
2968
|
+
return true;
|
|
3027
2969
|
return collectionName in keyring.permissions;
|
|
3028
2970
|
}
|
|
3029
2971
|
async function persistKeyring(adapter, vault, keyring) {
|
|
@@ -3075,7 +3017,7 @@ function hasImportCapability(keyring, tier, format) {
|
|
|
3075
3017
|
return cap?.bundle === true;
|
|
3076
3018
|
}
|
|
3077
3019
|
function resolvePermissions(role, explicit) {
|
|
3078
|
-
if (role === "owner" || role === "admin" || role === "viewer") return {};
|
|
3020
|
+
if (role === "owner" || role === "admin" || role === "custodian" || role === "viewer") return {};
|
|
3079
3021
|
return explicit ?? {};
|
|
3080
3022
|
}
|
|
3081
3023
|
async function writeKeyringFile(adapter, vault, userId, keyringFile) {
|
|
@@ -3781,15 +3723,6 @@ var init_store = __esm({
|
|
|
3781
3723
|
}
|
|
3782
3724
|
});
|
|
3783
3725
|
|
|
3784
|
-
// src/federation/constants.ts
|
|
3785
|
-
var STATE_VAULT_NAME;
|
|
3786
|
-
var init_constants2 = __esm({
|
|
3787
|
-
"src/federation/constants.ts"() {
|
|
3788
|
-
"use strict";
|
|
3789
|
-
STATE_VAULT_NAME = "__noydb_state__";
|
|
3790
|
-
}
|
|
3791
|
-
});
|
|
3792
|
-
|
|
3793
3726
|
// src/policy/errors.ts
|
|
3794
3727
|
var PolicyDeniedError, RecoveryNotEnrolledError, ManagedRecoveryNotEnrolledError, RecoveryProfileNotImplementedError;
|
|
3795
3728
|
var init_errors2 = __esm({
|
|
@@ -4992,10 +4925,10 @@ function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAf
|
|
|
4992
4925
|
}
|
|
4993
4926
|
}
|
|
4994
4927
|
function parseToScaledInt(input, scale, rounding) {
|
|
4995
|
-
const
|
|
4996
|
-
if (
|
|
4997
|
-
const negative =
|
|
4998
|
-
const unsigned = negative ?
|
|
4928
|
+
const canonical = toCanonicalDecimalString(input);
|
|
4929
|
+
if (canonical === null) return { ok: false, reason: "nonfinite" };
|
|
4930
|
+
const negative = canonical.startsWith("-");
|
|
4931
|
+
const unsigned = negative ? canonical.slice(1) : canonical;
|
|
4999
4932
|
const dot = unsigned.indexOf(".");
|
|
5000
4933
|
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
5001
4934
|
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
@@ -5491,7 +5424,7 @@ function dekKey(collection, tier) {
|
|
|
5491
5424
|
}
|
|
5492
5425
|
function assertTierAccess(keyring, collection, tier) {
|
|
5493
5426
|
if (tier <= 0) return;
|
|
5494
|
-
if (keyring.role === "owner" || keyring.role === "admin") return;
|
|
5427
|
+
if (keyring.role === "owner" || keyring.role === "admin" || keyring.role === "custodian") return;
|
|
5495
5428
|
if (!keyring.deks.has(dekKey(collection, tier))) {
|
|
5496
5429
|
throw new TierNotGrantedError(collection, tier);
|
|
5497
5430
|
}
|
|
@@ -6562,7 +6495,7 @@ function serializeClause(clause) {
|
|
|
6562
6495
|
}
|
|
6563
6496
|
function canonicalCtxHash(ctx) {
|
|
6564
6497
|
if (ctx === void 0) return "";
|
|
6565
|
-
const
|
|
6498
|
+
const canonical = JSON.stringify(ctx, (_key, value) => {
|
|
6566
6499
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6567
6500
|
const sorted = {};
|
|
6568
6501
|
for (const k of Object.keys(value).sort()) {
|
|
@@ -6573,8 +6506,8 @@ function canonicalCtxHash(ctx) {
|
|
|
6573
6506
|
return value;
|
|
6574
6507
|
});
|
|
6575
6508
|
let h = 5381;
|
|
6576
|
-
for (let i = 0; i <
|
|
6577
|
-
h = (h << 5) + h ^
|
|
6509
|
+
for (let i = 0; i < canonical.length; i++) {
|
|
6510
|
+
h = (h << 5) + h ^ canonical.charCodeAt(i);
|
|
6578
6511
|
}
|
|
6579
6512
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
6580
6513
|
}
|
|
@@ -7283,29 +7216,6 @@ var init_builder = __esm({
|
|
|
7283
7216
|
}
|
|
7284
7217
|
});
|
|
7285
7218
|
|
|
7286
|
-
// src/aggregate/aggregation.ts
|
|
7287
|
-
function reduceRecords(records, spec) {
|
|
7288
|
-
const state = {};
|
|
7289
|
-
for (const key of Object.keys(spec)) {
|
|
7290
|
-
state[key] = spec[key].init();
|
|
7291
|
-
}
|
|
7292
|
-
for (const record of records) {
|
|
7293
|
-
for (const key of Object.keys(spec)) {
|
|
7294
|
-
state[key] = spec[key].step(state[key], record);
|
|
7295
|
-
}
|
|
7296
|
-
}
|
|
7297
|
-
const result = {};
|
|
7298
|
-
for (const key of Object.keys(spec)) {
|
|
7299
|
-
result[key] = spec[key].finalize(state[key]);
|
|
7300
|
-
}
|
|
7301
|
-
return result;
|
|
7302
|
-
}
|
|
7303
|
-
var init_aggregation = __esm({
|
|
7304
|
-
"src/aggregate/aggregation.ts"() {
|
|
7305
|
-
"use strict";
|
|
7306
|
-
}
|
|
7307
|
-
});
|
|
7308
|
-
|
|
7309
7219
|
// src/aggregate/canonical-key.ts
|
|
7310
7220
|
function canonicalGroupKey(fields, row) {
|
|
7311
7221
|
const sorted = [...fields].sort();
|
|
@@ -8534,7 +8444,7 @@ var init_transaction = __esm({
|
|
|
8534
8444
|
const v = this._db.vault(name);
|
|
8535
8445
|
if (this._amendment && !this._amendmentVaults.has(name)) {
|
|
8536
8446
|
const role = v.role;
|
|
8537
|
-
if (role !== "admin" && role !== "owner") {
|
|
8447
|
+
if (role !== "admin" && role !== "owner" && role !== "custodian") {
|
|
8538
8448
|
throw new AmendmentForbiddenError(v.userId, role);
|
|
8539
8449
|
}
|
|
8540
8450
|
const reg = v._getGuardRegistry();
|
|
@@ -8904,12 +8814,12 @@ var init_dependency_analyzer = __esm({
|
|
|
8904
8814
|
|
|
8905
8815
|
// src/materialized-views/query-hash.ts
|
|
8906
8816
|
async function computeQueryHash(mvName, dependencies, queryPlanSummary) {
|
|
8907
|
-
const
|
|
8817
|
+
const canonical = JSON.stringify({
|
|
8908
8818
|
mvName,
|
|
8909
8819
|
dependencies: [...dependencies].sort(),
|
|
8910
8820
|
queryPlanSummary
|
|
8911
8821
|
});
|
|
8912
|
-
const bytes = new TextEncoder().encode(
|
|
8822
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
8913
8823
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
8914
8824
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8915
8825
|
}
|
|
@@ -9722,6 +9632,13 @@ var init_collection = __esm({
|
|
|
9722
9632
|
* flag) still decrypts CEK records.
|
|
9723
9633
|
*/
|
|
9724
9634
|
perRecordCek;
|
|
9635
|
+
/**
|
|
9636
|
+
* Per-record provenance opt-in (`provenance: true`). When set, `put()` calls
|
|
9637
|
+
* that supply a `source` option stamp `_source`/`_sourceTs` onto the
|
|
9638
|
+
* unencrypted envelope metadata. Off by default — zero cost for collections
|
|
9639
|
+
* that don't need lineage tracking (FR-5, #445).
|
|
9640
|
+
*/
|
|
9641
|
+
provenance;
|
|
9725
9642
|
/**
|
|
9726
9643
|
* Session-scoped `(id) → CEK` cache for this collection. Lets updates
|
|
9727
9644
|
* reuse a record's stable CEK and lets repeated reads skip the AES-KW
|
|
@@ -9881,6 +9798,7 @@ var init_collection = __esm({
|
|
|
9881
9798
|
}
|
|
9882
9799
|
this.perRecordCek = opts.perRecordKeys === true;
|
|
9883
9800
|
this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
|
|
9801
|
+
this.provenance = opts.provenance === true;
|
|
9884
9802
|
if (opts.crdt && opts.onRegisterConflictResolver) {
|
|
9885
9803
|
const crdtMode = opts.crdt;
|
|
9886
9804
|
const crdtResolver = async (id, local, remote) => {
|
|
@@ -10068,6 +9986,33 @@ var init_collection = __esm({
|
|
|
10068
9986
|
if (json === null) return null;
|
|
10069
9987
|
return JSON.parse(json);
|
|
10070
9988
|
}
|
|
9989
|
+
/**
|
|
9990
|
+
* Read a record's unencrypted envelope metadata (version, timestamps,
|
|
9991
|
+
* provenance) without decrypting the body.
|
|
9992
|
+
*
|
|
9993
|
+
* Returns `null` when no envelope exists for `id` (record absent or never
|
|
9994
|
+
* written). Only `_source`/`_sourceTs` fields are populated when the
|
|
9995
|
+
* collection was opened with `provenance: true` AND the record was written
|
|
9996
|
+
* with a `source` option — but this method works on any collection because
|
|
9997
|
+
* it reads the raw envelope directly.
|
|
9998
|
+
*
|
|
9999
|
+
* @returns `{ version, timestamp, by?, source?, sourceTs? }` or `null`.
|
|
10000
|
+
*
|
|
10001
|
+
* @example
|
|
10002
|
+
* const meta = await clients.getMetadata('c1')
|
|
10003
|
+
* if (meta) console.log(meta.source, meta.timestamp)
|
|
10004
|
+
*/
|
|
10005
|
+
async getMetadata(id) {
|
|
10006
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
10007
|
+
if (!env) return null;
|
|
10008
|
+
return {
|
|
10009
|
+
version: env._v,
|
|
10010
|
+
timestamp: env._ts,
|
|
10011
|
+
...env._by !== void 0 ? { by: env._by } : {},
|
|
10012
|
+
...env._source !== void 0 ? { source: env._source } : {},
|
|
10013
|
+
...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
|
|
10014
|
+
};
|
|
10015
|
+
}
|
|
10071
10016
|
/**
|
|
10072
10017
|
* Return a presence handle for this collection.
|
|
10073
10018
|
*
|
|
@@ -10105,6 +10050,14 @@ var init_collection = __esm({
|
|
|
10105
10050
|
* `reason` is stamped onto the resulting ledger entry
|
|
10106
10051
|
* so audit consumers can filter via
|
|
10107
10052
|
* `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
10053
|
+
* `source` is an opaque source id (e.g. `'crm-sync'`, `'firm-A'`)
|
|
10054
|
+
* stamped onto the envelope as `_source`/`_sourceTs` when
|
|
10055
|
+
* the collection has `provenance: true`. Ignored otherwise
|
|
10056
|
+
* (zero cost). (FR-5, #445)
|
|
10057
|
+
* `sourceTs` is an optional ISO-8601 origin timestamp override;
|
|
10058
|
+
* when supplied together with `source` on a provenance collection,
|
|
10059
|
+
* replaces the machine-stamped `now()` so re-merges preserve the
|
|
10060
|
+
* ORIGIN refresh time across vaults. (FR-4)
|
|
10108
10061
|
*/
|
|
10109
10062
|
async put(id, record, options) {
|
|
10110
10063
|
await this.schemaUpdateGate?.assertWritable();
|
|
@@ -10136,6 +10089,20 @@ var init_collection = __esm({
|
|
|
10136
10089
|
if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
|
|
10137
10090
|
}
|
|
10138
10091
|
}
|
|
10092
|
+
/**
|
|
10093
|
+
* Validate a record against this collection's schema WITHOUT writing it.
|
|
10094
|
+
* Returns the (possibly coerced) record on success; throws
|
|
10095
|
+
* {@link SchemaValidationError} (direction: `'input'`) on violation.
|
|
10096
|
+
* A no-op pass-through when no schema is declared.
|
|
10097
|
+
*
|
|
10098
|
+
* Used by FR-8 migrate-then-merge to pre-validate all staged records
|
|
10099
|
+
* before `mergeDecryptedRecords` writes anything — so a failed upgrade
|
|
10100
|
+
* never half-writes the receiver.
|
|
10101
|
+
*/
|
|
10102
|
+
async validateInput(record) {
|
|
10103
|
+
if (this.schema === void 0) return record;
|
|
10104
|
+
return validateSchemaInput(this.schema, record, `validateInput(${this.name})`);
|
|
10105
|
+
}
|
|
10139
10106
|
/** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
|
|
10140
10107
|
#hooksActive() {
|
|
10141
10108
|
return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
|
|
@@ -10293,7 +10260,7 @@ var init_collection = __esm({
|
|
|
10293
10260
|
}
|
|
10294
10261
|
const version2 = existingVersion + 1;
|
|
10295
10262
|
const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
10296
|
-
const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
|
|
10263
|
+
const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2, options?.source, options?.sourceTs);
|
|
10297
10264
|
await this.adapter.put(this.vault, this.name, id, envelope2);
|
|
10298
10265
|
const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
|
|
10299
10266
|
const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
|
|
@@ -10372,7 +10339,7 @@ var init_collection = __esm({
|
|
|
10372
10339
|
});
|
|
10373
10340
|
}
|
|
10374
10341
|
}
|
|
10375
|
-
const envelope = await this.encryptRecord(record, version, cek);
|
|
10342
|
+
const envelope = await this.encryptRecord(record, version, cek, options?.source, options?.sourceTs);
|
|
10376
10343
|
await this.adapter.put(this.vault, this.name, id, envelope);
|
|
10377
10344
|
if (this.ledger) {
|
|
10378
10345
|
const appendInput = {
|
|
@@ -10665,7 +10632,7 @@ var init_collection = __esm({
|
|
|
10665
10632
|
priorEnvelope
|
|
10666
10633
|
});
|
|
10667
10634
|
}
|
|
10668
|
-
await outputCollection.put(entry.key, entry.value);
|
|
10635
|
+
await outputCollection.put(entry.key, entry.value, { source: "derived" });
|
|
10669
10636
|
}
|
|
10670
10637
|
await saveFanoutSidecar2(this.adapter, this.vault, {
|
|
10671
10638
|
source: spec.source,
|
|
@@ -10698,7 +10665,7 @@ var init_collection = __esm({
|
|
|
10698
10665
|
priorEnvelope: prior
|
|
10699
10666
|
});
|
|
10700
10667
|
}
|
|
10701
|
-
await outputCollection.put(run.runId, patched);
|
|
10668
|
+
await outputCollection.put(run.runId, patched, { source: "derived" });
|
|
10702
10669
|
continue;
|
|
10703
10670
|
}
|
|
10704
10671
|
if (txCtx !== null) {
|
|
@@ -10713,7 +10680,7 @@ var init_collection = __esm({
|
|
|
10713
10680
|
priorEnvelope: prior
|
|
10714
10681
|
});
|
|
10715
10682
|
}
|
|
10716
|
-
await outputCollection.put(run.runId, out.value);
|
|
10683
|
+
await outputCollection.put(run.runId, out.value, { source: "derived" });
|
|
10717
10684
|
}
|
|
10718
10685
|
}
|
|
10719
10686
|
}
|
|
@@ -12376,7 +12343,7 @@ var init_collection = __esm({
|
|
|
12376
12343
|
* (see {@link encryptRecord}). Rejects `_`-prefixed record fields, which
|
|
12377
12344
|
* would collide with the reserved metadata namespace.
|
|
12378
12345
|
*/
|
|
12379
|
-
buildDebugEnvelope(record, version) {
|
|
12346
|
+
buildDebugEnvelope(record, version, source, sourceTs) {
|
|
12380
12347
|
const rec = record;
|
|
12381
12348
|
for (const key of Object.keys(rec)) {
|
|
12382
12349
|
if (key.startsWith("_")) throw new DebugReservedFieldError(this.name, key);
|
|
@@ -12389,11 +12356,13 @@ var init_collection = __esm({
|
|
|
12389
12356
|
_data: "",
|
|
12390
12357
|
_by: this.keyring.userId,
|
|
12391
12358
|
_debug: NOYDB_FORMAT_VERSION,
|
|
12359
|
+
...this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {},
|
|
12392
12360
|
...rec
|
|
12393
12361
|
};
|
|
12394
12362
|
}
|
|
12395
|
-
async encryptJsonString(json, version, cek) {
|
|
12363
|
+
async encryptJsonString(json, version, cek, source, sourceTs) {
|
|
12396
12364
|
const by = this.keyring.userId;
|
|
12365
|
+
const provenanceFields = this.provenance && source !== void 0 ? { _source: source, _sourceTs: sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {};
|
|
12397
12366
|
if (!this.encrypted) {
|
|
12398
12367
|
return {
|
|
12399
12368
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
@@ -12401,7 +12370,8 @@ var init_collection = __esm({
|
|
|
12401
12370
|
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12402
12371
|
_iv: "",
|
|
12403
12372
|
_data: json,
|
|
12404
|
-
_by: by
|
|
12373
|
+
_by: by,
|
|
12374
|
+
...provenanceFields
|
|
12405
12375
|
};
|
|
12406
12376
|
}
|
|
12407
12377
|
const dek = await this.getDEK(this.name);
|
|
@@ -12415,7 +12385,8 @@ var init_collection = __esm({
|
|
|
12415
12385
|
_iv: iv2,
|
|
12416
12386
|
_data: data2,
|
|
12417
12387
|
_by: by,
|
|
12418
|
-
_cek: wrapped
|
|
12388
|
+
_cek: wrapped,
|
|
12389
|
+
...provenanceFields
|
|
12419
12390
|
};
|
|
12420
12391
|
}
|
|
12421
12392
|
const { iv, data } = await encrypt(json, dek);
|
|
@@ -12425,14 +12396,15 @@ var init_collection = __esm({
|
|
|
12425
12396
|
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12426
12397
|
_iv: iv,
|
|
12427
12398
|
_data: data,
|
|
12428
|
-
_by: by
|
|
12399
|
+
_by: by,
|
|
12400
|
+
...provenanceFields
|
|
12429
12401
|
};
|
|
12430
12402
|
}
|
|
12431
|
-
async encryptRecord(record, version, cek) {
|
|
12403
|
+
async encryptRecord(record, version, cek, source, sourceTs) {
|
|
12432
12404
|
if (!this.encrypted && this.keyring.debugPlaintext === true && !this.name.startsWith("_")) {
|
|
12433
|
-
return this.buildDebugEnvelope(record, version);
|
|
12405
|
+
return this.buildDebugEnvelope(record, version, source, sourceTs);
|
|
12434
12406
|
}
|
|
12435
|
-
const base = await this.encryptJsonString(JSON.stringify(record), version, cek);
|
|
12407
|
+
const base = await this.encryptJsonString(JSON.stringify(record), version, cek, source, sourceTs);
|
|
12436
12408
|
if (!this.deterministicFields || !this.encrypted) return base;
|
|
12437
12409
|
const dek = await this.getDEK(this.name);
|
|
12438
12410
|
const rec = record;
|
|
@@ -12566,7 +12538,8 @@ var init_collection = __esm({
|
|
|
12566
12538
|
_iv: iv,
|
|
12567
12539
|
_data: data,
|
|
12568
12540
|
_by: this.keyring.userId,
|
|
12569
|
-
...tier > 0 && { _tier: tier }
|
|
12541
|
+
...tier > 0 && { _tier: tier },
|
|
12542
|
+
...this.provenance && opts?.source !== void 0 ? { _source: opts.source, _sourceTs: opts.sourceTs ?? (/* @__PURE__ */ new Date()).toISOString() } : {}
|
|
12570
12543
|
};
|
|
12571
12544
|
await this.adapter.put(this.vault, this.name, id, envelope);
|
|
12572
12545
|
if (tier > 0) {
|
|
@@ -12873,43 +12846,49 @@ function randomId() {
|
|
|
12873
12846
|
const b = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
12874
12847
|
return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
12875
12848
|
}
|
|
12876
|
-
async function
|
|
12849
|
+
async function freezeSnapshotOnly(vault, collections, opts) {
|
|
12877
12850
|
const { name: vaultName, adapter } = vault._introspectState();
|
|
12878
12851
|
const closure = [];
|
|
12879
12852
|
for (const c of collections) {
|
|
12880
12853
|
for (const id of await adapter.list(vaultName, c)) closure.push({ collection: c, id });
|
|
12881
12854
|
}
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
|
|
12885
|
-
const snap = {};
|
|
12886
|
-
for (const { collection, id } of closure) {
|
|
12887
|
-
const env = await adapter.get(vaultName, collection, id);
|
|
12888
|
-
if (env) (snap[collection] ??= {})[id] = env;
|
|
12889
|
-
}
|
|
12890
|
-
const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12891
|
-
const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
|
|
12892
|
-
const sha = await sha256Hex2(ENC.encode(body));
|
|
12893
|
-
await adapter.put(
|
|
12894
|
-
vaultName,
|
|
12895
|
-
FROZEN_SNAPSHOTS_COLLECTION,
|
|
12896
|
-
withdrawalId,
|
|
12897
|
-
{ _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
|
|
12898
|
-
0
|
|
12899
|
-
);
|
|
12900
|
-
await vault._getLedgerOrNull()?.append({
|
|
12901
|
-
op: "lifecycle",
|
|
12902
|
-
collection: "",
|
|
12903
|
-
id: "",
|
|
12904
|
-
version: 0,
|
|
12905
|
-
actor: opts.actorUserId,
|
|
12906
|
-
payloadHash: "",
|
|
12907
|
-
reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
|
|
12908
|
-
});
|
|
12909
|
-
snapshot = { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
|
|
12910
|
-
}
|
|
12855
|
+
const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
|
|
12856
|
+
const snap = {};
|
|
12911
12857
|
for (const { collection, id } of closure) {
|
|
12912
|
-
await
|
|
12858
|
+
const env = await adapter.get(vaultName, collection, id);
|
|
12859
|
+
if (env) (snap[collection] ??= {})[id] = env;
|
|
12860
|
+
}
|
|
12861
|
+
const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12862
|
+
const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
|
|
12863
|
+
const sha = await sha256Hex2(ENC.encode(body));
|
|
12864
|
+
await adapter.put(
|
|
12865
|
+
vaultName,
|
|
12866
|
+
FROZEN_SNAPSHOTS_COLLECTION,
|
|
12867
|
+
withdrawalId,
|
|
12868
|
+
{ _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
|
|
12869
|
+
0
|
|
12870
|
+
);
|
|
12871
|
+
await vault._getLedgerOrNull()?.append({
|
|
12872
|
+
op: "lifecycle",
|
|
12873
|
+
collection: "",
|
|
12874
|
+
id: "",
|
|
12875
|
+
version: 0,
|
|
12876
|
+
actor: opts.actorUserId,
|
|
12877
|
+
payloadHash: "",
|
|
12878
|
+
reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
|
|
12879
|
+
});
|
|
12880
|
+
return { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
|
|
12881
|
+
}
|
|
12882
|
+
async function freezeAndDeleteClosure(vault, collections, opts) {
|
|
12883
|
+
const snapshot = opts.disposition === "freeze" ? await freezeSnapshotOnly(vault, collections, {
|
|
12884
|
+
actorUserId: opts.actorUserId,
|
|
12885
|
+
...opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}
|
|
12886
|
+
}) : void 0;
|
|
12887
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
12888
|
+
for (const c of collections) {
|
|
12889
|
+
for (const id of await adapter.list(vaultName, c)) {
|
|
12890
|
+
await vault.collection(c).delete(id);
|
|
12891
|
+
}
|
|
12913
12892
|
}
|
|
12914
12893
|
return snapshot;
|
|
12915
12894
|
}
|
|
@@ -12921,6 +12900,11 @@ async function withdrawAccessibleData(vault, opts) {
|
|
|
12921
12900
|
"unilateralWithdrawal is the scoped self-service path; an owner/admin should use extractPartition"
|
|
12922
12901
|
);
|
|
12923
12902
|
}
|
|
12903
|
+
if (keyring.role === "custodian") {
|
|
12904
|
+
throw new ReadOnlyError(
|
|
12905
|
+
"a custodian cannot destructively withdraw/sever; use vault.custody.liberate for an audited ownership claim"
|
|
12906
|
+
);
|
|
12907
|
+
}
|
|
12924
12908
|
if (keyring.role === "client" || keyring.role === "viewer") {
|
|
12925
12909
|
throw new ReadOnlyError(
|
|
12926
12910
|
"read-only role cannot self-serve a destructive withdrawal \u2014 use requestWithdrawal (two-party)"
|
|
@@ -14819,6 +14803,157 @@ var init_api = __esm({
|
|
|
14819
14803
|
}
|
|
14820
14804
|
});
|
|
14821
14805
|
|
|
14806
|
+
// src/custody/index.ts
|
|
14807
|
+
var CustodyApi;
|
|
14808
|
+
var init_custody = __esm({
|
|
14809
|
+
"src/custody/index.ts"() {
|
|
14810
|
+
"use strict";
|
|
14811
|
+
CustodyApi = class {
|
|
14812
|
+
constructor(_grantCustodian, _revokeCustodian, _liberate) {
|
|
14813
|
+
this._grantCustodian = _grantCustodian;
|
|
14814
|
+
this._revokeCustodian = _revokeCustodian;
|
|
14815
|
+
this._liberate = _liberate;
|
|
14816
|
+
}
|
|
14817
|
+
_grantCustodian;
|
|
14818
|
+
_revokeCustodian;
|
|
14819
|
+
_liberate;
|
|
14820
|
+
/**
|
|
14821
|
+
* Owner-only: grant the FR-6 `custodian` role. The custodian operates every
|
|
14822
|
+
* collection (rw + access) but is provably unable to grant / revoke / rotate /
|
|
14823
|
+
* extract-and-sever. Defended in depth (gate + owner-only role check) inside
|
|
14824
|
+
* the injected `Noydb.grantCustodian`.
|
|
14825
|
+
*/
|
|
14826
|
+
async grantCustodian(options, factors) {
|
|
14827
|
+
return this._grantCustodian(options, factors);
|
|
14828
|
+
}
|
|
14829
|
+
/** Owner-only: revoke a custodian. */
|
|
14830
|
+
async revokeCustodian(options, factors) {
|
|
14831
|
+
return this._revokeCustodian(options, factors);
|
|
14832
|
+
}
|
|
14833
|
+
/**
|
|
14834
|
+
* Custodian-only: the audited claim of ownership over a sealed-owner (Deed)
|
|
14835
|
+
* vault. Mints a DISTINCT new owner re-wrapping the incumbent DEKs under a
|
|
14836
|
+
* fresh KEK (the latent owner is never impersonated), ledger-audited. See
|
|
14837
|
+
* {@link liberateVault}.
|
|
14838
|
+
*/
|
|
14839
|
+
async liberate(opts) {
|
|
14840
|
+
return this._liberate(opts);
|
|
14841
|
+
}
|
|
14842
|
+
};
|
|
14843
|
+
}
|
|
14844
|
+
});
|
|
14845
|
+
|
|
14846
|
+
// src/team/deed.ts
|
|
14847
|
+
async function loadDeedMarker(store, vault) {
|
|
14848
|
+
const envelope = await store.get(vault, "_meta", DEED_RECORD_ID);
|
|
14849
|
+
if (!envelope) return null;
|
|
14850
|
+
let payload;
|
|
14851
|
+
try {
|
|
14852
|
+
payload = JSON.parse(envelope._data);
|
|
14853
|
+
} catch {
|
|
14854
|
+
return null;
|
|
14855
|
+
}
|
|
14856
|
+
if (typeof payload !== "object" || payload === null) return null;
|
|
14857
|
+
const r = payload;
|
|
14858
|
+
if (r._noydb_deed !== 1) return null;
|
|
14859
|
+
if (typeof r.ownerUserId !== "string" || typeof r.sealedUnder !== "string" || r.latent !== true || typeof r.issuedAt !== "string") {
|
|
14860
|
+
return null;
|
|
14861
|
+
}
|
|
14862
|
+
const marker = {
|
|
14863
|
+
ownerUserId: r.ownerUserId,
|
|
14864
|
+
sealedUnder: r.sealedUnder,
|
|
14865
|
+
latent: true,
|
|
14866
|
+
issuedAt: r.issuedAt,
|
|
14867
|
+
...typeof r.liberatedAt === "string" ? { liberatedAt: r.liberatedAt } : {}
|
|
14868
|
+
};
|
|
14869
|
+
return marker;
|
|
14870
|
+
}
|
|
14871
|
+
async function saveDeedMarker(store, vault, marker) {
|
|
14872
|
+
const persisted = { _noydb_deed: 1, ...marker };
|
|
14873
|
+
const prior = await store.get(vault, "_meta", DEED_RECORD_ID);
|
|
14874
|
+
const env = {
|
|
14875
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
14876
|
+
_v: (prior?._v ?? 0) + 1,
|
|
14877
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14878
|
+
// AES-GCM bypassed — the marker is plaintext audit metadata.
|
|
14879
|
+
_iv: "",
|
|
14880
|
+
_data: JSON.stringify(persisted)
|
|
14881
|
+
};
|
|
14882
|
+
await store.put(vault, "_meta", DEED_RECORD_ID, env);
|
|
14883
|
+
}
|
|
14884
|
+
var DEED_RECORD_ID;
|
|
14885
|
+
var init_deed = __esm({
|
|
14886
|
+
"src/team/deed.ts"() {
|
|
14887
|
+
"use strict";
|
|
14888
|
+
init_types();
|
|
14889
|
+
DEED_RECORD_ID = "deed";
|
|
14890
|
+
}
|
|
14891
|
+
});
|
|
14892
|
+
|
|
14893
|
+
// src/custody/liberate.ts
|
|
14894
|
+
async function liberateVault(vault, opts) {
|
|
14895
|
+
await vault.noydb.checkGate(vault.name, "liberate-vault", opts.factors);
|
|
14896
|
+
const { name: vaultName, adapter, keyring } = vault._introspectState();
|
|
14897
|
+
if (keyring.role !== "custodian") {
|
|
14898
|
+
throw new PermissionDeniedError(
|
|
14899
|
+
"liberation is claimed only by the custodian (the de-facto authority holding the DEKs)"
|
|
14900
|
+
);
|
|
14901
|
+
}
|
|
14902
|
+
const existing = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
|
|
14903
|
+
if (existing) {
|
|
14904
|
+
throw new PermissionDeniedError(
|
|
14905
|
+
`liberateVault: newOwnerId "${opts.newOwnerId}" already exists as a principal; choose a fresh id (liberation mints a distinct owner, it never overwrites an existing keyring)`
|
|
14906
|
+
);
|
|
14907
|
+
}
|
|
14908
|
+
const collections = await listOperationalCollections(vault);
|
|
14909
|
+
const snapshot = await freezeSnapshotOnly(vault, collections, { actorUserId: keyring.userId });
|
|
14910
|
+
const newOwner = await createOwnerKeyring(adapter, vaultName, opts.newOwnerId, opts.newOwnerPassphrase);
|
|
14911
|
+
if (!newOwner.kek) {
|
|
14912
|
+
throw new PermissionDeniedError(
|
|
14913
|
+
`new owner keyring for "${opts.newOwnerId}" has no KEK to re-wrap the incumbent DEKs under`
|
|
14914
|
+
);
|
|
14915
|
+
}
|
|
14916
|
+
const env = await adapter.get(vaultName, "_keyring", opts.newOwnerId);
|
|
14917
|
+
if (!env) {
|
|
14918
|
+
throw new PermissionDeniedError(`new owner keyring for "${opts.newOwnerId}" did not persist`);
|
|
14919
|
+
}
|
|
14920
|
+
const keyringFile = JSON.parse(env._data);
|
|
14921
|
+
const mergedDeks = { ...keyringFile.deks };
|
|
14922
|
+
for (const [collection, dek] of keyring.deks) {
|
|
14923
|
+
mergedDeks[collection] = await wrapKey(dek, newOwner.kek);
|
|
14924
|
+
}
|
|
14925
|
+
const mergedFile = { ...keyringFile, deks: mergedDeks };
|
|
14926
|
+
await adapter.put(vaultName, "_keyring", opts.newOwnerId, { ...env, _data: JSON.stringify(mergedFile) });
|
|
14927
|
+
await vault._getLedgerOrNull()?.append({
|
|
14928
|
+
op: "lifecycle",
|
|
14929
|
+
collection: "",
|
|
14930
|
+
id: "",
|
|
14931
|
+
version: 0,
|
|
14932
|
+
actor: opts.newOwnerId,
|
|
14933
|
+
payloadHash: "",
|
|
14934
|
+
reason: `liberation-claimed:${opts.newOwnerId}:${opts.legalBasis}`
|
|
14935
|
+
});
|
|
14936
|
+
const marker = await loadDeedMarker(adapter, vaultName);
|
|
14937
|
+
if (marker) {
|
|
14938
|
+
await saveDeedMarker(adapter, vaultName, { ...marker, liberatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
14939
|
+
}
|
|
14940
|
+
return { snapshot };
|
|
14941
|
+
}
|
|
14942
|
+
async function listOperationalCollections(vault) {
|
|
14943
|
+
const { keyring } = vault._introspectState();
|
|
14944
|
+
return [...keyring.deks.keys()].filter((c) => !c.startsWith("_"));
|
|
14945
|
+
}
|
|
14946
|
+
var init_liberate = __esm({
|
|
14947
|
+
"src/custody/liberate.ts"() {
|
|
14948
|
+
"use strict";
|
|
14949
|
+
init_errors();
|
|
14950
|
+
init_crypto();
|
|
14951
|
+
init_keyring();
|
|
14952
|
+
init_withdraw_accessible();
|
|
14953
|
+
init_deed();
|
|
14954
|
+
}
|
|
14955
|
+
});
|
|
14956
|
+
|
|
14822
14957
|
// src/persisted-schemas/canonicalize.ts
|
|
14823
14958
|
function canonicalize(value) {
|
|
14824
14959
|
if (value === null || typeof value !== "object") {
|
|
@@ -14868,8 +15003,8 @@ async function derivePersistedSchema(validator) {
|
|
|
14868
15003
|
if (kind === "Zod") {
|
|
14869
15004
|
const convert = await loadZodConverter();
|
|
14870
15005
|
const jsonSchema = convert(validator);
|
|
14871
|
-
const
|
|
14872
|
-
const hash = await sha256Hex2(new TextEncoder().encode(
|
|
15006
|
+
const canonical = canonicalize(jsonSchema);
|
|
15007
|
+
const hash = await sha256Hex2(new TextEncoder().encode(canonical));
|
|
14873
15008
|
return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt };
|
|
14874
15009
|
}
|
|
14875
15010
|
return {
|
|
@@ -15912,7 +16047,7 @@ var init_read_only_facade = __esm({
|
|
|
15912
16047
|
|
|
15913
16048
|
// src/derivations/strategy-hash.ts
|
|
15914
16049
|
async function computeStrategyHash(source, outputKeys, derive, sources) {
|
|
15915
|
-
const
|
|
16050
|
+
const canonical = JSON.stringify({
|
|
15916
16051
|
source,
|
|
15917
16052
|
outputs: [...outputKeys].sort(),
|
|
15918
16053
|
derive: derive.toString(),
|
|
@@ -15921,7 +16056,7 @@ async function computeStrategyHash(source, outputKeys, derive, sources) {
|
|
|
15921
16056
|
// so strategies without siblings keep their existing hash.
|
|
15922
16057
|
...sources?.length ? { sources: [...sources].sort() } : {}
|
|
15923
16058
|
});
|
|
15924
|
-
const bytes = new TextEncoder().encode(
|
|
16059
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
15925
16060
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
15926
16061
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15927
16062
|
}
|
|
@@ -16254,6 +16389,8 @@ var init_vault = __esm({
|
|
|
16254
16389
|
init_blob_compaction();
|
|
16255
16390
|
init_magic_link_grant();
|
|
16256
16391
|
init_api();
|
|
16392
|
+
init_custody();
|
|
16393
|
+
init_liberate();
|
|
16257
16394
|
init_register();
|
|
16258
16395
|
init_gate();
|
|
16259
16396
|
init_fence_controller();
|
|
@@ -16361,6 +16498,18 @@ var init_vault = __esm({
|
|
|
16361
16498
|
* @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
|
|
16362
16499
|
*/
|
|
16363
16500
|
user;
|
|
16501
|
+
/**
|
|
16502
|
+
* FR-6 custody API — the sovereign-custody surface, mirroring `vault.user.*`.
|
|
16503
|
+
*
|
|
16504
|
+
* - `grantCustodian(opts)` / `revokeCustodian(opts)` — owner-only: mint /
|
|
16505
|
+
* remove a `custodian` who operates the vault fully but can never grant /
|
|
16506
|
+
* rotate / sever / extract.
|
|
16507
|
+
* - `liberate(opts)` — custodian-only: the audited claim of ownership over a
|
|
16508
|
+
* sealed-owner (Deed) vault (mints a DISTINCT new owner; ledger-audited).
|
|
16509
|
+
*
|
|
16510
|
+
* @see docs/superpowers/specs/2026-06-17-fr6-deed-custodian-liberate-design.md
|
|
16511
|
+
*/
|
|
16512
|
+
custody;
|
|
16364
16513
|
/**
|
|
16365
16514
|
* Optional callback that re-derives an UnlockedKeyring from the
|
|
16366
16515
|
* adapter using the active user's passphrase. Called by `load()`
|
|
@@ -16571,6 +16720,11 @@ var init_vault = __esm({
|
|
|
16571
16720
|
(requestId, opts2) => approveWithdrawal(this, requestId, opts2),
|
|
16572
16721
|
(requestId, opts2) => rejectWithdrawal(this, requestId, opts2)
|
|
16573
16722
|
);
|
|
16723
|
+
this.custody = new CustodyApi(
|
|
16724
|
+
(options, factors) => this.noydb.grantCustodian(this.name, options, factors),
|
|
16725
|
+
(options, factors) => this.noydb.revokeCustodian(this.name, options, factors),
|
|
16726
|
+
(opts2) => liberateVault(this, opts2)
|
|
16727
|
+
);
|
|
16574
16728
|
}
|
|
16575
16729
|
/**
|
|
16576
16730
|
* Construct (or reconstruct) the lazy DEK resolver. Captures the
|
|
@@ -16798,6 +16952,7 @@ var init_vault = __esm({
|
|
|
16798
16952
|
}
|
|
16799
16953
|
collOpts.perRecordKeys = true;
|
|
16800
16954
|
}
|
|
16955
|
+
if (options?.provenance !== void 0) collOpts.provenance = options.provenance;
|
|
16801
16956
|
if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
|
|
16802
16957
|
if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
|
|
16803
16958
|
collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
|
|
@@ -18620,7 +18775,7 @@ var init_vault = __esm({
|
|
|
18620
18775
|
if (this.activeElevation) {
|
|
18621
18776
|
throw new AlreadyElevatedError(this.activeElevation.tier);
|
|
18622
18777
|
}
|
|
18623
|
-
if (this.keyring.role !== "owner" && this.keyring.role !== "admin") {
|
|
18778
|
+
if (this.keyring.role !== "owner" && this.keyring.role !== "admin" && this.keyring.role !== "custodian") {
|
|
18624
18779
|
const suffix = `#${tier}`;
|
|
18625
18780
|
let found = false;
|
|
18626
18781
|
for (const k of this.keyring.deks.keys()) {
|
|
@@ -20822,997 +20977,6 @@ var init_executor3 = __esm({
|
|
|
20822
20977
|
}
|
|
20823
20978
|
});
|
|
20824
20979
|
|
|
20825
|
-
// src/federation/schema-manifest.ts
|
|
20826
|
-
function captureBlueprint(configure) {
|
|
20827
|
-
const recorded = [];
|
|
20828
|
-
const collectionStub = new Proxy(
|
|
20829
|
-
{},
|
|
20830
|
-
{
|
|
20831
|
-
get: () => () => collectionStub
|
|
20832
|
-
}
|
|
20833
|
-
);
|
|
20834
|
-
const proxy = new Proxy(
|
|
20835
|
-
{},
|
|
20836
|
-
{
|
|
20837
|
-
get: (_t, prop) => {
|
|
20838
|
-
if (prop === "collection") {
|
|
20839
|
-
return (name, opts) => {
|
|
20840
|
-
recorded.push({
|
|
20841
|
-
name,
|
|
20842
|
-
indexes: opts?.indexes ?? [],
|
|
20843
|
-
persistJsonSchema: !!opts?.persistJsonSchema
|
|
20844
|
-
});
|
|
20845
|
-
return collectionStub;
|
|
20846
|
-
};
|
|
20847
|
-
}
|
|
20848
|
-
return () => proxy;
|
|
20849
|
-
}
|
|
20850
|
-
}
|
|
20851
|
-
);
|
|
20852
|
-
configure(proxy);
|
|
20853
|
-
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
20854
|
-
const indexes = {};
|
|
20855
|
-
const persistJsonSchema = [];
|
|
20856
|
-
for (const c of sorted) {
|
|
20857
|
-
indexes[c.name] = c.indexes;
|
|
20858
|
-
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
20859
|
-
}
|
|
20860
|
-
return {
|
|
20861
|
-
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
20862
|
-
// iterating `sorted` (collections in name order).
|
|
20863
|
-
collections: sorted.map((c) => c.name),
|
|
20864
|
-
indexes,
|
|
20865
|
-
persistJsonSchema
|
|
20866
|
-
};
|
|
20867
|
-
}
|
|
20868
|
-
function canonical(value) {
|
|
20869
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
20870
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
20871
|
-
const obj = value;
|
|
20872
|
-
const keys = Object.keys(obj).sort();
|
|
20873
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
20874
|
-
}
|
|
20875
|
-
async function fingerprintBlueprint(bp) {
|
|
20876
|
-
return sha256Hex2(new TextEncoder().encode(canonical(bp)));
|
|
20877
|
-
}
|
|
20878
|
-
var init_schema_manifest = __esm({
|
|
20879
|
-
"src/federation/schema-manifest.ts"() {
|
|
20880
|
-
"use strict";
|
|
20881
|
-
init_crypto();
|
|
20882
|
-
}
|
|
20883
|
-
});
|
|
20884
|
-
|
|
20885
|
-
// src/federation/state-vault.ts
|
|
20886
|
-
var state_vault_exports = {};
|
|
20887
|
-
__export(state_vault_exports, {
|
|
20888
|
-
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
20889
|
-
StateManagementVault: () => StateManagementVault
|
|
20890
|
-
});
|
|
20891
|
-
var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, StateManagementVault;
|
|
20892
|
-
var init_state_vault = __esm({
|
|
20893
|
-
"src/federation/state-vault.ts"() {
|
|
20894
|
-
"use strict";
|
|
20895
|
-
init_schema_manifest();
|
|
20896
|
-
init_constants2();
|
|
20897
|
-
init_ulid();
|
|
20898
|
-
init_constants2();
|
|
20899
|
-
REGISTRY = "vaultRegistry";
|
|
20900
|
-
MANIFEST = "schemaManifest";
|
|
20901
|
-
EVENTS = "deploymentEvents";
|
|
20902
|
-
MIGRATION_STATUS = "migrationStatus";
|
|
20903
|
-
StateManagementVault = class _StateManagementVault {
|
|
20904
|
-
constructor(registry, schemaManifest, events, migrationStatus) {
|
|
20905
|
-
this.registry = registry;
|
|
20906
|
-
this.schemaManifest = schemaManifest;
|
|
20907
|
-
this.#events = events;
|
|
20908
|
-
this.#migrationStatus = migrationStatus;
|
|
20909
|
-
}
|
|
20910
|
-
registry;
|
|
20911
|
-
schemaManifest;
|
|
20912
|
-
/**
|
|
20913
|
-
* The append-only deployment-events log is kept truly private so the raw
|
|
20914
|
-
* mutable Collection is never surfaced — events may only be written via
|
|
20915
|
-
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
20916
|
-
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
20917
|
-
*/
|
|
20918
|
-
#events;
|
|
20919
|
-
/** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
|
|
20920
|
-
#migrationStatus;
|
|
20921
|
-
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
20922
|
-
static async open(db) {
|
|
20923
|
-
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
20924
|
-
return new _StateManagementVault(
|
|
20925
|
-
vault.collection(REGISTRY),
|
|
20926
|
-
vault.collection(MANIFEST),
|
|
20927
|
-
vault.collection(EVENTS),
|
|
20928
|
-
vault.collection(MIGRATION_STATUS)
|
|
20929
|
-
);
|
|
20930
|
-
}
|
|
20931
|
-
/** Read one shard's migration status (or null). */
|
|
20932
|
-
async getMigrationStatus(vaultId) {
|
|
20933
|
-
return this.#migrationStatus.get(vaultId);
|
|
20934
|
-
}
|
|
20935
|
-
/** All migration-status rows (hydrates first). */
|
|
20936
|
-
async listMigrationStatus() {
|
|
20937
|
-
await this.#migrationStatus.list();
|
|
20938
|
-
return this.#migrationStatus.query().toArray();
|
|
20939
|
-
}
|
|
20940
|
-
/** Upsert one shard's migration status (keyed by vaultId). */
|
|
20941
|
-
async upsertMigrationStatus(row) {
|
|
20942
|
-
await this.#migrationStatus.put(row.vaultId, row);
|
|
20943
|
-
}
|
|
20944
|
-
/** Read-only query over the append-only deployment-events log. */
|
|
20945
|
-
queryEvents() {
|
|
20946
|
-
return this.#events.query();
|
|
20947
|
-
}
|
|
20948
|
-
/**
|
|
20949
|
-
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
20950
|
-
* only write path to the events log; no update/delete is exposed.
|
|
20951
|
-
* Callers should treat failures as non-fatal — this method does not
|
|
20952
|
-
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
20953
|
-
*/
|
|
20954
|
-
async appendEvent(event) {
|
|
20955
|
-
const ts = event.ts ?? Date.now();
|
|
20956
|
-
const id = generateULID();
|
|
20957
|
-
await this.#events.put(id, { ...event, id, ts });
|
|
20958
|
-
}
|
|
20959
|
-
/**
|
|
20960
|
-
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
20961
|
-
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
20962
|
-
* the template's declared shape (stable across calls), though each call
|
|
20963
|
-
* refreshes `recordedAt`.
|
|
20964
|
-
*/
|
|
20965
|
-
async recordManifest(templateName, template) {
|
|
20966
|
-
const bp = captureBlueprint(template.configure);
|
|
20967
|
-
const fingerprint = await fingerprintBlueprint(bp);
|
|
20968
|
-
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
20969
|
-
templateName,
|
|
20970
|
-
version: template.version,
|
|
20971
|
-
collections: bp.collections,
|
|
20972
|
-
indexes: bp.indexes,
|
|
20973
|
-
persistJsonSchema: bp.persistJsonSchema,
|
|
20974
|
-
fingerprint,
|
|
20975
|
-
recordedAt: Date.now()
|
|
20976
|
-
});
|
|
20977
|
-
return fingerprint;
|
|
20978
|
-
}
|
|
20979
|
-
/**
|
|
20980
|
-
* True when `template`'s current declared shape does not match the recorded
|
|
20981
|
-
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
20982
|
-
* schema state independent of their template, this catches "a template's
|
|
20983
|
-
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
20984
|
-
* A missing manifest is treated as drift (nothing to verify against).
|
|
20985
|
-
*/
|
|
20986
|
-
async detectDrift(templateName, template) {
|
|
20987
|
-
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
20988
|
-
if (!row) return true;
|
|
20989
|
-
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
20990
|
-
return current !== row.fingerprint;
|
|
20991
|
-
}
|
|
20992
|
-
};
|
|
20993
|
-
}
|
|
20994
|
-
});
|
|
20995
|
-
|
|
20996
|
-
// src/federation/classify-skip.ts
|
|
20997
|
-
function classifyShardSkip(err) {
|
|
20998
|
-
return err instanceof NoAccessError ? "no-grant" : "error";
|
|
20999
|
-
}
|
|
21000
|
-
var init_classify_skip = __esm({
|
|
21001
|
-
"src/federation/classify-skip.ts"() {
|
|
21002
|
-
"use strict";
|
|
21003
|
-
init_errors();
|
|
21004
|
-
}
|
|
21005
|
-
});
|
|
21006
|
-
|
|
21007
|
-
// src/federation/cross-shard-join.ts
|
|
21008
|
-
function coerceKey(value) {
|
|
21009
|
-
if (value === null || value === void 0) return null;
|
|
21010
|
-
if (typeof value === "string") return value;
|
|
21011
|
-
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
21012
|
-
return null;
|
|
21013
|
-
}
|
|
21014
|
-
function warnOnceBroadcastMiss(field, as, key) {
|
|
21015
|
-
const dedup = `${field}\u2192${as}:${key}`;
|
|
21016
|
-
if (warnedBroadcastKeys.has(dedup)) return;
|
|
21017
|
-
warnedBroadcastKeys.add(dedup);
|
|
21018
|
-
console.warn(
|
|
21019
|
-
`[noy-db] broadcastJoin: no "${as}" dimension row for ${field}="${key}". Attaching null. Use mode: 'cascade' to silence.`
|
|
21020
|
-
);
|
|
21021
|
-
}
|
|
21022
|
-
async function applyBroadcastLegs(rows, legs) {
|
|
21023
|
-
if (legs.length === 0) return [...rows];
|
|
21024
|
-
const indexes = [];
|
|
21025
|
-
for (const leg of legs) {
|
|
21026
|
-
const map = /* @__PURE__ */ new Map();
|
|
21027
|
-
for (const rec of await leg.from.list()) {
|
|
21028
|
-
const k = coerceKey(readPath(rec, leg.on));
|
|
21029
|
-
if (k !== null && !map.has(k)) map.set(k, rec);
|
|
21030
|
-
}
|
|
21031
|
-
indexes.push({ leg, map });
|
|
21032
|
-
}
|
|
21033
|
-
return rows.map((row) => {
|
|
21034
|
-
const out = { ...row };
|
|
21035
|
-
for (const { leg, map } of indexes) {
|
|
21036
|
-
const key = coerceKey(readPath(row, leg.field));
|
|
21037
|
-
const match = key === null ? null : map.get(key) ?? null;
|
|
21038
|
-
if (match === null && leg.mode === "warn") {
|
|
21039
|
-
warnOnceBroadcastMiss(leg.field, leg.as, key ?? "<null>");
|
|
21040
|
-
}
|
|
21041
|
-
out[leg.as] = match;
|
|
21042
|
-
}
|
|
21043
|
-
return out;
|
|
21044
|
-
});
|
|
21045
|
-
}
|
|
21046
|
-
var warnedBroadcastKeys;
|
|
21047
|
-
var init_cross_shard_join = __esm({
|
|
21048
|
-
"src/federation/cross-shard-join.ts"() {
|
|
21049
|
-
"use strict";
|
|
21050
|
-
init_predicate();
|
|
21051
|
-
warnedBroadcastKeys = /* @__PURE__ */ new Set();
|
|
21052
|
-
}
|
|
21053
|
-
});
|
|
21054
|
-
|
|
21055
|
-
// src/federation/cross-vault-live.ts
|
|
21056
|
-
var CrossVaultLive;
|
|
21057
|
-
var init_cross_vault_live = __esm({
|
|
21058
|
-
"src/federation/cross-vault-live.ts"() {
|
|
21059
|
-
"use strict";
|
|
21060
|
-
CrossVaultLive = class {
|
|
21061
|
-
snapshot;
|
|
21062
|
-
error = null;
|
|
21063
|
-
ready;
|
|
21064
|
-
subs = /* @__PURE__ */ new Set();
|
|
21065
|
-
unsubChange;
|
|
21066
|
-
opts;
|
|
21067
|
-
stopped = false;
|
|
21068
|
-
computing = false;
|
|
21069
|
-
dirty = false;
|
|
21070
|
-
scheduled = false;
|
|
21071
|
-
timer = null;
|
|
21072
|
-
resolveReady;
|
|
21073
|
-
settledOnce = false;
|
|
21074
|
-
constructor(opts) {
|
|
21075
|
-
this.opts = opts;
|
|
21076
|
-
this.snapshot = opts.initialSnapshot;
|
|
21077
|
-
this.ready = new Promise((res) => {
|
|
21078
|
-
this.resolveReady = res;
|
|
21079
|
-
});
|
|
21080
|
-
this.unsubChange = opts.subscribeToChanges((e) => {
|
|
21081
|
-
if (this.stopped || !opts.isRelevant(e)) return;
|
|
21082
|
-
this.schedule();
|
|
21083
|
-
});
|
|
21084
|
-
this.schedule();
|
|
21085
|
-
}
|
|
21086
|
-
subscribe(cb) {
|
|
21087
|
-
if (this.stopped) return () => {
|
|
21088
|
-
};
|
|
21089
|
-
this.subs.add(cb);
|
|
21090
|
-
return () => this.subs.delete(cb);
|
|
21091
|
-
}
|
|
21092
|
-
stop() {
|
|
21093
|
-
if (this.stopped) return;
|
|
21094
|
-
this.stopped = true;
|
|
21095
|
-
this.unsubChange();
|
|
21096
|
-
if (this.timer !== null) clearTimeout(this.timer);
|
|
21097
|
-
this.subs.clear();
|
|
21098
|
-
if (!this.settledOnce) this.resolveReady();
|
|
21099
|
-
}
|
|
21100
|
-
schedule() {
|
|
21101
|
-
if (this.stopped) return;
|
|
21102
|
-
if (this.computing) {
|
|
21103
|
-
this.dirty = true;
|
|
21104
|
-
return;
|
|
21105
|
-
}
|
|
21106
|
-
if (this.scheduled) return;
|
|
21107
|
-
this.scheduled = true;
|
|
21108
|
-
const run = () => {
|
|
21109
|
-
this.scheduled = false;
|
|
21110
|
-
void this.runCompute();
|
|
21111
|
-
};
|
|
21112
|
-
const ms = this.opts.debounceMs ?? 0;
|
|
21113
|
-
if (ms > 0) this.timer = setTimeout(run, ms);
|
|
21114
|
-
else queueMicrotask(run);
|
|
21115
|
-
}
|
|
21116
|
-
async runCompute() {
|
|
21117
|
-
if (this.stopped) return;
|
|
21118
|
-
this.computing = true;
|
|
21119
|
-
this.dirty = false;
|
|
21120
|
-
try {
|
|
21121
|
-
const next = await this.opts.compute();
|
|
21122
|
-
if (this.stopped) return;
|
|
21123
|
-
this.snapshot = next;
|
|
21124
|
-
this.error = null;
|
|
21125
|
-
} catch (err) {
|
|
21126
|
-
if (this.stopped) return;
|
|
21127
|
-
this.error = err instanceof Error ? err : new Error(String(err));
|
|
21128
|
-
} finally {
|
|
21129
|
-
this.computing = false;
|
|
21130
|
-
if (!this.stopped) {
|
|
21131
|
-
if (!this.settledOnce) {
|
|
21132
|
-
this.settledOnce = true;
|
|
21133
|
-
this.resolveReady();
|
|
21134
|
-
}
|
|
21135
|
-
for (const cb of this.subs) cb();
|
|
21136
|
-
if (this.dirty) this.schedule();
|
|
21137
|
-
}
|
|
21138
|
-
}
|
|
21139
|
-
}
|
|
21140
|
-
};
|
|
21141
|
-
}
|
|
21142
|
-
});
|
|
21143
|
-
|
|
21144
|
-
// src/federation/aggregate-across.ts
|
|
21145
|
-
var CrossVaultAggregation, CrossVaultGroupedAggregation;
|
|
21146
|
-
var init_aggregate_across = __esm({
|
|
21147
|
-
"src/federation/aggregate-across.ts"() {
|
|
21148
|
-
"use strict";
|
|
21149
|
-
init_aggregation();
|
|
21150
|
-
init_groupby();
|
|
21151
|
-
init_cross_vault_live();
|
|
21152
|
-
CrossVaultAggregation = class {
|
|
21153
|
-
constructor(src, spec, bind) {
|
|
21154
|
-
this.src = src;
|
|
21155
|
-
this.spec = spec;
|
|
21156
|
-
this.bind = bind;
|
|
21157
|
-
}
|
|
21158
|
-
src;
|
|
21159
|
-
spec;
|
|
21160
|
-
bind;
|
|
21161
|
-
async run(options = {}) {
|
|
21162
|
-
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
21163
|
-
return { result: reduceRecords(records, this.spec), skippedVaults };
|
|
21164
|
-
}
|
|
21165
|
-
live(options = {}) {
|
|
21166
|
-
if (!this.bind) throw new Error("CrossVaultAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.aggregate()");
|
|
21167
|
-
const spec = this.spec;
|
|
21168
|
-
const src = this.src;
|
|
21169
|
-
const core = new CrossVaultLive({
|
|
21170
|
-
subscribeToChanges: this.bind.subscribeToChanges,
|
|
21171
|
-
isRelevant: this.bind.isRelevant,
|
|
21172
|
-
compute: async () => {
|
|
21173
|
-
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
21174
|
-
return { value: reduceRecords(records, spec), skipped: skippedVaults };
|
|
21175
|
-
},
|
|
21176
|
-
initialSnapshot: { value: void 0, skipped: [] },
|
|
21177
|
-
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
21178
|
-
});
|
|
21179
|
-
return {
|
|
21180
|
-
get value() {
|
|
21181
|
-
return core.snapshot.value;
|
|
21182
|
-
},
|
|
21183
|
-
get skippedVaults() {
|
|
21184
|
-
return core.snapshot.skipped;
|
|
21185
|
-
},
|
|
21186
|
-
get error() {
|
|
21187
|
-
return core.error;
|
|
21188
|
-
},
|
|
21189
|
-
ready: core.ready,
|
|
21190
|
-
subscribe: (cb) => core.subscribe(cb),
|
|
21191
|
-
stop: () => core.stop()
|
|
21192
|
-
};
|
|
21193
|
-
}
|
|
21194
|
-
};
|
|
21195
|
-
CrossVaultGroupedAggregation = class {
|
|
21196
|
-
constructor(src, field, spec, bind) {
|
|
21197
|
-
this.src = src;
|
|
21198
|
-
this.field = field;
|
|
21199
|
-
this.spec = spec;
|
|
21200
|
-
this.bind = bind;
|
|
21201
|
-
}
|
|
21202
|
-
src;
|
|
21203
|
-
field;
|
|
21204
|
-
spec;
|
|
21205
|
-
bind;
|
|
21206
|
-
async run(options = {}) {
|
|
21207
|
-
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
21208
|
-
return {
|
|
21209
|
-
results: groupAndReduce(records, this.field, this.spec),
|
|
21210
|
-
skippedVaults
|
|
21211
|
-
};
|
|
21212
|
-
}
|
|
21213
|
-
live(options = {}) {
|
|
21214
|
-
if (!this.bind) throw new Error("CrossVaultGroupedAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.groupBy().aggregate()");
|
|
21215
|
-
const field = this.field;
|
|
21216
|
-
const spec = this.spec;
|
|
21217
|
-
const src = this.src;
|
|
21218
|
-
const core = new CrossVaultLive({
|
|
21219
|
-
subscribeToChanges: this.bind.subscribeToChanges,
|
|
21220
|
-
isRelevant: this.bind.isRelevant,
|
|
21221
|
-
compute: async () => {
|
|
21222
|
-
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
21223
|
-
return {
|
|
21224
|
-
records: groupAndReduce(records, field, spec),
|
|
21225
|
-
skipped: skippedVaults
|
|
21226
|
-
};
|
|
21227
|
-
},
|
|
21228
|
-
initialSnapshot: { records: [], skipped: [] },
|
|
21229
|
-
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
21230
|
-
});
|
|
21231
|
-
return {
|
|
21232
|
-
get value() {
|
|
21233
|
-
return core.snapshot.records;
|
|
21234
|
-
},
|
|
21235
|
-
get skippedVaults() {
|
|
21236
|
-
return core.snapshot.skipped;
|
|
21237
|
-
},
|
|
21238
|
-
get error() {
|
|
21239
|
-
return core.error;
|
|
21240
|
-
},
|
|
21241
|
-
ready: core.ready,
|
|
21242
|
-
subscribe: (cb) => core.subscribe(cb),
|
|
21243
|
-
stop: () => core.stop()
|
|
21244
|
-
};
|
|
21245
|
-
}
|
|
21246
|
-
};
|
|
21247
|
-
}
|
|
21248
|
-
});
|
|
21249
|
-
|
|
21250
|
-
// src/federation/vault-group.ts
|
|
21251
|
-
var vault_group_exports = {};
|
|
21252
|
-
__export(vault_group_exports, {
|
|
21253
|
-
ShardedCollection: () => ShardedCollection,
|
|
21254
|
-
ShardedGroupedQuery: () => ShardedGroupedQuery,
|
|
21255
|
-
ShardedQuery: () => ShardedQuery,
|
|
21256
|
-
VaultGroup: () => VaultGroup
|
|
21257
|
-
});
|
|
21258
|
-
function assertSafePartitionKey(partitionKey) {
|
|
21259
|
-
if (partitionKey.length === 0) {
|
|
21260
|
-
throw new ValidationError("partitionKey must be a non-empty string");
|
|
21261
|
-
}
|
|
21262
|
-
if (partitionKey === STATE_VAULT_NAME) {
|
|
21263
|
-
throw new ReservedVaultNameError(partitionKey);
|
|
21264
|
-
}
|
|
21265
|
-
if (!SAFE_PARTITION_KEY.test(partitionKey)) {
|
|
21266
|
-
throw new ValidationError(
|
|
21267
|
-
`partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
|
|
21268
|
-
);
|
|
21269
|
-
}
|
|
21270
|
-
if (partitionKey.includes(SHARD_SEPARATOR)) {
|
|
21271
|
-
throw new ValidationError(
|
|
21272
|
-
`partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
|
|
21273
|
-
);
|
|
21274
|
-
}
|
|
21275
|
-
}
|
|
21276
|
-
var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
|
|
21277
|
-
var init_vault_group = __esm({
|
|
21278
|
-
"src/federation/vault-group.ts"() {
|
|
21279
|
-
"use strict";
|
|
21280
|
-
init_state_vault();
|
|
21281
|
-
init_errors();
|
|
21282
|
-
init_constants2();
|
|
21283
|
-
init_classify_skip();
|
|
21284
|
-
init_cross_shard_join();
|
|
21285
|
-
init_cross_vault_live();
|
|
21286
|
-
init_aggregate_across();
|
|
21287
|
-
SHARD_SEPARATOR = "--";
|
|
21288
|
-
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
21289
|
-
VaultGroup = class {
|
|
21290
|
-
constructor(db, name, registry, sharding, template, migrateOnOpen = false) {
|
|
21291
|
-
this.db = db;
|
|
21292
|
-
this.name = name;
|
|
21293
|
-
this.registry = registry;
|
|
21294
|
-
this.sharding = sharding;
|
|
21295
|
-
this.template = template;
|
|
21296
|
-
this.migrateOnOpen = migrateOnOpen;
|
|
21297
|
-
if (name.includes(SHARD_SEPARATOR)) {
|
|
21298
|
-
throw new ValidationError(
|
|
21299
|
-
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
21300
|
-
);
|
|
21301
|
-
}
|
|
21302
|
-
}
|
|
21303
|
-
db;
|
|
21304
|
-
name;
|
|
21305
|
-
registry;
|
|
21306
|
-
sharding;
|
|
21307
|
-
template;
|
|
21308
|
-
migrateOnOpen;
|
|
21309
|
-
/** @internal — set when the group is managed (no explicit registry). */
|
|
21310
|
-
stateVault;
|
|
21311
|
-
/** @internal */
|
|
21312
|
-
_attachStateVault(sv) {
|
|
21313
|
-
this.stateVault = sv;
|
|
21314
|
-
}
|
|
21315
|
-
/** Deterministic vault name for a partition key, namespaced by the group. */
|
|
21316
|
-
shardVaultId(partitionKey) {
|
|
21317
|
-
assertSafePartitionKey(partitionKey);
|
|
21318
|
-
return `${this.name}${SHARD_SEPARATOR}${partitionKey}`;
|
|
21319
|
-
}
|
|
21320
|
-
/**
|
|
21321
|
-
* @internal — group-qualified registry record key (avoids cross-group key
|
|
21322
|
-
* collisions). Identical to the shard vault id by design — the registry row
|
|
21323
|
-
* for a shard is keyed by that shard's vault id — so it delegates to
|
|
21324
|
-
* `shardVaultId`, reusing its partition-key validation.
|
|
21325
|
-
*/
|
|
21326
|
-
registryId(partitionKey) {
|
|
21327
|
-
return this.shardVaultId(partitionKey);
|
|
21328
|
-
}
|
|
21329
|
-
/**
|
|
21330
|
-
* Registry rows for THIS group (hydrates the registry collection first).
|
|
21331
|
-
* The registry may be shared across groups (the auto-wired StateManagement
|
|
21332
|
-
* vault holds one `vaultRegistry` for the whole instance), so rows are
|
|
21333
|
-
* filtered by `group` — without this, a group's fan-out reads would leak
|
|
21334
|
-
* across into other groups' shards. Mirrors the `${group}--` scoping that
|
|
21335
|
-
* `liveBinding().isRelevant` already applies to the reactive path.
|
|
21336
|
-
*/
|
|
21337
|
-
async allRows() {
|
|
21338
|
-
await this.registry.list();
|
|
21339
|
-
const rows = this.registry.query().toArray();
|
|
21340
|
-
return rows.filter((r) => r.group === this.name);
|
|
21341
|
-
}
|
|
21342
|
-
/**
|
|
21343
|
-
* Open an existing shard and apply the template. When `migrateOnOpen` is set
|
|
21344
|
-
* (#271) and the shard's registry version is behind the template, its cutover
|
|
21345
|
-
* runs inline first — so a behind shard never surfaces a stale handle.
|
|
21346
|
-
*/
|
|
21347
|
-
async openShard(partitionKey) {
|
|
21348
|
-
if (this.migrateOnOpen) {
|
|
21349
|
-
const row = await this.registry.get(this.registryId(partitionKey));
|
|
21350
|
-
if (row && row.schemaVersion < this.template.version) {
|
|
21351
|
-
await this.migrateShard(partitionKey);
|
|
21352
|
-
}
|
|
21353
|
-
}
|
|
21354
|
-
return this._openShardRaw(partitionKey);
|
|
21355
|
-
}
|
|
21356
|
-
/** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
|
|
21357
|
-
async _openShardRaw(partitionKey) {
|
|
21358
|
-
const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
|
|
21359
|
-
this.template.configure(vault);
|
|
21360
|
-
return vault;
|
|
21361
|
-
}
|
|
21362
|
-
/**
|
|
21363
|
-
* Idempotently provision a shard for `partitionKey`. Returns the
|
|
21364
|
-
* configured vault handle.
|
|
21365
|
-
*
|
|
21366
|
-
* - row + vault present → no-op, return handle
|
|
21367
|
-
* - row present, vault gone → ShardProvisioningError
|
|
21368
|
-
* - row absent (vault present or not) → open-or-create, configure, write row
|
|
21369
|
-
*
|
|
21370
|
-
* When `region` is given (the routing `put` passes `sharding.regionOf(record)`),
|
|
21371
|
-
* the candidate backend's `capabilities.region` must match or this throws
|
|
21372
|
-
* `DataResidencyError` BEFORE provisioning (#271 data-residency guard).
|
|
21373
|
-
*/
|
|
21374
|
-
async createShard(partitionKey, region) {
|
|
21375
|
-
const vaultId = this.shardVaultId(partitionKey);
|
|
21376
|
-
const row = await this.registry.get(this.registryId(partitionKey));
|
|
21377
|
-
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
21378
|
-
if (row && !provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
|
|
21379
|
-
if (row && provisioned) return this.openShard(partitionKey);
|
|
21380
|
-
if (region !== void 0) {
|
|
21381
|
-
const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
|
|
21382
|
-
if (backendRegion !== region) throw new DataResidencyError(vaultId, region, backendRegion);
|
|
21383
|
-
}
|
|
21384
|
-
const vault = await this.db.openVault(vaultId);
|
|
21385
|
-
this.template.configure(vault);
|
|
21386
|
-
await this.registry.put(this.registryId(partitionKey), {
|
|
21387
|
-
vaultId,
|
|
21388
|
-
partitionKey,
|
|
21389
|
-
templateName: this.sharding.vaultTemplate,
|
|
21390
|
-
schemaVersion: this.template.version,
|
|
21391
|
-
createdAt: Date.now(),
|
|
21392
|
-
group: this.name
|
|
21393
|
-
});
|
|
21394
|
-
if (this.stateVault) {
|
|
21395
|
-
try {
|
|
21396
|
-
await this.stateVault.appendEvent({
|
|
21397
|
-
type: "shard-created",
|
|
21398
|
-
group: this.name,
|
|
21399
|
-
vaultId,
|
|
21400
|
-
templateName: this.sharding.vaultTemplate,
|
|
21401
|
-
version: this.template.version
|
|
21402
|
-
});
|
|
21403
|
-
} catch {
|
|
21404
|
-
}
|
|
21405
|
-
}
|
|
21406
|
-
return vault;
|
|
21407
|
-
}
|
|
21408
|
-
/**
|
|
21409
|
-
* Drill down to a single shard's full Collection API. Throws if the shard is unknown.
|
|
21410
|
-
* Also throws ShardProvisioningError if the registry row exists but the vault has been deleted
|
|
21411
|
-
* (registry/store divergence).
|
|
21412
|
-
*/
|
|
21413
|
-
async shard(partitionKey) {
|
|
21414
|
-
const vaultId = this.shardVaultId(partitionKey);
|
|
21415
|
-
const row = await this.registry.get(this.registryId(partitionKey));
|
|
21416
|
-
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
21417
|
-
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
21418
|
-
if (!provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
|
|
21419
|
-
return this.openShard(partitionKey);
|
|
21420
|
-
}
|
|
21421
|
-
/** A sharded view over one logical collection across all shards. */
|
|
21422
|
-
collection(collectionName) {
|
|
21423
|
-
return new ShardedCollection(this, collectionName);
|
|
21424
|
-
}
|
|
21425
|
-
/** @internal — eligible (openable-candidate) rows + drift/divergence skips. */
|
|
21426
|
-
async resolveEligible(options = {}) {
|
|
21427
|
-
const rows = await this.allRows();
|
|
21428
|
-
const skipped = [];
|
|
21429
|
-
const versionOk = [];
|
|
21430
|
-
for (const row of rows) {
|
|
21431
|
-
if (options.minVersion !== void 0 && row.schemaVersion < options.minVersion) {
|
|
21432
|
-
skipped.push({ vaultId: row.vaultId, reason: "schema-drift" });
|
|
21433
|
-
} else versionOk.push(row);
|
|
21434
|
-
}
|
|
21435
|
-
const provisioned = await Promise.all(versionOk.map((r) => this.db._shardVaultProvisioned(r.vaultId)));
|
|
21436
|
-
const eligible = [];
|
|
21437
|
-
versionOk.forEach((row, i) => {
|
|
21438
|
-
if (provisioned[i]) eligible.push(row);
|
|
21439
|
-
else skipped.push({ vaultId: row.vaultId, reason: "error", error: new ShardProvisioningError(row.vaultId, row.partitionKey) });
|
|
21440
|
-
});
|
|
21441
|
-
return { eligible, skipped };
|
|
21442
|
-
}
|
|
21443
|
-
/** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
|
|
21444
|
-
crossVaultDerivations = [];
|
|
21445
|
-
/**
|
|
21446
|
-
* Register a push-model cross-vault derivation — the Insight Vault pattern
|
|
21447
|
-
* (#271, Layer 4). Drive it with {@link refreshInsights}.
|
|
21448
|
-
*
|
|
21449
|
-
* For each shard, `derive(records, ctx)` runs on that shard's `source`
|
|
21450
|
-
* records and its return value is written into the analytics
|
|
21451
|
-
* (`target.vault` / `target.collection`) vault, keyed by partition key —
|
|
21452
|
-
* one summary row per shard. The derivation runs in-process under THIS
|
|
21453
|
-
* group's `Noydb` (which already holds both the shard and Insight Vault
|
|
21454
|
-
* keyrings); the shard's decrypted records are reduced to a summary that is
|
|
21455
|
-
* re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
|
|
21456
|
-
* crosses a DEK boundary.
|
|
21457
|
-
*
|
|
21458
|
-
* **Zero-knowledge note:** the Insight Vault backend sees aggregated
|
|
21459
|
-
* structure (totals, counts, timestamps) drawn from many shards — a weaker
|
|
21460
|
-
* ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
|
|
21461
|
-
* scalars (no embeddings / no raw records).
|
|
21462
|
-
*
|
|
21463
|
-
* v1 is explicit-refresh (no write-path push); call `refreshInsights()`
|
|
21464
|
-
* after a batch of writes, or on a schedule.
|
|
21465
|
-
*
|
|
21466
|
-
* The `target.vault` must NOT be the group itself or one of its shards —
|
|
21467
|
-
* a summary writing back into client-shard data would breach the Insight
|
|
21468
|
-
* Vault's separate-DEK-boundary contract. Such a target throws a
|
|
21469
|
-
* `ValidationError` at registration (#271 Insight-write isolation).
|
|
21470
|
-
*/
|
|
21471
|
-
withCrossVaultDerivation(spec) {
|
|
21472
|
-
const target = spec.target.vault;
|
|
21473
|
-
if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
|
|
21474
|
-
throw new ValidationError(
|
|
21475
|
-
`withCrossVaultDerivation: target.vault "${target}" is the "${this.name}" group itself or one of its shards \u2014 an Insight summary must target a SEPARATE analytics vault, never write back into client-shard data (it would breach the per-shard DEK boundary). Use a distinct vault name.`
|
|
21476
|
-
);
|
|
21477
|
-
}
|
|
21478
|
-
this.crossVaultDerivations.push(spec);
|
|
21479
|
-
}
|
|
21480
|
-
/**
|
|
21481
|
-
* Run every registered {@link withCrossVaultDerivation}: read each eligible
|
|
21482
|
-
* shard's source records, derive a per-shard summary, and write it into the
|
|
21483
|
-
* Insight Vault keyed by partition key. Shards behind `minVersion`,
|
|
21484
|
-
* unprovisioned, or whose read errors are reported in `skippedVaults` and
|
|
21485
|
-
* are not written (a stale summary is never left behind for a failed shard).
|
|
21486
|
-
*/
|
|
21487
|
-
async refreshInsights(options = {}) {
|
|
21488
|
-
if (this.crossVaultDerivations.length === 0) return { written: 0, skippedVaults: [] };
|
|
21489
|
-
const { eligible, skipped } = await this.resolveEligible(
|
|
21490
|
-
options.minVersion !== void 0 ? { minVersion: options.minVersion } : {}
|
|
21491
|
-
);
|
|
21492
|
-
let written = 0;
|
|
21493
|
-
for (const spec of this.crossVaultDerivations) {
|
|
21494
|
-
const results = await this.db.queryAcross(
|
|
21495
|
-
eligible.map((r) => r.vaultId),
|
|
21496
|
-
async (vault) => {
|
|
21497
|
-
this.template.configure(vault);
|
|
21498
|
-
return vault.collection(spec.source).list();
|
|
21499
|
-
},
|
|
21500
|
-
{ create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
|
|
21501
|
-
);
|
|
21502
|
-
const insight = await this.db.openVault(spec.target.vault);
|
|
21503
|
-
const out = insight.collection(spec.target.collection);
|
|
21504
|
-
for (let i = 0; i < eligible.length; i++) {
|
|
21505
|
-
const row = eligible[i];
|
|
21506
|
-
const res = results[i];
|
|
21507
|
-
if (!res || res.result === void 0) {
|
|
21508
|
-
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
21509
|
-
continue;
|
|
21510
|
-
}
|
|
21511
|
-
const ctx = {
|
|
21512
|
-
vaultId: row.vaultId,
|
|
21513
|
-
partitionKey: row.partitionKey,
|
|
21514
|
-
schemaVersion: row.schemaVersion
|
|
21515
|
-
};
|
|
21516
|
-
const summary = spec.derive(res.result, ctx);
|
|
21517
|
-
await out.put(row.partitionKey, summary);
|
|
21518
|
-
written++;
|
|
21519
|
-
}
|
|
21520
|
-
}
|
|
21521
|
-
return { written, skippedVaults: skipped };
|
|
21522
|
-
}
|
|
21523
|
-
/** @internal — the control-plane vault for migration status; lazily opened. */
|
|
21524
|
-
async ensureStateVault() {
|
|
21525
|
-
if (!this.stateVault) this.stateVault = await StateManagementVault.open(this.db);
|
|
21526
|
-
return this.stateVault;
|
|
21527
|
-
}
|
|
21528
|
-
/**
|
|
21529
|
-
* Migrate ONE shard to the template's current version (#271 fleet runner,
|
|
21530
|
-
* per-shard step). Opens the shard (applying the template, which arms the
|
|
21531
|
-
* M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
|
|
21532
|
-
* (the per-vault drain-barrier-transform protocol), then advances the
|
|
21533
|
-
* registry row's `schemaVersion` and records `migration-status`. A shard
|
|
21534
|
-
* already at the template version is a no-op (`status: 'done'`, migrated 0).
|
|
21535
|
-
* Never throws on a cutover failure — it records `status: 'failed'` and
|
|
21536
|
-
* returns the row, so a fleet run continues past a bad shard.
|
|
21537
|
-
*/
|
|
21538
|
-
async migrateShard(partitionKey) {
|
|
21539
|
-
const vaultId = this.shardVaultId(partitionKey);
|
|
21540
|
-
const row = await this.registry.get(this.registryId(partitionKey));
|
|
21541
|
-
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
21542
|
-
const target = this.template.version;
|
|
21543
|
-
const sv = await this.ensureStateVault();
|
|
21544
|
-
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
21545
|
-
if (row.schemaVersion >= target) {
|
|
21546
|
-
const done = { ...base, status: "done", migrated: 0, finishedAt: Date.now() };
|
|
21547
|
-
await sv.upsertMigrationStatus(done);
|
|
21548
|
-
return done;
|
|
21549
|
-
}
|
|
21550
|
-
await sv.upsertMigrationStatus({ ...base, status: "running", startedAt: Date.now() });
|
|
21551
|
-
try {
|
|
21552
|
-
await sv.appendEvent({ type: "migration-started", group: this.name, vaultId, version: target });
|
|
21553
|
-
} catch {
|
|
21554
|
-
}
|
|
21555
|
-
try {
|
|
21556
|
-
const vault = await this._openShardRaw(partitionKey);
|
|
21557
|
-
await vault._drainPendingSchemaWrites();
|
|
21558
|
-
const { migrated } = await vault.runSchemaCutover();
|
|
21559
|
-
await this.registry.put(this.registryId(partitionKey), { ...row, schemaVersion: target });
|
|
21560
|
-
const done = { ...base, currentVersion: target, status: "done", migrated, finishedAt: Date.now() };
|
|
21561
|
-
await sv.upsertMigrationStatus(done);
|
|
21562
|
-
try {
|
|
21563
|
-
await sv.appendEvent({ type: "migration-completed", group: this.name, vaultId, version: target });
|
|
21564
|
-
} catch {
|
|
21565
|
-
}
|
|
21566
|
-
return done;
|
|
21567
|
-
} catch (err) {
|
|
21568
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
21569
|
-
const failed = { ...base, status: "failed", error, finishedAt: Date.now() };
|
|
21570
|
-
await sv.upsertMigrationStatus(failed);
|
|
21571
|
-
try {
|
|
21572
|
-
await sv.appendEvent({ type: "migration-failed", group: this.name, vaultId, version: target, detail: error });
|
|
21573
|
-
} catch {
|
|
21574
|
-
}
|
|
21575
|
-
return failed;
|
|
21576
|
-
}
|
|
21577
|
-
}
|
|
21578
|
-
/**
|
|
21579
|
-
* Active batch runner (#271): migrate every shard behind the template version
|
|
21580
|
-
* to it, in controlled batches. **Resumable + crash-safe** — shards already at
|
|
21581
|
-
* the target are skipped (the registry version is the source of truth), so a
|
|
21582
|
-
* re-run after a crash only picks up the unfinished + previously-failed shards.
|
|
21583
|
-
*
|
|
21584
|
-
* - `cohort` — restrict to these partition keys (the staged / canary rollout:
|
|
21585
|
-
* migrate a small cohort, verify the Insight Vault, then run the rest).
|
|
21586
|
-
* - `batchSize` — max shards migrated concurrently per batch (back-pressure).
|
|
21587
|
-
* Default 4. Batches run sequentially; shards within a batch run in parallel.
|
|
21588
|
-
*/
|
|
21589
|
-
async migrateFleet(options = {}) {
|
|
21590
|
-
const target = this.template.version;
|
|
21591
|
-
const rows = await this.allRows();
|
|
21592
|
-
const cohort = options.cohort;
|
|
21593
|
-
const todo = rows.filter(
|
|
21594
|
-
(r) => r.schemaVersion < target && (cohort === void 0 || cohort.includes(r.partitionKey))
|
|
21595
|
-
);
|
|
21596
|
-
const batchSize = Math.max(1, options.batchSize ?? 4);
|
|
21597
|
-
const migrated = [];
|
|
21598
|
-
const failed = [];
|
|
21599
|
-
for (let i = 0; i < todo.length; i += batchSize) {
|
|
21600
|
-
const batch = todo.slice(i, i + batchSize);
|
|
21601
|
-
const settled = await Promise.all(batch.map((r) => this.migrateShard(r.partitionKey)));
|
|
21602
|
-
for (const res of settled) {
|
|
21603
|
-
if (res.status === "done") migrated.push(res.vaultId);
|
|
21604
|
-
else failed.push({ vaultId: res.vaultId, error: res.error ?? "unknown" });
|
|
21605
|
-
}
|
|
21606
|
-
}
|
|
21607
|
-
return { target, migrated, failed };
|
|
21608
|
-
}
|
|
21609
|
-
};
|
|
21610
|
-
ShardedCollection = class {
|
|
21611
|
-
constructor(group, collectionName) {
|
|
21612
|
-
this.group = group;
|
|
21613
|
-
this.collectionName = collectionName;
|
|
21614
|
-
}
|
|
21615
|
-
group;
|
|
21616
|
-
collectionName;
|
|
21617
|
-
/** Route a write to the shard owning `keyOf(record)`. */
|
|
21618
|
-
async put(id, record) {
|
|
21619
|
-
const key = this.group.sharding.keyOf(record);
|
|
21620
|
-
const row = await this.group.registry.get(this.group.registryId(key));
|
|
21621
|
-
let vault;
|
|
21622
|
-
if (!row) {
|
|
21623
|
-
if (this.group.sharding.autoCreate === false) {
|
|
21624
|
-
throw new UnknownShardError(key, this.group.name);
|
|
21625
|
-
}
|
|
21626
|
-
vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
|
|
21627
|
-
} else {
|
|
21628
|
-
vault = await this.group.openShard(key);
|
|
21629
|
-
}
|
|
21630
|
-
await vault.collection(this.collectionName).put(id, record);
|
|
21631
|
-
}
|
|
21632
|
-
/** Begin a cross-shard fan-out query. */
|
|
21633
|
-
query() {
|
|
21634
|
-
return new ShardedQuery(this.group, this.collectionName, []);
|
|
21635
|
-
}
|
|
21636
|
-
};
|
|
21637
|
-
ShardedQuery = class _ShardedQuery {
|
|
21638
|
-
constructor(group, collectionName, clauses, coPartitionedLegs = [], broadcastLegs = []) {
|
|
21639
|
-
this.group = group;
|
|
21640
|
-
this.collectionName = collectionName;
|
|
21641
|
-
this.clauses = clauses;
|
|
21642
|
-
this.coPartitionedLegs = coPartitionedLegs;
|
|
21643
|
-
this.broadcastLegs = broadcastLegs;
|
|
21644
|
-
}
|
|
21645
|
-
group;
|
|
21646
|
-
collectionName;
|
|
21647
|
-
clauses;
|
|
21648
|
-
coPartitionedLegs;
|
|
21649
|
-
broadcastLegs;
|
|
21650
|
-
where(field, op, value) {
|
|
21651
|
-
return new _ShardedQuery(
|
|
21652
|
-
this.group,
|
|
21653
|
-
this.collectionName,
|
|
21654
|
-
[...this.clauses, { field, op, value }],
|
|
21655
|
-
this.coPartitionedLegs,
|
|
21656
|
-
this.broadcastLegs
|
|
21657
|
-
);
|
|
21658
|
-
}
|
|
21659
|
-
/** Co-partitioned join: each shard joins its own same-vault right collection (resolved via ref()), then union. */
|
|
21660
|
-
crossShardJoin(field, opts) {
|
|
21661
|
-
const leg = { field, as: opts.as, maxRows: opts.maxRows, strategy: opts.strategy };
|
|
21662
|
-
return new _ShardedQuery(
|
|
21663
|
-
this.group,
|
|
21664
|
-
this.collectionName,
|
|
21665
|
-
this.clauses,
|
|
21666
|
-
[...this.coPartitionedLegs, leg],
|
|
21667
|
-
this.broadcastLegs
|
|
21668
|
-
);
|
|
21669
|
-
}
|
|
21670
|
-
/** Broadcast dimension join: enrich every merged row from a single shared collection. */
|
|
21671
|
-
broadcastJoin(field, opts) {
|
|
21672
|
-
const leg = {
|
|
21673
|
-
field,
|
|
21674
|
-
as: opts.as,
|
|
21675
|
-
from: opts.from,
|
|
21676
|
-
on: opts.on ?? "id",
|
|
21677
|
-
mode: opts.mode ?? "warn"
|
|
21678
|
-
};
|
|
21679
|
-
return new _ShardedQuery(
|
|
21680
|
-
this.group,
|
|
21681
|
-
this.collectionName,
|
|
21682
|
-
this.clauses,
|
|
21683
|
-
this.coPartitionedLegs,
|
|
21684
|
-
[...this.broadcastLegs, leg]
|
|
21685
|
-
);
|
|
21686
|
-
}
|
|
21687
|
-
/** @internal — fan out the where-filtered records across eligible shards. */
|
|
21688
|
-
async fanoutRecords(options = {}) {
|
|
21689
|
-
const { eligible, skipped } = await this.group.resolveEligible(options);
|
|
21690
|
-
const probeRow = eligible[0];
|
|
21691
|
-
if (this.coPartitionedLegs.length > 0 && probeRow) {
|
|
21692
|
-
const probe = await this.group.openShard(probeRow.partitionKey);
|
|
21693
|
-
this.group.template.configure(probe);
|
|
21694
|
-
for (const leg of this.coPartitionedLegs) {
|
|
21695
|
-
if (!probe.resolveRef(this.collectionName, leg.field)) {
|
|
21696
|
-
throw new CrossShardJoinError(
|
|
21697
|
-
`crossShardJoin("${leg.field}"): no ref() declared for "${leg.field}" on collection "${this.collectionName}" in template "${this.group.sharding.vaultTemplate}". Add refs: { ${leg.field}: ref('<target>') } to the template's collection options.`
|
|
21698
|
-
);
|
|
21699
|
-
}
|
|
21700
|
-
}
|
|
21701
|
-
}
|
|
21702
|
-
const across = await this.group.db.queryAcross(
|
|
21703
|
-
eligible.map((r) => r.vaultId),
|
|
21704
|
-
async (vault) => {
|
|
21705
|
-
this.group.template.configure(vault);
|
|
21706
|
-
const coll = vault.collection(this.collectionName);
|
|
21707
|
-
await coll.list();
|
|
21708
|
-
for (const leg of this.coPartitionedLegs) {
|
|
21709
|
-
const desc = vault.resolveRef(this.collectionName, leg.field);
|
|
21710
|
-
if (desc) await vault.collection(desc.target).list();
|
|
21711
|
-
}
|
|
21712
|
-
let q = coll.query();
|
|
21713
|
-
for (const c of this.clauses) q = q.where(c.field, c.op, c.value);
|
|
21714
|
-
for (const leg of this.coPartitionedLegs) {
|
|
21715
|
-
q = q.join(leg.field, {
|
|
21716
|
-
as: leg.as,
|
|
21717
|
-
...leg.maxRows !== void 0 ? { maxRows: leg.maxRows } : {},
|
|
21718
|
-
...leg.strategy ? { strategy: leg.strategy } : {}
|
|
21719
|
-
});
|
|
21720
|
-
}
|
|
21721
|
-
return q.toArray();
|
|
21722
|
-
},
|
|
21723
|
-
{ concurrency: options.concurrency ?? 1, create: false }
|
|
21724
|
-
);
|
|
21725
|
-
const results = [];
|
|
21726
|
-
for (const r of across) {
|
|
21727
|
-
if (r.error) skipped.push({ vaultId: r.vault, reason: classifyShardSkip(r.error), error: r.error });
|
|
21728
|
-
else for (const item of r.result) results.push(item);
|
|
21729
|
-
}
|
|
21730
|
-
return { records: results, skippedVaults: skipped };
|
|
21731
|
-
}
|
|
21732
|
-
/** Fan out across eligible shards, merge, then apply any broadcast dimension legs. */
|
|
21733
|
-
async toArray(options = {}) {
|
|
21734
|
-
const { records, skippedVaults } = await this.fanoutRecords(options);
|
|
21735
|
-
const results = await applyBroadcastLegs(records, this.broadcastLegs);
|
|
21736
|
-
return { results, skippedVaults };
|
|
21737
|
-
}
|
|
21738
|
-
/** @internal — build the change-subscription + relevance binding for this query's group+collection. */
|
|
21739
|
-
liveBinding() {
|
|
21740
|
-
const group = this.group;
|
|
21741
|
-
const collectionName = this.collectionName;
|
|
21742
|
-
return {
|
|
21743
|
-
subscribeToChanges: (h) => {
|
|
21744
|
-
group.db.on("change", h);
|
|
21745
|
-
return () => group.db.off("change", h);
|
|
21746
|
-
},
|
|
21747
|
-
isRelevant: (e) => e.collection === collectionName && e.vault.startsWith(`${group.name}--`)
|
|
21748
|
-
};
|
|
21749
|
-
}
|
|
21750
|
-
/** @internal — joined queries don't support reactive/aggregate surfaces in v1. */
|
|
21751
|
-
assertNoJoinLegs(surface) {
|
|
21752
|
-
if (this.coPartitionedLegs.length || this.broadcastLegs.length) {
|
|
21753
|
-
throw new CrossShardJoinError(
|
|
21754
|
-
`${surface}() is not supported on a ShardedQuery with crossShardJoin/broadcastJoin legs in v1. Use toArray() for joined cross-shard queries.`
|
|
21755
|
-
);
|
|
21756
|
-
}
|
|
21757
|
-
}
|
|
21758
|
-
/** Returns a reactive cross-shard live query — a facade over CrossVaultLive. */
|
|
21759
|
-
live(options = {}) {
|
|
21760
|
-
this.assertNoJoinLegs("live");
|
|
21761
|
-
const bind = this.liveBinding();
|
|
21762
|
-
const core = new CrossVaultLive({
|
|
21763
|
-
...bind,
|
|
21764
|
-
compute: async () => {
|
|
21765
|
-
const { records, skippedVaults } = await this.fanoutRecords(options);
|
|
21766
|
-
return { records, skipped: skippedVaults };
|
|
21767
|
-
},
|
|
21768
|
-
initialSnapshot: { records: [], skipped: [] },
|
|
21769
|
-
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
21770
|
-
});
|
|
21771
|
-
return {
|
|
21772
|
-
get value() {
|
|
21773
|
-
return core.snapshot.records;
|
|
21774
|
-
},
|
|
21775
|
-
get skippedVaults() {
|
|
21776
|
-
return core.snapshot.skipped;
|
|
21777
|
-
},
|
|
21778
|
-
get error() {
|
|
21779
|
-
return core.error;
|
|
21780
|
-
},
|
|
21781
|
-
ready: core.ready,
|
|
21782
|
-
subscribe: (cb) => core.subscribe(cb),
|
|
21783
|
-
stop: () => core.stop()
|
|
21784
|
-
};
|
|
21785
|
-
}
|
|
21786
|
-
/** One-shot distributed aggregate — central reduce over all shard records. */
|
|
21787
|
-
aggregate(spec) {
|
|
21788
|
-
this.assertNoJoinLegs("aggregate");
|
|
21789
|
-
return new CrossVaultAggregation(this, spec, this.liveBinding());
|
|
21790
|
-
}
|
|
21791
|
-
/** Begin a grouped cross-shard aggregate. */
|
|
21792
|
-
groupBy(field) {
|
|
21793
|
-
this.assertNoJoinLegs("groupBy");
|
|
21794
|
-
return new ShardedGroupedQuery(this, field);
|
|
21795
|
-
}
|
|
21796
|
-
};
|
|
21797
|
-
ShardedGroupedQuery = class {
|
|
21798
|
-
constructor(query, field) {
|
|
21799
|
-
this.query = query;
|
|
21800
|
-
this.field = field;
|
|
21801
|
-
}
|
|
21802
|
-
query;
|
|
21803
|
-
field;
|
|
21804
|
-
aggregate(spec) {
|
|
21805
|
-
return new CrossVaultGroupedAggregation(
|
|
21806
|
-
{ fanoutRecords: (o) => this.query.fanoutRecords(o) },
|
|
21807
|
-
this.field,
|
|
21808
|
-
spec,
|
|
21809
|
-
this.query.liveBinding()
|
|
21810
|
-
);
|
|
21811
|
-
}
|
|
21812
|
-
};
|
|
21813
|
-
}
|
|
21814
|
-
});
|
|
21815
|
-
|
|
21816
20980
|
// src/noydb.ts
|
|
21817
20981
|
var noydb_exports = {};
|
|
21818
20982
|
__export(noydb_exports, {
|
|
@@ -21873,7 +21037,6 @@ var init_noydb = __esm({
|
|
|
21873
21037
|
"src/noydb.ts"() {
|
|
21874
21038
|
"use strict";
|
|
21875
21039
|
init_errors();
|
|
21876
|
-
init_constants2();
|
|
21877
21040
|
init_storage3();
|
|
21878
21041
|
init_rotate_recover();
|
|
21879
21042
|
init_peer_recover();
|
|
@@ -21907,6 +21070,12 @@ var init_noydb = __esm({
|
|
|
21907
21070
|
client: 1,
|
|
21908
21071
|
viewer: 2,
|
|
21909
21072
|
operator: 3,
|
|
21073
|
+
// FR-6: custodian is operationally admin-rank (rw + access on every
|
|
21074
|
+
// collection) — it ranks alongside admin for "how much can this
|
|
21075
|
+
// principal see/operate." It is NOT above admin, and explicitly below
|
|
21076
|
+
// owner: a custodian can never grant/revoke/rotate/sever (those are
|
|
21077
|
+
// owner meta-capabilities), so it must not outrank or equal the owner.
|
|
21078
|
+
custodian: 4,
|
|
21910
21079
|
admin: 4,
|
|
21911
21080
|
owner: 5
|
|
21912
21081
|
};
|
|
@@ -21955,7 +21124,6 @@ var init_noydb = __esm({
|
|
|
21955
21124
|
writeRelay;
|
|
21956
21125
|
/** Per-vault policy enforcers. */
|
|
21957
21126
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
21958
|
-
vaultTemplates = /* @__PURE__ */ new Map();
|
|
21959
21127
|
txStrategy;
|
|
21960
21128
|
forgetStrategy;
|
|
21961
21129
|
sessionStrategy;
|
|
@@ -22405,6 +21573,37 @@ var init_noydb = __esm({
|
|
|
22405
21573
|
const keyring = await this.getKeyringInternal(vault);
|
|
22406
21574
|
await revoke(this.options.store, vault, keyring, options);
|
|
22407
21575
|
}
|
|
21576
|
+
/**
|
|
21577
|
+
* Grant the FR-6 `custodian` role to a user (owner-only custody API).
|
|
21578
|
+
*
|
|
21579
|
+
* A custodian operates every collection (rw + access) but is provably
|
|
21580
|
+
* unable to grant / revoke / rotate / extract-and-sever. Only the Deed
|
|
21581
|
+
* owner may mint one. Defended in depth: the `grant-custodian` gate
|
|
21582
|
+
* (fail-closed) AND an explicit `keyring.role !== 'owner'` check — the
|
|
21583
|
+
* gate enforces host policy, the role check enforces the cryptographic
|
|
21584
|
+
* owner-only invariant even if a host mis-configures the gate.
|
|
21585
|
+
*/
|
|
21586
|
+
async grantCustodian(vault, options, factors) {
|
|
21587
|
+
this.checkPolicyOperation(vault, "grant");
|
|
21588
|
+
await this.checkGate(vault, "grant-custodian", factors);
|
|
21589
|
+
const keyring = await this.getKeyringInternal(vault);
|
|
21590
|
+
if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can grant a custodian");
|
|
21591
|
+
await grant(this.options.store, vault, keyring, { ...options, role: "custodian" });
|
|
21592
|
+
}
|
|
21593
|
+
/**
|
|
21594
|
+
* Revoke a custodian (owner-only custody API).
|
|
21595
|
+
*
|
|
21596
|
+
* Mirrors {@link revoke} but pins the caller to the Deed owner: defended
|
|
21597
|
+
* in depth by the `revoke-user` gate AND an explicit `keyring.role !==
|
|
21598
|
+
* 'owner'` check, so an admin cannot unwind a custodianship.
|
|
21599
|
+
*/
|
|
21600
|
+
async revokeCustodian(vault, options, factors) {
|
|
21601
|
+
this.checkPolicyOperation(vault, "revoke");
|
|
21602
|
+
await this.checkGate(vault, "revoke-user", factors);
|
|
21603
|
+
const keyring = await this.getKeyringInternal(vault);
|
|
21604
|
+
if (keyring.role !== "owner") throw new PermissionDeniedError("only the Deed owner can revoke a custodian");
|
|
21605
|
+
await revoke(this.options.store, vault, keyring, options);
|
|
21606
|
+
}
|
|
22408
21607
|
/**
|
|
22409
21608
|
* Mutate post-grant identity fields on an existing keyring — `role`,
|
|
22410
21609
|
* `displayName`, and/or `permissions`. Pure plaintext-header rewrite:
|
|
@@ -22674,52 +21873,12 @@ var init_noydb = __esm({
|
|
|
22674
21873
|
return results;
|
|
22675
21874
|
}
|
|
22676
21875
|
/**
|
|
22677
|
-
*
|
|
22678
|
-
*
|
|
22679
|
-
|
|
22680
|
-
withVaultTemplate(name, template) {
|
|
22681
|
-
this.vaultTemplates.set(name, template);
|
|
22682
|
-
}
|
|
22683
|
-
/**
|
|
22684
|
-
* Open a VaultGroup — transparent routing over per-partition shard
|
|
22685
|
-
* vaults, with shard discovery backed by the supplied `vault-registry`
|
|
22686
|
-
* collection.
|
|
22687
|
-
*/
|
|
22688
|
-
async openVaultGroup(name, opts) {
|
|
22689
|
-
if (this.closed) throw new ValidationError("Instance is closed");
|
|
22690
|
-
if (name === STATE_VAULT_NAME) throw new ReservedVaultNameError(name);
|
|
22691
|
-
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
22692
|
-
if (!template) throw new VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
22693
|
-
const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
|
|
22694
|
-
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
22695
|
-
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
|
|
22696
|
-
const registry = opts.registry ?? stateVault.registry;
|
|
22697
|
-
const group = new VaultGroup2(this, name, registry, opts.sharding, template, opts.migrateOnOpen ?? false);
|
|
22698
|
-
if (stateVault) {
|
|
22699
|
-
group._attachStateVault(stateVault);
|
|
22700
|
-
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|
|
22701
|
-
try {
|
|
22702
|
-
await stateVault.appendEvent({
|
|
22703
|
-
type: "manifest-recorded",
|
|
22704
|
-
group: name,
|
|
22705
|
-
templateName: opts.sharding.vaultTemplate,
|
|
22706
|
-
version: template.version
|
|
22707
|
-
});
|
|
22708
|
-
await stateVault.appendEvent({ type: "group-opened", group: name });
|
|
22709
|
-
} catch {
|
|
22710
|
-
}
|
|
22711
|
-
}
|
|
22712
|
-
return group;
|
|
22713
|
-
}
|
|
22714
|
-
/**
|
|
22715
|
-
* Open the reserved StateManagement control-plane vault (registry +
|
|
22716
|
-
* schema-manifest + deployment-events). Lazy-loaded so the federation
|
|
22717
|
-
* chunk stays out of the core graph until used.
|
|
21876
|
+
* @internal True once `close()` has been called. Read by outward
|
|
21877
|
+
* orchestration frameworks whose entry points can't see the private
|
|
21878
|
+
* `closed` field.
|
|
22718
21879
|
*/
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
22722
|
-
return StateManagementVault2.open(this);
|
|
21880
|
+
get isClosed() {
|
|
21881
|
+
return this.closed;
|
|
22723
21882
|
}
|
|
22724
21883
|
/**
|
|
22725
21884
|
* @internal — true when an encrypted shard vault is provisioned
|
|
@@ -24215,6 +23374,7 @@ __export(bundle_exports, {
|
|
|
24215
23374
|
TransferSealError: () => TransferSealError,
|
|
24216
23375
|
adoptPartition: () => adoptPartition,
|
|
24217
23376
|
createOwnerOnAdoptedPartition: () => createOwnerOnAdoptedPartition,
|
|
23377
|
+
decryptExtractedPartition: () => decryptExtractedPartition,
|
|
24218
23378
|
describeExtraction: () => describeExtraction,
|
|
24219
23379
|
encodeBundleHeader: () => encodeBundleHeader,
|
|
24220
23380
|
extractPartition: () => extractPartition,
|
|
@@ -24388,7 +23548,7 @@ init_constants();
|
|
|
24388
23548
|
init_entry();
|
|
24389
23549
|
init_hash();
|
|
24390
23550
|
init_bundle();
|
|
24391
|
-
async function reKeyClosure(vault, closure) {
|
|
23551
|
+
async function reKeyClosure(vault, closure, fieldProjection) {
|
|
24392
23552
|
const { name: vaultName, adapter, getDEK } = vault._introspectState();
|
|
24393
23553
|
const collections = {};
|
|
24394
23554
|
const deks = /* @__PURE__ */ new Map();
|
|
@@ -24397,29 +23557,40 @@ async function reKeyClosure(vault, closure) {
|
|
|
24397
23557
|
const destDek = await generateDEK();
|
|
24398
23558
|
deks.set(collectionName, destDek);
|
|
24399
23559
|
const out = {};
|
|
23560
|
+
const projList = fieldProjection?.[collectionName];
|
|
23561
|
+
const proj = projList ? new Set(projList) : void 0;
|
|
23562
|
+
const project = (plaintext) => {
|
|
23563
|
+
if (!proj) return plaintext;
|
|
23564
|
+
const rec = JSON.parse(plaintext);
|
|
23565
|
+
const kept = {};
|
|
23566
|
+
if ("id" in rec) kept["id"] = rec["id"];
|
|
23567
|
+
for (const f of proj) if (f in rec) kept[f] = rec[f];
|
|
23568
|
+
return JSON.stringify(kept);
|
|
23569
|
+
};
|
|
24400
23570
|
for (const id of ids) {
|
|
24401
23571
|
const env = await adapter.get(vaultName, collectionName, id);
|
|
24402
23572
|
if (!env) continue;
|
|
24403
23573
|
if (env._cek !== void 0) {
|
|
24404
23574
|
const cek = await unwrapCek(env._cek, srcDek);
|
|
24405
23575
|
const plaintext2 = await decrypt(env._iv, env._data, cek);
|
|
24406
|
-
const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
|
|
23576
|
+
const { iv: iv2, data: data2 } = await encrypt(project(plaintext2), cek);
|
|
24407
23577
|
const wrapped = await wrapCek(cek, destDek);
|
|
24408
23578
|
out[id] = { ...env, _iv: iv2, _data: data2, _cek: wrapped };
|
|
24409
23579
|
continue;
|
|
24410
23580
|
}
|
|
24411
23581
|
const plaintext = await decrypt(env._iv, env._data, srcDek);
|
|
24412
|
-
const { iv, data } = await encrypt(plaintext, destDek);
|
|
23582
|
+
const { iv, data } = await encrypt(project(plaintext), destDek);
|
|
24413
23583
|
out[id] = { ...env, _iv: iv, _data: data };
|
|
24414
23584
|
}
|
|
24415
23585
|
collections[collectionName] = out;
|
|
24416
23586
|
}
|
|
24417
23587
|
return { collections, deks };
|
|
24418
23588
|
}
|
|
24419
|
-
async function reKeySchemas(vault, closure, destDeks) {
|
|
23589
|
+
async function reKeySchemas(vault, closure, destDeks, fieldProjection) {
|
|
24420
23590
|
const { name: vaultName, adapter, getDEK } = vault._introspectState();
|
|
24421
23591
|
const out = {};
|
|
24422
23592
|
for (const collectionName of closure.keys()) {
|
|
23593
|
+
if (fieldProjection?.[collectionName]) continue;
|
|
24423
23594
|
const env = await adapter.get(vaultName, SCHEMAS_COLLECTION, collectionName);
|
|
24424
23595
|
if (!env) continue;
|
|
24425
23596
|
const destDek = destDeks.get(collectionName);
|
|
@@ -24511,6 +23682,11 @@ async function sealDeks(deks) {
|
|
|
24511
23682
|
};
|
|
24512
23683
|
}
|
|
24513
23684
|
async function extractPartition(vault, opts) {
|
|
23685
|
+
if (vault.role === "custodian") {
|
|
23686
|
+
throw new PartitionExtractionError(
|
|
23687
|
+
"extractPartition is owner-only; a custodian cannot extract-and-sever (FR-6: producing a re-keyed standalone partition is an ownership operation; use the Deed owner)."
|
|
23688
|
+
);
|
|
23689
|
+
}
|
|
24514
23690
|
if (vault.role !== "owner") {
|
|
24515
23691
|
throw new PartitionExtractionError(
|
|
24516
23692
|
`extractPartition requires the 'owner' role on the source vault; caller is '${vault.role}'. Producing a re-keyed standalone partition is an ownership operation.`
|
|
@@ -24518,7 +23694,7 @@ async function extractPartition(vault, opts) {
|
|
|
24518
23694
|
}
|
|
24519
23695
|
if (opts.carrySchemas) await vault._drainPendingSchemaWrites();
|
|
24520
23696
|
const { closure } = await walkClosure(vault, opts);
|
|
24521
|
-
const { collections, deks } = await reKeyClosure(vault, closure);
|
|
23697
|
+
const { collections, deks } = await reKeyClosure(vault, closure, opts.fieldProjection);
|
|
24522
23698
|
let ledgerHead;
|
|
24523
23699
|
let ledgerEntries;
|
|
24524
23700
|
if (opts.carryLedger && vault._getLedgerOrNull() !== null) {
|
|
@@ -24530,7 +23706,7 @@ async function extractPartition(vault, opts) {
|
|
|
24530
23706
|
deks.set(LEDGER_COLLECTION, ledgerDek);
|
|
24531
23707
|
}
|
|
24532
23708
|
}
|
|
24533
|
-
const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks) : {};
|
|
23709
|
+
const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks, opts.fieldProjection) : {};
|
|
24534
23710
|
const internal = {};
|
|
24535
23711
|
if (Object.keys(internalSchemas).length > 0) internal[SCHEMAS_COLLECTION] = internalSchemas;
|
|
24536
23712
|
if (ledgerEntries) internal[LEDGER_COLLECTION] = ledgerEntries;
|
|
@@ -24745,6 +23921,41 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
|
|
|
24745
23921
|
return { vaultName, userId };
|
|
24746
23922
|
}
|
|
24747
23923
|
|
|
23924
|
+
// src/bundle/decrypt-partition.ts
|
|
23925
|
+
init_crypto();
|
|
23926
|
+
init_record_keys();
|
|
23927
|
+
init_bundle();
|
|
23928
|
+
async function decryptExtractedPartition(bundleBytes, transferKey) {
|
|
23929
|
+
const header = readNoydbBundleHeader(bundleBytes);
|
|
23930
|
+
if (header.bundleKind !== "extracted-partition" || header.transferSeal === void 0) {
|
|
23931
|
+
throw new Error("decryptExtractedPartition: bundle is not an extracted-partition.");
|
|
23932
|
+
}
|
|
23933
|
+
const { dumpJson } = await readNoydbBundle(bundleBytes);
|
|
23934
|
+
const { dump, seal } = parseExtractedPartitionBody(dumpJson);
|
|
23935
|
+
const deks = await unsealDeks(seal, transferKey);
|
|
23936
|
+
const backup = JSON.parse(dump);
|
|
23937
|
+
const out = {};
|
|
23938
|
+
for (const [collection, byId] of Object.entries(backup.collections)) {
|
|
23939
|
+
const dek = deks.get(collection);
|
|
23940
|
+
if (dek === void 0) continue;
|
|
23941
|
+
const recs = [];
|
|
23942
|
+
for (const [id, env] of Object.entries(byId)) {
|
|
23943
|
+
const plaintext = env._cek !== void 0 ? await decrypt(env._iv, env._data, await unwrapCek(env._cek, dek)) : await decrypt(env._iv, env._data, dek);
|
|
23944
|
+
const body = JSON.parse(plaintext);
|
|
23945
|
+
recs.push({
|
|
23946
|
+
id,
|
|
23947
|
+
record: { ...body, id },
|
|
23948
|
+
ts: env._ts,
|
|
23949
|
+
version: env._v,
|
|
23950
|
+
...env._source !== void 0 ? { source: env._source } : {},
|
|
23951
|
+
...env._sourceTs !== void 0 ? { sourceTs: env._sourceTs } : {}
|
|
23952
|
+
});
|
|
23953
|
+
}
|
|
23954
|
+
out[collection] = recs;
|
|
23955
|
+
}
|
|
23956
|
+
return out;
|
|
23957
|
+
}
|
|
23958
|
+
|
|
24748
23959
|
// src/bundle/index.ts
|
|
24749
23960
|
init_errors();
|
|
24750
23961
|
init_errors();
|
|
@@ -24768,6 +23979,7 @@ init_errors();
|
|
|
24768
23979
|
TransferSealError,
|
|
24769
23980
|
adoptPartition,
|
|
24770
23981
|
createOwnerOnAdoptedPartition,
|
|
23982
|
+
decryptExtractedPartition,
|
|
24771
23983
|
describeExtraction,
|
|
24772
23984
|
encodeBundleHeader,
|
|
24773
23985
|
extractPartition,
|