@noy-db/hub 0.2.0-pre.3 → 0.2.0-pre.5
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.js +4 -4
- 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 +5 -5
- package/dist/blobs/index.d.ts +5 -5
- package/dist/blobs/index.js +5 -5
- package/dist/bundle/index.cjs +443 -338
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +17 -17
- package/dist/bundle/index.d.ts +17 -17
- package/dist/bundle/index.js +10 -10
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-YL2DR3HY.js → chunk-25WFLKOH.js} +2 -2
- package/dist/chunk-25WFLKOH.js.map +1 -0
- package/dist/{chunk-EMEX37ZN.js → chunk-2GMRNNI3.js} +3 -3
- package/dist/chunk-2GMRNNI3.js.map +1 -0
- package/dist/{chunk-NGSPBLLE.js → chunk-34XGYMQT.js} +3 -3
- package/dist/chunk-34XGYMQT.js.map +1 -0
- package/dist/{chunk-FXQYZNOW.js → chunk-5OVIFUQE.js} +1 -1
- package/dist/chunk-5OVIFUQE.js.map +1 -0
- package/dist/{chunk-P6256WTJ.js → chunk-5QPF2MJ5.js} +3 -3
- package/dist/chunk-5QPF2MJ5.js.map +1 -0
- package/dist/{chunk-5ZGZ6HIZ.js → chunk-5VMTAX4Y.js} +2 -2
- package/dist/{chunk-74JEQFMT.js → chunk-6A4AMQ2H.js} +5 -5
- package/dist/chunk-6A4AMQ2H.js.map +1 -0
- package/dist/{chunk-YDLAFP36.js → chunk-6HJ2ZALB.js} +1 -1
- package/dist/chunk-6HJ2ZALB.js.map +1 -0
- package/dist/{chunk-GDTCGIPX.js → chunk-7TX7HN42.js} +2 -2
- package/dist/chunk-7TX7HN42.js.map +1 -0
- package/dist/{chunk-EPK6A3WJ.js → chunk-A3JMGXPG.js} +2 -2
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/{chunk-75QDHSE4.js → chunk-A4JNVBPF.js} +5 -5
- package/dist/{chunk-IS5HWQO7.js → chunk-ARZAHCCF.js} +3 -3
- package/dist/{chunk-T6HQMVML.js → chunk-BT7544RM.js} +399 -301
- package/dist/chunk-BT7544RM.js.map +1 -0
- package/dist/{chunk-4OQWR46B.js → chunk-CCC25PA7.js} +5 -5
- package/dist/{chunk-NSLTPGEN.js → chunk-CGJFCT3X.js} +2 -2
- package/dist/{chunk-YK72A4IT.js → chunk-CKH247ZR.js} +4 -4
- package/dist/{chunk-HGZ7DC5H.js → chunk-DFCINPB5.js} +2 -2
- package/dist/chunk-DFCINPB5.js.map +1 -0
- package/dist/{chunk-4X2S7PBF.js → chunk-E225X5CQ.js} +3 -3
- package/dist/chunk-E225X5CQ.js.map +1 -0
- package/dist/{chunk-5YHWBPOT.js → chunk-ED3E3OLO.js} +2 -2
- package/dist/{chunk-UOF74WQY.js → chunk-EKTOYEZ3.js} +2 -2
- package/dist/{chunk-SAVQ6E2O.js → chunk-G26QAQNI.js} +2 -2
- package/dist/{chunk-YMYK7US4.js → chunk-HIELMTUK.js} +2 -2
- package/dist/{chunk-MRIBLZL3.js → chunk-ICH4AIGL.js} +1 -1
- package/dist/chunk-ICH4AIGL.js.map +1 -0
- package/dist/{chunk-LOL725S4.js → chunk-JSYTGEX4.js} +3 -3
- package/dist/{chunk-FBMXWVGP.js → chunk-KGFV72WK.js} +5 -5
- package/dist/{chunk-GVXBHCZ2.js → chunk-LJO6Q3X6.js} +5 -5
- package/dist/chunk-LJO6Q3X6.js.map +1 -0
- package/dist/{chunk-ZC2AAE6J.js → chunk-LWFQYT4N.js} +2 -2
- package/dist/chunk-LWFQYT4N.js.map +1 -0
- package/dist/{chunk-K5PVGKE4.js → chunk-MDIC4FAU.js} +2 -2
- package/dist/{chunk-A6SWRXUQ.js → chunk-NONMIU6C.js} +2 -2
- package/dist/{chunk-ZUMGGHRB.js → chunk-OPD3PZOG.js} +4 -4
- package/dist/{chunk-LS3JLEIB.js → chunk-PS5G6A3Y.js} +4 -4
- package/dist/{chunk-KYKMKLJ6.js → chunk-PX3MJ6RB.js} +3 -3
- package/dist/{chunk-FCDO7UAO.js → chunk-R4LTCI6O.js} +2 -2
- package/dist/{chunk-BFI3RS42.js → chunk-R7JTYCRX.js} +2 -2
- package/dist/chunk-R7JTYCRX.js.map +1 -0
- package/dist/{chunk-WRLHNG6H.js → chunk-RIHZBSWJ.js} +4 -4
- package/dist/chunk-RIHZBSWJ.js.map +1 -0
- package/dist/{chunk-UVPGJXVO.js → chunk-SGSHQ4PH.js} +5 -5
- package/dist/{chunk-TLFUDXVV.js → chunk-T6MTNGBM.js} +5 -5
- package/dist/chunk-T6MTNGBM.js.map +1 -0
- package/dist/{chunk-6S3LLAQ5.js → chunk-TNBIWSQ7.js} +2 -2
- package/dist/{chunk-GD3BGKAR.js → chunk-UGVDIOY7.js} +2 -2
- package/dist/{chunk-FS7A4XNF.js → chunk-WEA4TDTJ.js} +3 -3
- package/dist/{chunk-4UBOTYP5.js → chunk-XDW37COG.js} +5 -5
- package/dist/chunk-XDW37COG.js.map +1 -0
- package/dist/{chunk-QAU5HM6Q.js → chunk-XVJFFGTG.js} +3 -3
- package/dist/{chunk-2EYC3WDT.js → chunk-Y3P5DEMZ.js} +6 -6
- package/dist/chunk-Y3P5DEMZ.js.map +1 -0
- package/dist/{chunk-G7PAZ3TD.js → chunk-YEHUEUNP.js} +4 -4
- package/dist/chunk-YEHUEUNP.js.map +1 -0
- package/dist/{chunk-2XLVPKXG.js → chunk-YJ46RFCD.js} +2 -2
- package/dist/{chunk-KMI2NBBF.js → chunk-YZ6JETII.js} +6 -6
- package/dist/{chunk-NCO2JGKK.js → chunk-Z6FNBOTC.js} +1 -1
- package/dist/chunk-Z6FNBOTC.js.map +1 -0
- package/dist/{chunk-GAUBWHAF.js → chunk-ZQMYB56Z.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-H2Y3DDFW.js → crypto-5UDZZL26.js} +3 -3
- package/dist/{delegation-QSC7G5QC.js → delegation-42LO4WFO.js} +5 -5
- package/dist/derivations/index.cjs +1 -1
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +8 -8
- package/dist/derivations/index.d.ts +8 -8
- package/dist/derivations/index.js +4 -4
- package/dist/{dev-unlock-Cf2B7Kih.d.ts → dev-unlock--ahUTrhc.d.ts} +1 -1
- package/dist/{dev-unlock-De3mjQWv.d.cts → dev-unlock-BIwt2V3p.d.cts} +1 -1
- package/dist/executor-AWCHQ2KN.js +8 -0
- package/dist/executor-RWICJI7J.js +11 -0
- package/dist/executor-SOLEQVUB.js +8 -0
- package/dist/{fanout-sidecar-NRBWSLRK.js → fanout-sidecar-EVICRM46.js} +2 -2
- package/dist/fanout-sidecar-EVICRM46.js.map +1 -0
- package/dist/guards/index.cjs +1 -1
- 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 +4 -4
- package/dist/{hash-vBCB0-Ps.d.cts → hash-BQVrGV-t.d.cts} +1 -1
- package/dist/{hash-gVn_uKhp.d.ts → hash-CJEFQxSD.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 +6 -6
- 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 +7 -7
- package/dist/{index-DVkvrgpm.d.cts → index-5I0MZ0jQ.d.cts} +12 -12
- package/dist/{index-BF1B2HB9.d.ts → index-fIPPh5dg.d.ts} +12 -12
- package/dist/index.cjs +362 -264
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -22
- package/dist/index.d.ts +20 -22
- package/dist/index.js +45 -45
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +1 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-IODMTPME.js +12 -0
- package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-D1MyR1qH.d.ts} +2 -2
- package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-DXlSCNCJ.d.cts} +2 -2
- package/dist/{ledger-WOEJUYTP.js → ledger-UX4QIHWI.js} +6 -6
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +18 -18
- package/dist/materialized-views/index.d.ts +18 -18
- package/dist/materialized-views/index.js +7 -7
- package/dist/noydb-6TADQIYH.js +34 -0
- package/dist/overlay-views/index.cjs +1 -1
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +8 -8
- package/dist/overlay-views/index.d.ts +8 -8
- 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 +6 -6
- package/dist/{predicate-Dnu81tsS.d.cts → predicate-B0IKeBXx.d.cts} +1 -1
- package/dist/{predicate-Dnu81tsS.d.ts → predicate-B0IKeBXx.d.ts} +1 -1
- package/dist/{public-envelope-OHQ5UZFM.js → public-envelope-YKHKP74C.js} +4 -4
- package/dist/query/index.cjs +2 -2
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +6 -6
- package/dist/registry-446I2NMN.js +8 -0
- package/dist/{registry-CDHASH73.js → registry-4NEW7LQY.js} +3 -3
- package/dist/registry-524KJZG4.js +8 -0
- package/dist/registry-DKEXOJVO.js +7 -0
- package/dist/{revoke-7JOVLZFD.js → revoke-R5NIQ74J.js} +6 -6
- 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-M4K5HBLD.js → signer-WGDJNWSU.js} +5 -5
- package/dist/{stale-PAGCS4K5.js → stale-74WGLVZ2.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/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 +1 -1
- 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/tx/index.cjs +2 -2
- 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/tx/index.js.map +1 -1
- package/dist/{types-D9eB0Rvh.d.ts → types-BV4AZKmx.d.ts} +340 -302
- package/dist/{types-CSLcfytP.d.cts → types-BeKi0hCx.d.cts} +340 -302
- package/dist/{ulid-CiM2OAeM.d.ts → ulid-CQc0eBxE.d.ts} +19 -19
- package/dist/{ulid-CG2YvAbg.d.cts → ulid-Cvljl7ZZ.d.cts} +19 -19
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/{with-derivation-Bzpj6UTv.d.ts → with-derivation-BWcwmevt.d.ts} +1 -1
- package/dist/{with-derivation-DWajFh4K.d.cts → with-derivation-BkOBDhsu.d.cts} +1 -1
- package/dist/{with-guard-DF_Ul3DT.d.cts → with-guard-BD4Hyu8s.d.cts} +1 -1
- package/dist/{with-guard-DR7U-l4v.d.ts → with-guard-Du54s3Ti.d.ts} +1 -1
- package/dist/{with-materialized-view-qtoJ3xKJ.d.ts → with-materialized-view-B5W4wFAC.d.ts} +2 -2
- package/dist/{with-materialized-view-_piodoIz.d.cts → with-materialized-view-BCPPZdjC.d.cts} +2 -2
- package/dist/{with-overlayed-view-DFaRfgMr.d.ts → with-overlayed-view-B8RrlLsG.d.cts} +2 -2
- package/dist/{with-overlayed-view-DwzCKxn2.d.cts → with-overlayed-view-Cw-h9p9N.d.ts} +2 -2
- package/package.json +3 -3
- package/dist/chunk-2EYC3WDT.js.map +0 -1
- package/dist/chunk-4UBOTYP5.js.map +0 -1
- package/dist/chunk-4X2S7PBF.js.map +0 -1
- package/dist/chunk-74JEQFMT.js.map +0 -1
- package/dist/chunk-BFI3RS42.js.map +0 -1
- package/dist/chunk-EMEX37ZN.js.map +0 -1
- package/dist/chunk-EPK6A3WJ.js.map +0 -1
- package/dist/chunk-FXQYZNOW.js.map +0 -1
- package/dist/chunk-G7PAZ3TD.js.map +0 -1
- package/dist/chunk-GDTCGIPX.js.map +0 -1
- package/dist/chunk-GVXBHCZ2.js.map +0 -1
- package/dist/chunk-HGZ7DC5H.js.map +0 -1
- package/dist/chunk-MRIBLZL3.js.map +0 -1
- package/dist/chunk-NCO2JGKK.js.map +0 -1
- package/dist/chunk-NGSPBLLE.js.map +0 -1
- package/dist/chunk-P6256WTJ.js.map +0 -1
- package/dist/chunk-T6HQMVML.js.map +0 -1
- package/dist/chunk-TLFUDXVV.js.map +0 -1
- package/dist/chunk-WRLHNG6H.js.map +0 -1
- package/dist/chunk-YDLAFP36.js.map +0 -1
- package/dist/chunk-YL2DR3HY.js.map +0 -1
- package/dist/chunk-ZC2AAE6J.js.map +0 -1
- package/dist/executor-BZKFZVRC.js +0 -8
- package/dist/executor-GFZFDQXV.js +0 -8
- package/dist/executor-KT2IOZVP.js +0 -11
- package/dist/fanout-sidecar-NRBWSLRK.js.map +0 -1
- package/dist/issue-BAJ7ZB4S.js +0 -12
- package/dist/noydb-XNQSKXGO.js +0 -34
- package/dist/registry-2IEARCGT.js +0 -7
- package/dist/registry-EMGLZGR6.js +0 -8
- package/dist/registry-NQALYR77.js +0 -8
- /package/dist/{chunk-5ZGZ6HIZ.js.map → chunk-5VMTAX4Y.js.map} +0 -0
- /package/dist/{chunk-75QDHSE4.js.map → chunk-A4JNVBPF.js.map} +0 -0
- /package/dist/{chunk-IS5HWQO7.js.map → chunk-ARZAHCCF.js.map} +0 -0
- /package/dist/{chunk-4OQWR46B.js.map → chunk-CCC25PA7.js.map} +0 -0
- /package/dist/{chunk-NSLTPGEN.js.map → chunk-CGJFCT3X.js.map} +0 -0
- /package/dist/{chunk-YK72A4IT.js.map → chunk-CKH247ZR.js.map} +0 -0
- /package/dist/{chunk-5YHWBPOT.js.map → chunk-ED3E3OLO.js.map} +0 -0
- /package/dist/{chunk-UOF74WQY.js.map → chunk-EKTOYEZ3.js.map} +0 -0
- /package/dist/{chunk-SAVQ6E2O.js.map → chunk-G26QAQNI.js.map} +0 -0
- /package/dist/{chunk-YMYK7US4.js.map → chunk-HIELMTUK.js.map} +0 -0
- /package/dist/{chunk-LOL725S4.js.map → chunk-JSYTGEX4.js.map} +0 -0
- /package/dist/{chunk-FBMXWVGP.js.map → chunk-KGFV72WK.js.map} +0 -0
- /package/dist/{chunk-K5PVGKE4.js.map → chunk-MDIC4FAU.js.map} +0 -0
- /package/dist/{chunk-A6SWRXUQ.js.map → chunk-NONMIU6C.js.map} +0 -0
- /package/dist/{chunk-ZUMGGHRB.js.map → chunk-OPD3PZOG.js.map} +0 -0
- /package/dist/{chunk-LS3JLEIB.js.map → chunk-PS5G6A3Y.js.map} +0 -0
- /package/dist/{chunk-KYKMKLJ6.js.map → chunk-PX3MJ6RB.js.map} +0 -0
- /package/dist/{chunk-FCDO7UAO.js.map → chunk-R4LTCI6O.js.map} +0 -0
- /package/dist/{chunk-UVPGJXVO.js.map → chunk-SGSHQ4PH.js.map} +0 -0
- /package/dist/{chunk-6S3LLAQ5.js.map → chunk-TNBIWSQ7.js.map} +0 -0
- /package/dist/{chunk-GD3BGKAR.js.map → chunk-UGVDIOY7.js.map} +0 -0
- /package/dist/{chunk-FS7A4XNF.js.map → chunk-WEA4TDTJ.js.map} +0 -0
- /package/dist/{chunk-QAU5HM6Q.js.map → chunk-XVJFFGTG.js.map} +0 -0
- /package/dist/{chunk-2XLVPKXG.js.map → chunk-YJ46RFCD.js.map} +0 -0
- /package/dist/{chunk-KMI2NBBF.js.map → chunk-YZ6JETII.js.map} +0 -0
- /package/dist/{chunk-GAUBWHAF.js.map → chunk-ZQMYB56Z.js.map} +0 -0
- /package/dist/{crypto-H2Y3DDFW.js.map → crypto-5UDZZL26.js.map} +0 -0
- /package/dist/{delegation-QSC7G5QC.js.map → delegation-42LO4WFO.js.map} +0 -0
- /package/dist/{executor-BZKFZVRC.js.map → executor-AWCHQ2KN.js.map} +0 -0
- /package/dist/{executor-GFZFDQXV.js.map → executor-RWICJI7J.js.map} +0 -0
- /package/dist/{executor-KT2IOZVP.js.map → executor-SOLEQVUB.js.map} +0 -0
- /package/dist/{issue-BAJ7ZB4S.js.map → issue-IODMTPME.js.map} +0 -0
- /package/dist/{ledger-WOEJUYTP.js.map → ledger-UX4QIHWI.js.map} +0 -0
- /package/dist/{noydb-XNQSKXGO.js.map → noydb-6TADQIYH.js.map} +0 -0
- /package/dist/{public-envelope-OHQ5UZFM.js.map → public-envelope-YKHKP74C.js.map} +0 -0
- /package/dist/{registry-2IEARCGT.js.map → registry-446I2NMN.js.map} +0 -0
- /package/dist/{registry-CDHASH73.js.map → registry-4NEW7LQY.js.map} +0 -0
- /package/dist/{registry-EMGLZGR6.js.map → registry-524KJZG4.js.map} +0 -0
- /package/dist/{registry-NQALYR77.js.map → registry-DKEXOJVO.js.map} +0 -0
- /package/dist/{revoke-7JOVLZFD.js.map → revoke-R5NIQ74J.js.map} +0 -0
- /package/dist/{signer-M4K5HBLD.js.map → signer-WGDJNWSU.js.map} +0 -0
- /package/dist/{stale-PAGCS4K5.js.map → stale-74WGLVZ2.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -3542,7 +3542,7 @@ var init_registry2 = __esm({
|
|
|
3542
3542
|
guardsFor(collection) {
|
|
3543
3543
|
return this._byCollection.get(collection) ?? [];
|
|
3544
3544
|
}
|
|
3545
|
-
/** Per-collection guard counts, for introspection
|
|
3545
|
+
/** Per-collection guard counts, for introspection. */
|
|
3546
3546
|
summary() {
|
|
3547
3547
|
return [...this._byCollection.entries()].map(([collection, guards]) => ({
|
|
3548
3548
|
collection,
|
|
@@ -8084,7 +8084,7 @@ async function changeSecret(adapter, vault, keyring, newPassphrase, passphraseOp
|
|
|
8084
8084
|
// Tier-2 slots are NOT preserved through `changeSecret` —
|
|
8085
8085
|
// each slot wraps the OLD KEK, so the new keyring has no
|
|
8086
8086
|
// authenticator slots until the user re-enrolls. The higher-level
|
|
8087
|
-
// `db.rotatePassphrase()`
|
|
8087
|
+
// `db.rotatePassphrase()` preserves slots by rewrapping the
|
|
8088
8088
|
// KEK reference, not the KEK itself.
|
|
8089
8089
|
authenticators: [],
|
|
8090
8090
|
...keyring.policy !== void 0 && { policy: keyring.policy }
|
|
@@ -8976,7 +8976,7 @@ var UserApi = class {
|
|
|
8976
8976
|
* the envelope on first call. Optimistic-concurrency safe — a stale
|
|
8977
8977
|
* `_v` (parallel writer on another device) throws `ConflictError`.
|
|
8978
8978
|
*
|
|
8979
|
-
* Patch semantics
|
|
8979
|
+
* Patch semantics:
|
|
8980
8980
|
* - `undefined` (or omitted key) — skip; existing value preserved
|
|
8981
8981
|
* - `null` — delete the field from the merged result
|
|
8982
8982
|
* - any other value — overwrite (deep-merge for plain objects,
|
|
@@ -9032,7 +9032,7 @@ var UserApi = class {
|
|
|
9032
9032
|
this.fireChange(this.writerKeyringId, written);
|
|
9033
9033
|
return written;
|
|
9034
9034
|
}
|
|
9035
|
-
// ─── Visibility
|
|
9035
|
+
// ─── Visibility ──────────────────────────────────────────────────────
|
|
9036
9036
|
/**
|
|
9037
9037
|
* Read the current user's visibility flag from
|
|
9038
9038
|
* `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no
|
|
@@ -10028,7 +10028,7 @@ var Query = class _Query {
|
|
|
10028
10028
|
/**
|
|
10029
10029
|
* @internal — clone this Query with a declared-predicate map
|
|
10030
10030
|
* attached. Used by the materialized-view registry to enable
|
|
10031
|
-
* `.wherePredicate(name, ctx?)` for the MV's query callback
|
|
10031
|
+
* `.wherePredicate(name, ctx?)` for the MV's query callback.
|
|
10032
10032
|
* Consumers don't call this directly.
|
|
10033
10033
|
*/
|
|
10034
10034
|
_withPredicates(predicates) {
|
|
@@ -10041,7 +10041,7 @@ var Query = class _Query {
|
|
|
10041
10041
|
);
|
|
10042
10042
|
}
|
|
10043
10043
|
/**
|
|
10044
|
-
* Filter by a registered deterministic predicate
|
|
10044
|
+
* Filter by a registered deterministic predicate. Requires
|
|
10045
10045
|
* the Query to have been augmented with a predicates map (typically
|
|
10046
10046
|
* via the materialized-view registry — bare Queries constructed
|
|
10047
10047
|
* outside an MV throw on `.wherePredicate()`).
|
|
@@ -11695,7 +11695,7 @@ var NO_BLOBS = {
|
|
|
11695
11695
|
init_errors();
|
|
11696
11696
|
init_ulid();
|
|
11697
11697
|
var TxContext = class {
|
|
11698
|
-
/** Stable id for this transaction; shared by all writes it performs
|
|
11698
|
+
/** Stable id for this transaction; shared by all writes it performs. */
|
|
11699
11699
|
txId = generateULID();
|
|
11700
11700
|
/** @internal */
|
|
11701
11701
|
_ops = [];
|
|
@@ -11705,7 +11705,7 @@ var TxContext = class {
|
|
|
11705
11705
|
* restore prior state via `revertExecuted`. Side-effect writes (e.g.
|
|
11706
11706
|
* recursive derivation outputs fired inside `Collection.put`) are
|
|
11707
11707
|
* appended here in execution order so they roll back alongside the
|
|
11708
|
-
* main staged ops
|
|
11708
|
+
* main staged ops.
|
|
11709
11709
|
*/
|
|
11710
11710
|
_executed = [];
|
|
11711
11711
|
/** @internal */
|
|
@@ -12027,7 +12027,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
12027
12027
|
}
|
|
12028
12028
|
if (out.kind === "array") {
|
|
12029
12029
|
console.warn(
|
|
12030
|
-
`[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager"
|
|
12030
|
+
`[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
|
|
12031
12031
|
);
|
|
12032
12032
|
continue;
|
|
12033
12033
|
}
|
|
@@ -12065,6 +12065,7 @@ var Collection = class {
|
|
|
12065
12065
|
schemaUpdateGate;
|
|
12066
12066
|
schemaFence;
|
|
12067
12067
|
writeHooks;
|
|
12068
|
+
subsystemBus;
|
|
12068
12069
|
activeTxId;
|
|
12069
12070
|
getDEK;
|
|
12070
12071
|
onDirty;
|
|
@@ -12253,42 +12254,14 @@ var Collection = class {
|
|
|
12253
12254
|
syncAdapter;
|
|
12254
12255
|
/** — consent-audit hook, no-op when no scope is active. */
|
|
12255
12256
|
onAccess;
|
|
12256
|
-
/**
|
|
12257
|
-
* accounting-period write guard. Called BEFORE any
|
|
12258
|
-
* adapter write with:
|
|
12259
|
-
* - `existing` — the prior envelope's `_ts` and decrypted record
|
|
12260
|
-
* (or `null` if no prior envelope exists)
|
|
12261
|
-
* - `incoming` — the record being written (or `null` for delete)
|
|
12262
|
-
*
|
|
12263
|
-
* Throws `PeriodClosedError` if either side falls inside a closed
|
|
12264
|
-
* period. Installed by Vault; no-op when no period has been closed.
|
|
12265
|
-
* Async so the Vault can lazy-load the period list from the
|
|
12266
|
-
* adapter on first use.
|
|
12267
|
-
*/
|
|
12268
|
-
periodGuard;
|
|
12269
|
-
/**
|
|
12270
|
-
* Optional back-reference to the owning vault's guard registry + a
|
|
12271
|
-
* read-only vault facade. When present, `Collection.put` and
|
|
12272
|
-
* `Collection.delete` consult the registry for guards declared
|
|
12273
|
-
* against this collection and run their `check` + `frozenFields`
|
|
12274
|
-
* before the adapter write. Absent in unit tests that construct
|
|
12275
|
-
* a Collection directly; production code always sets it via
|
|
12276
|
-
* `Vault.collection()`.
|
|
12277
|
-
*
|
|
12278
|
-
* Typed structurally rather than as `Vault` to avoid a circular
|
|
12279
|
-
* import (mirrors the `refEnforcer` / `joinResolver` pattern).
|
|
12280
|
-
*/
|
|
12281
|
-
guardSource;
|
|
12282
12257
|
/**
|
|
12283
12258
|
* Vault-internal hook for derivation dispatch. When set,
|
|
12284
12259
|
* `Collection.put` consults the registry after the source-write
|
|
12285
12260
|
* commits and writes derived outputs through `getCollection(name).put`.
|
|
12286
|
-
* Same structural-interface pattern as `guardSource` to avoid a
|
|
12287
|
-
* circular Vault import.
|
|
12288
12261
|
*/
|
|
12289
12262
|
derivationSource;
|
|
12290
12263
|
/**
|
|
12291
|
-
* Vault-internal hook for materialized-view dispatch
|
|
12264
|
+
* Vault-internal hook for materialized-view dispatch.
|
|
12292
12265
|
* Parallel to `derivationSource` — when set, `Collection.put` fires
|
|
12293
12266
|
* `MaterializedViewRegistry.onSourceWrite` after the source-write
|
|
12294
12267
|
* commits + after `dispatchDerivations` has run.
|
|
@@ -12344,6 +12317,7 @@ var Collection = class {
|
|
|
12344
12317
|
this.schemaUpdateGate = opts.schemaUpdateGate;
|
|
12345
12318
|
this.schemaFence = opts.schemaFence;
|
|
12346
12319
|
this.writeHooks = opts.writeHooks;
|
|
12320
|
+
this.subsystemBus = opts.subsystemBus;
|
|
12347
12321
|
this.activeTxId = opts.activeTxId;
|
|
12348
12322
|
this.blobStrategy = opts.blobStrategy ?? NO_BLOBS;
|
|
12349
12323
|
this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE;
|
|
@@ -12368,8 +12342,6 @@ var Collection = class {
|
|
|
12368
12342
|
this.crdtMode = opts.crdt;
|
|
12369
12343
|
this.syncAdapter = opts.syncAdapter;
|
|
12370
12344
|
this.onAccess = opts.onAccess;
|
|
12371
|
-
this.periodGuard = opts.periodGuard;
|
|
12372
|
-
this.guardSource = opts.guardSource;
|
|
12373
12345
|
this.derivationSource = opts.derivationSource;
|
|
12374
12346
|
this.materializedViewSource = opts.materializedViewSource;
|
|
12375
12347
|
this.tiers = opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null;
|
|
@@ -12571,21 +12543,23 @@ var Collection = class {
|
|
|
12571
12543
|
}
|
|
12572
12544
|
/**
|
|
12573
12545
|
* Create or update a record. Runs inside the hub's write-queue tracker
|
|
12574
|
-
*
|
|
12546
|
+
* so `hub.writeQueue.pending` reflects this write.
|
|
12575
12547
|
*
|
|
12576
12548
|
* @param id Record identifier.
|
|
12577
12549
|
* @param record The record body (validated by the collection's schema
|
|
12578
12550
|
* if one was attached at `vault.collection(...)` time).
|
|
12579
12551
|
* @param options Optional metadata for audit + import workflows.
|
|
12580
12552
|
* `reason` is stamped onto the resulting ledger entry
|
|
12581
|
-
*
|
|
12553
|
+
* so audit consumers can filter via
|
|
12582
12554
|
* `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
12583
12555
|
*/
|
|
12584
12556
|
async put(id, record, options) {
|
|
12585
12557
|
await this.schemaUpdateGate?.assertWritable();
|
|
12586
12558
|
await this.schemaFence?.assertWritable(this.name);
|
|
12559
|
+
const hooksActive = this.#hooksActive();
|
|
12560
|
+
const busAfterPut = (this.subsystemBus?.hasHandlers("afterPut") ?? false) && !(this.subsystemBus?.dispatching ?? false);
|
|
12587
12561
|
let event;
|
|
12588
|
-
if (
|
|
12562
|
+
if (hooksActive || busAfterPut) {
|
|
12589
12563
|
const prior = await this.#priorForHook(id);
|
|
12590
12564
|
event = {
|
|
12591
12565
|
op: prior.record === null ? "create" : "update",
|
|
@@ -12600,23 +12574,26 @@ var Collection = class {
|
|
|
12600
12574
|
baseVersion: prior.version,
|
|
12601
12575
|
version: prior.version + 1
|
|
12602
12576
|
};
|
|
12603
|
-
await this.writeHooks.runBefore(event);
|
|
12577
|
+
if (hooksActive) await this.writeHooks.runBefore(event);
|
|
12604
12578
|
}
|
|
12605
12579
|
if (this.writeQueue) await this.writeQueue.track(() => this.putInternal(id, record, options));
|
|
12606
12580
|
else await this.putInternal(id, record, options);
|
|
12607
|
-
if (event)
|
|
12581
|
+
if (event) {
|
|
12582
|
+
if (hooksActive) await this.writeHooks.runAfter(event);
|
|
12583
|
+
if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
|
|
12584
|
+
}
|
|
12608
12585
|
}
|
|
12609
|
-
/** @internal
|
|
12586
|
+
/** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
|
|
12610
12587
|
#hooksActive() {
|
|
12611
12588
|
return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
|
|
12612
12589
|
}
|
|
12613
12590
|
/**
|
|
12614
|
-
* @internal
|
|
12591
|
+
* @internal — resolve the prior record for a hook's `before` and
|
|
12615
12592
|
* its version. Critically, this uses the SAME basis `putInternal` writes from
|
|
12616
12593
|
* (the in-memory cache in eager mode; lru-then-adapter in lazy) — NOT a fresh
|
|
12617
12594
|
* store read — so `baseVersion`/`version` match the version actually written.
|
|
12618
12595
|
* A separate store read would diverge once another tab has advanced the shared
|
|
12619
|
-
* store past this tab's cache, breaking
|
|
12596
|
+
* store past this tab's cache, breaking cross-tab conflict detection.
|
|
12620
12597
|
*/
|
|
12621
12598
|
async #priorForHook(id) {
|
|
12622
12599
|
if (this.lazy && this.lru) {
|
|
@@ -12638,52 +12615,28 @@ var Collection = class {
|
|
|
12638
12615
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
12639
12616
|
throw new ReadOnlyError();
|
|
12640
12617
|
}
|
|
12641
|
-
if (this.
|
|
12642
|
-
const registry = this.guardSource.registry();
|
|
12643
|
-
const guards = registry.guardsFor(this.name);
|
|
12644
|
-
if (guards.length > 0) {
|
|
12645
|
-
const existingEnv = await this.adapter.get(this.vault, this.name, id);
|
|
12646
|
-
let existingRecord = null;
|
|
12647
|
-
if (existingEnv) {
|
|
12648
|
-
try {
|
|
12649
|
-
existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
|
|
12650
|
-
} catch {
|
|
12651
|
-
existingRecord = null;
|
|
12652
|
-
}
|
|
12653
|
-
}
|
|
12654
|
-
const incomingRecord = record;
|
|
12655
|
-
const ctx = {
|
|
12656
|
-
existing: existingRecord,
|
|
12657
|
-
vault: this.guardSource.readOnlyVault(),
|
|
12658
|
-
userId: this.keyring.userId,
|
|
12659
|
-
role: this.keyring.role
|
|
12660
|
-
};
|
|
12661
|
-
if (registry.isAmendmentActive()) {
|
|
12662
|
-
const vBefore = existingEnv?._v ?? 0;
|
|
12663
|
-
registry.collectChange(this.name, id, existingRecord, incomingRecord, vBefore, vBefore + 1);
|
|
12664
|
-
} else {
|
|
12665
|
-
await registry.runChecks(this.name, incomingRecord, ctx);
|
|
12666
|
-
const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
|
|
12667
|
-
for (const g of guards) {
|
|
12668
|
-
await GuardExecutor2.checkFrozenFields(g, id, existingRecord, incomingRecord);
|
|
12669
|
-
}
|
|
12670
|
-
}
|
|
12671
|
-
}
|
|
12672
|
-
}
|
|
12673
|
-
if (this.periodGuard !== void 0) {
|
|
12618
|
+
if (this.subsystemBus?.hasGateHandlers("beforePut")) {
|
|
12674
12619
|
const existingEnv = await this.adapter.get(this.vault, this.name, id);
|
|
12675
|
-
let
|
|
12620
|
+
let existingRecord = null;
|
|
12676
12621
|
if (existingEnv) {
|
|
12677
12622
|
try {
|
|
12678
|
-
|
|
12623
|
+
existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
|
|
12679
12624
|
} catch {
|
|
12680
|
-
|
|
12625
|
+
existingRecord = null;
|
|
12681
12626
|
}
|
|
12682
12627
|
}
|
|
12683
|
-
await this.
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12628
|
+
await this.subsystemBus.dispatchGate("beforePut", {
|
|
12629
|
+
op: existingEnv ? "update" : "create",
|
|
12630
|
+
vault: this.vault,
|
|
12631
|
+
collection: this.name,
|
|
12632
|
+
docId: id,
|
|
12633
|
+
incoming: record,
|
|
12634
|
+
existing: existingRecord,
|
|
12635
|
+
existingVersion: existingEnv?._v ?? 0,
|
|
12636
|
+
existingTs: existingEnv?._ts,
|
|
12637
|
+
userId: this.keyring.userId,
|
|
12638
|
+
role: this.keyring.role
|
|
12639
|
+
});
|
|
12687
12640
|
}
|
|
12688
12641
|
if (this.schema !== void 0) {
|
|
12689
12642
|
record = await validateSchemaInput(this.schema, record, `put(${id})`);
|
|
@@ -12869,7 +12822,7 @@ var Collection = class {
|
|
|
12869
12822
|
* Fire registered MV strategies whose dependency set includes this
|
|
12870
12823
|
* collection. Eager-mode MVs re-materialize inline via
|
|
12871
12824
|
* `MaterializedViewExecutor.refresh`; lazy / manual modes are
|
|
12872
|
-
* no-ops in the foundation
|
|
12825
|
+
* no-ops in the foundation; wired in the lazy-mode implementation.
|
|
12873
12826
|
*
|
|
12874
12827
|
* Skips entirely when the record being written is itself an
|
|
12875
12828
|
* MV-emitted row (carries `_materializedFrom`) — defensive guard
|
|
@@ -13013,13 +12966,15 @@ var Collection = class {
|
|
|
13013
12966
|
}
|
|
13014
12967
|
/**
|
|
13015
12968
|
* Delete a record by ID. Runs inside the hub's write-queue tracker
|
|
13016
|
-
*
|
|
12969
|
+
* so `hub.writeQueue.pending` reflects this write.
|
|
13017
12970
|
*/
|
|
13018
12971
|
async delete(id) {
|
|
13019
12972
|
await this.schemaUpdateGate?.assertWritable();
|
|
13020
12973
|
await this.schemaFence?.assertWritable(this.name);
|
|
12974
|
+
const hooksActive = this.#hooksActive();
|
|
12975
|
+
const busAfterDelete = (this.subsystemBus?.hasHandlers("afterDelete") ?? false) && !(this.subsystemBus?.dispatching ?? false);
|
|
13021
12976
|
let event;
|
|
13022
|
-
if (
|
|
12977
|
+
if (hooksActive || busAfterDelete) {
|
|
13023
12978
|
const prior = await this.#priorForHook(id);
|
|
13024
12979
|
event = {
|
|
13025
12980
|
op: "delete",
|
|
@@ -13034,14 +12989,17 @@ var Collection = class {
|
|
|
13034
12989
|
baseVersion: prior.version,
|
|
13035
12990
|
version: prior.version + 1
|
|
13036
12991
|
};
|
|
13037
|
-
await this.writeHooks.runBefore(event);
|
|
12992
|
+
if (hooksActive) await this.writeHooks.runBefore(event);
|
|
13038
12993
|
}
|
|
13039
12994
|
if (this.writeQueue) await this.writeQueue.track(() => this.deleteInternal(id));
|
|
13040
12995
|
else await this.deleteInternal(id);
|
|
13041
|
-
if (event)
|
|
12996
|
+
if (event) {
|
|
12997
|
+
if (hooksActive) await this.writeHooks.runAfter(event);
|
|
12998
|
+
if (busAfterDelete) await this.subsystemBus.dispatch("afterDelete", event);
|
|
12999
|
+
}
|
|
13042
13000
|
}
|
|
13043
13001
|
/**
|
|
13044
|
-
* @internal
|
|
13002
|
+
* @internal — bulk-rewrite every record through a cutover transform.
|
|
13045
13003
|
* Raw adapter path (bypasses the write gate + guards — the transform is
|
|
13046
13004
|
* trusted and runs only during the `migrating` phase). Bumps each
|
|
13047
13005
|
* record's `_v` and appends a ledger `op:'migration'` entry.
|
|
@@ -13080,8 +13038,7 @@ var Collection = class {
|
|
|
13080
13038
|
}
|
|
13081
13039
|
/**
|
|
13082
13040
|
* @internal — system-internal delete that bypasses user-facing
|
|
13083
|
-
* delete hooks (`onDelete`,
|
|
13084
|
-
* enforcer). Used by derivation tombstones (#144) and MV refresh
|
|
13041
|
+
* delete hooks (`onDelete`, FK ref enforcer). Used by derivation tombstones and MV refresh
|
|
13085
13042
|
* (Dim 14 v2) — system housekeeping shouldn't trip user invariants
|
|
13086
13043
|
* registered against the output collection. The ledger entry and
|
|
13087
13044
|
* history snapshot still fire so backup integrity and time-travel
|
|
@@ -13093,7 +13050,7 @@ var Collection = class {
|
|
|
13093
13050
|
*
|
|
13094
13051
|
* When a `txCtx` is supplied, the prior envelope is captured and
|
|
13095
13052
|
* pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
|
|
13096
|
-
* the
|
|
13053
|
+
* the rollback hardening for puts. Callers outside a
|
|
13097
13054
|
* multi-record transaction pass `null` and skip the tracking.
|
|
13098
13055
|
*
|
|
13099
13056
|
* Amendment composition: if `_internalDelete` runs while a vault's
|
|
@@ -13136,58 +13093,27 @@ var Collection = class {
|
|
|
13136
13093
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
13137
13094
|
throw new ReadOnlyError();
|
|
13138
13095
|
}
|
|
13139
|
-
if (this.
|
|
13140
|
-
const registry = this.guardSource.registry();
|
|
13141
|
-
const guards = registry.guardsFor(this.name);
|
|
13142
|
-
if (guards.length > 0) {
|
|
13143
|
-
const existingEnv = await this.adapter.get(this.vault, this.name, id);
|
|
13144
|
-
if (existingEnv) {
|
|
13145
|
-
let existingRecord = null;
|
|
13146
|
-
try {
|
|
13147
|
-
existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
|
|
13148
|
-
} catch {
|
|
13149
|
-
existingRecord = null;
|
|
13150
|
-
}
|
|
13151
|
-
if (registry.isAmendmentActive()) {
|
|
13152
|
-
const vBefore = existingEnv._v;
|
|
13153
|
-
registry.collectChange(
|
|
13154
|
-
this.name,
|
|
13155
|
-
id,
|
|
13156
|
-
existingRecord,
|
|
13157
|
-
null,
|
|
13158
|
-
vBefore,
|
|
13159
|
-
vBefore
|
|
13160
|
-
);
|
|
13161
|
-
} else if (!internal) {
|
|
13162
|
-
const ctx = {
|
|
13163
|
-
existing: existingRecord,
|
|
13164
|
-
vault: this.guardSource.readOnlyVault(),
|
|
13165
|
-
userId: this.keyring.userId,
|
|
13166
|
-
role: this.keyring.role
|
|
13167
|
-
};
|
|
13168
|
-
await registry.runOnDelete(
|
|
13169
|
-
this.name,
|
|
13170
|
-
existingRecord ?? {},
|
|
13171
|
-
ctx
|
|
13172
|
-
);
|
|
13173
|
-
}
|
|
13174
|
-
}
|
|
13175
|
-
}
|
|
13176
|
-
}
|
|
13177
|
-
if (!internal && this.periodGuard !== void 0) {
|
|
13096
|
+
if (this.subsystemBus?.hasGateHandlers("beforeDelete")) {
|
|
13178
13097
|
const existingEnv = await this.adapter.get(this.vault, this.name, id);
|
|
13179
|
-
let priorRecord = null;
|
|
13180
13098
|
if (existingEnv) {
|
|
13099
|
+
let existingRecord = null;
|
|
13181
13100
|
try {
|
|
13182
|
-
|
|
13101
|
+
existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
|
|
13183
13102
|
} catch {
|
|
13184
|
-
|
|
13103
|
+
existingRecord = null;
|
|
13185
13104
|
}
|
|
13105
|
+
await this.subsystemBus.dispatchGate("beforeDelete", {
|
|
13106
|
+
vault: this.vault,
|
|
13107
|
+
collection: this.name,
|
|
13108
|
+
docId: id,
|
|
13109
|
+
existing: existingRecord,
|
|
13110
|
+
existingVersion: existingEnv._v,
|
|
13111
|
+
existingTs: existingEnv._ts,
|
|
13112
|
+
internal,
|
|
13113
|
+
userId: this.keyring.userId,
|
|
13114
|
+
role: this.keyring.role
|
|
13115
|
+
});
|
|
13186
13116
|
}
|
|
13187
|
-
await this.periodGuard(
|
|
13188
|
-
existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,
|
|
13189
|
-
null
|
|
13190
|
-
);
|
|
13191
13117
|
}
|
|
13192
13118
|
if (!internal && this.refEnforcer !== void 0) {
|
|
13193
13119
|
await this.refEnforcer.enforceRefsOnDelete(this.name, id);
|
|
@@ -13248,7 +13174,7 @@ var Collection = class {
|
|
|
13248
13174
|
}
|
|
13249
13175
|
/**
|
|
13250
13176
|
* Cascade deletes of array-shape derived rows when a source row is
|
|
13251
|
-
* deleted
|
|
13177
|
+
* deleted. Reads each registered strategy's fanout sidecar
|
|
13252
13178
|
* for this source id, deletes every listed derived row, then
|
|
13253
13179
|
* deletes the sidecar itself.
|
|
13254
13180
|
*
|
|
@@ -13287,8 +13213,8 @@ var Collection = class {
|
|
|
13287
13213
|
}
|
|
13288
13214
|
}
|
|
13289
13215
|
/**
|
|
13290
|
-
* Mirror of {@link dispatchMaterializedViews} for the delete path
|
|
13291
|
-
*
|
|
13216
|
+
* Mirror of {@link dispatchMaterializedViews} for the delete path.
|
|
13217
|
+
* No record content is available (it's gone), so the
|
|
13292
13218
|
* `_materializedFrom` skip used by the put-side dispatch doesn't
|
|
13293
13219
|
* apply here — instead, the recursion guard is the `internal` gate
|
|
13294
13220
|
* at the `_doDelete` call site above.
|
|
@@ -13818,7 +13744,7 @@ var Collection = class {
|
|
|
13818
13744
|
* .aggregate({ total: sum('amount'), n: count() })
|
|
13819
13745
|
* ```
|
|
13820
13746
|
*
|
|
13821
|
-
* **Lazy-MV gap
|
|
13747
|
+
* **Lazy-MV gap:** `scan()` is synchronous-build and does
|
|
13822
13748
|
* NOT trigger lazy materialized-view resolve-on-read. For lazy
|
|
13823
13749
|
* MVs, call `list()` (which DOES resolve) or `vault.refreshView(name)`
|
|
13824
13750
|
* before scanning. Same shape as the `query()` limitation.
|
|
@@ -13899,7 +13825,7 @@ var Collection = class {
|
|
|
13899
13825
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
13900
13826
|
}
|
|
13901
13827
|
/**
|
|
13902
|
-
*
|
|
13828
|
+
* Apply a peer tab's committed write to THIS tab's in-memory view:
|
|
13903
13829
|
* re-read the (already-persisted) envelope from the shared store + refresh
|
|
13904
13830
|
* cache/indexes, then emit a `change` event so reactive consumers re-render.
|
|
13905
13831
|
* Never writes to the store and never fires write hooks, so it cannot loop.
|
|
@@ -13908,7 +13834,7 @@ var Collection = class {
|
|
|
13908
13834
|
await this._invalidateCacheEntry(id);
|
|
13909
13835
|
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
|
|
13910
13836
|
}
|
|
13911
|
-
/** @internal
|
|
13837
|
+
/** @internal — the current in-memory record without a store read (for conflict capture). */
|
|
13912
13838
|
_peekCached(id) {
|
|
13913
13839
|
const entry = this.lazy && this.lru ? this.lru.get(id) : this.cache.get(id);
|
|
13914
13840
|
return entry ? entry.record : null;
|
|
@@ -14959,7 +14885,7 @@ var OverlayedCollection = class {
|
|
|
14959
14885
|
// error pointing at the relevant issue — so consumers don't hit a
|
|
14960
14886
|
// cryptic `undefined is not a function` runtime crash.
|
|
14961
14887
|
//
|
|
14962
|
-
//
|
|
14888
|
+
// Throw-stubs so consumers get actionable errors rather than cryptic crashes.
|
|
14963
14889
|
/** @throws — chainable Query<T> over a virtual collection is deferred. */
|
|
14964
14890
|
query() {
|
|
14965
14891
|
throw new Error(
|
|
@@ -16270,23 +16196,23 @@ var Vault = class {
|
|
|
16270
16196
|
* `null` for vaults that never register any guard strategy. The
|
|
16271
16197
|
* runtime class is dynamic-imported on demand so consumers that
|
|
16272
16198
|
* never use guards don't pull `GuardRegistry`/`GuardExecutor` into
|
|
16273
|
-
* their bundle
|
|
16199
|
+
* their bundle.
|
|
16274
16200
|
*/
|
|
16275
16201
|
guardRegistry = null;
|
|
16276
16202
|
/**
|
|
16277
16203
|
* Per-vault derivation registry. Same lazy-load contract as
|
|
16278
16204
|
* `guardRegistry` — `null` until `_initDerivations()` runs with at
|
|
16279
|
-
* least one strategy handle.
|
|
16205
|
+
* least one strategy handle.
|
|
16280
16206
|
*/
|
|
16281
16207
|
derivationRegistry = null;
|
|
16282
16208
|
/**
|
|
16283
|
-
* Per-vault materialized-view registry
|
|
16209
|
+
* Per-vault materialized-view registry. Same lazy-load
|
|
16284
16210
|
* contract as `derivationRegistry` — `null` until
|
|
16285
16211
|
* `_initMaterializedViews()` runs with at least one MV handle.
|
|
16286
16212
|
*/
|
|
16287
16213
|
materializedViewRegistry = null;
|
|
16288
16214
|
/**
|
|
16289
|
-
* Per-vault overlay registry
|
|
16215
|
+
* Per-vault overlay registry. Same lazy-load contract as
|
|
16290
16216
|
* `materializedViewRegistry` — `null` until `_initOverlayedViews()`
|
|
16291
16217
|
* runs with at least one handle.
|
|
16292
16218
|
*/
|
|
@@ -16307,7 +16233,7 @@ var Vault = class {
|
|
|
16307
16233
|
* target this vault session's keyringId. There is no method to write
|
|
16308
16234
|
* another principal's envelope (own-only write rule, structural).
|
|
16309
16235
|
* - Read-anyone: `get(keyringId)`, `list()` — read other principals'
|
|
16310
|
-
* envelopes, subject to the `view-team-profiles` policy gate
|
|
16236
|
+
* envelopes, subject to the `view-team-profiles` policy gate.
|
|
16311
16237
|
* - Reactive: `subscribe(id, cb)`, `live(id)` — fire on local writes.
|
|
16312
16238
|
*
|
|
16313
16239
|
* @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
|
|
@@ -16327,12 +16253,12 @@ var Vault = class {
|
|
|
16327
16253
|
*/
|
|
16328
16254
|
reloadKeyring;
|
|
16329
16255
|
collectionCache = /* @__PURE__ */ new Map();
|
|
16330
|
-
/**
|
|
16256
|
+
/** Vault-level schema cutover fence/controller. */
|
|
16331
16257
|
schemaFence;
|
|
16332
|
-
/**
|
|
16258
|
+
/** Per-client heartbeat/watcher; started lazily on cutover registration. */
|
|
16333
16259
|
#fenceWatcher;
|
|
16334
16260
|
#fenceCoordinationStarted = false;
|
|
16335
|
-
/**
|
|
16261
|
+
/** Per-collection registered schema-update strategy names. */
|
|
16336
16262
|
#schemaUpdateNames = /* @__PURE__ */ new Map();
|
|
16337
16263
|
/**
|
|
16338
16264
|
* per-collection `blobFields` retention/TTL config.
|
|
@@ -16406,8 +16332,7 @@ var Vault = class {
|
|
|
16406
16332
|
* Cache of closed/opened accounting periods.
|
|
16407
16333
|
* Populated on first `closePeriod` / `openPeriod` / `listPeriods` /
|
|
16408
16334
|
* per-collection write call. Kept in memory as an ordered list (by
|
|
16409
|
-
* `closedAt`) so
|
|
16410
|
-
* each collection's put/delete path.
|
|
16335
|
+
* `closedAt`) so period checks run fast when the gate bus fires.
|
|
16411
16336
|
*
|
|
16412
16337
|
* Sentinel `null` means "not yet loaded" — the first consumer
|
|
16413
16338
|
* triggers a one-time `loadPeriods()` pass. Every subsequent
|
|
@@ -16602,6 +16527,7 @@ var Vault = class {
|
|
|
16602
16527
|
emitter: this.emitter,
|
|
16603
16528
|
writeQueue: this.noydb._writeQueueTracker,
|
|
16604
16529
|
writeHooks: this.noydb._writeHooks,
|
|
16530
|
+
subsystemBus: this.noydb._subsystemBus,
|
|
16605
16531
|
activeTxId: () => this.noydb._activeTxContextOrNull?.txId ?? null,
|
|
16606
16532
|
schemaUpdateGate,
|
|
16607
16533
|
schemaFence: this.schemaFence,
|
|
@@ -16624,18 +16550,12 @@ var Vault = class {
|
|
|
16624
16550
|
defaultLocale: this.locale,
|
|
16625
16551
|
onRegisterConflictResolver: this.onRegisterConflictResolver,
|
|
16626
16552
|
onAccess: (op, id) => this._logConsent(op, collectionName, id),
|
|
16627
|
-
|
|
16628
|
-
// Guard
|
|
16629
|
-
//
|
|
16630
|
-
//
|
|
16631
|
-
// `if (this.
|
|
16632
|
-
//
|
|
16633
|
-
...this.guardRegistry !== null ? {
|
|
16634
|
-
guardSource: {
|
|
16635
|
-
registry: () => this.guardRegistry,
|
|
16636
|
-
readOnlyVault: () => this._ensureReadOnlyFacade()
|
|
16637
|
-
}
|
|
16638
|
-
} : {},
|
|
16553
|
+
// Derivation source is only wired when the corresponding registry
|
|
16554
|
+
// has been initialised. Guard source was removed in Track A slice 3b
|
|
16555
|
+
// — guards now run via the gate bus in Noydb.#registerGuardGate.
|
|
16556
|
+
// Vaults without derivations skip this so `Collection.put`'s
|
|
16557
|
+
// `if (this.derivationSource)` branch no-ops without touching the
|
|
16558
|
+
// derivation subsystem code.
|
|
16639
16559
|
...this.derivationRegistry !== null ? {
|
|
16640
16560
|
derivationSource: {
|
|
16641
16561
|
registry: () => this.derivationRegistry,
|
|
@@ -16725,7 +16645,7 @@ var Vault = class {
|
|
|
16725
16645
|
await Promise.allSettled(pending);
|
|
16726
16646
|
}
|
|
16727
16647
|
/**
|
|
16728
|
-
* Run a coordinated schema cutover
|
|
16648
|
+
* Run a coordinated schema cutover. Drains pending writes, waits
|
|
16729
16649
|
* for the active client set to quiesce (the ack-barrier), applies every
|
|
16730
16650
|
* pending collection transform in bulk, bumps the vault schema generation,
|
|
16731
16651
|
* and clears the fence. Returns the count of collections migrated.
|
|
@@ -16743,9 +16663,9 @@ var Vault = class {
|
|
|
16743
16663
|
await coll._applyCutoverTransform(transform);
|
|
16744
16664
|
}
|
|
16745
16665
|
/**
|
|
16746
|
-
*
|
|
16666
|
+
* Refresh a loaded collection's view of one document from a peer
|
|
16747
16667
|
* tab's broadcast. No-op when the collection isn't loaded in this tab
|
|
16748
|
-
* (it will read fresh on next open). Mirrors
|
|
16668
|
+
* (it will read fresh on next open). Mirrors `#runCutoverTransform`'s guard.
|
|
16749
16669
|
*/
|
|
16750
16670
|
async _applyRemoteWrite(collectionName, docId, action) {
|
|
16751
16671
|
const coll = this.collectionCache.get(collectionName);
|
|
@@ -16753,9 +16673,9 @@ var Vault = class {
|
|
|
16753
16673
|
await coll._applyRemoteChange(docId, action);
|
|
16754
16674
|
}
|
|
16755
16675
|
/**
|
|
16756
|
-
*
|
|
16676
|
+
* For a detected conflict: capture this tab's clobbered record,
|
|
16757
16677
|
* read the common ancestor from history, converge the cache to the store's
|
|
16758
|
-
* authoritative value (the
|
|
16678
|
+
* authoritative value (the re-read), and return all three for the
|
|
16759
16679
|
* WriteConflict payload. Returns null when the collection isn't loaded.
|
|
16760
16680
|
*/
|
|
16761
16681
|
async _captureAndConverge(collectionName, docId, action, baseV) {
|
|
@@ -16772,15 +16692,15 @@ var Vault = class {
|
|
|
16772
16692
|
const remote = await coll.get(docId);
|
|
16773
16693
|
return { local, remote, base };
|
|
16774
16694
|
}
|
|
16775
|
-
/** Recover a stuck cutover fence
|
|
16695
|
+
/** Recover a stuck cutover fence — reset to normal without bumping. */
|
|
16776
16696
|
async abortSchemaCutover() {
|
|
16777
16697
|
await this.schemaFence.abort();
|
|
16778
16698
|
}
|
|
16779
|
-
/** Current schema-cutover fence state for this vault
|
|
16699
|
+
/** Current schema-cutover fence state for this vault. Thin live read. */
|
|
16780
16700
|
async schemaFenceState() {
|
|
16781
16701
|
return loadFence(this.adapter, this.name);
|
|
16782
16702
|
}
|
|
16783
|
-
/** @internal Start the per-client heartbeat + fence watcher once a cutover is registered
|
|
16703
|
+
/** @internal Start the per-client heartbeat + fence watcher once a cutover is registered. */
|
|
16784
16704
|
_ensureFenceCoordination() {
|
|
16785
16705
|
if (this.#fenceCoordinationStarted) return;
|
|
16786
16706
|
this.#fenceCoordinationStarted = true;
|
|
@@ -17453,7 +17373,7 @@ var Vault = class {
|
|
|
17453
17373
|
* Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
|
|
17454
17374
|
* the registry with the supplied strategy handles. No-op when the
|
|
17455
17375
|
* handles array is empty — keeps the guard subsystem out of the
|
|
17456
|
-
* floor bundle for consumers that don't use guards
|
|
17376
|
+
* floor bundle for consumers that don't use guards.
|
|
17457
17377
|
*
|
|
17458
17378
|
* The read-only facade is eagerly instantiated here so the sync
|
|
17459
17379
|
* accessor `_getReadOnlyFacade()` (called from the tx amendment
|
|
@@ -17471,10 +17391,9 @@ var Vault = class {
|
|
|
17471
17391
|
this.readOnlyFacade = new ReadOnlyVaultFacade2(this);
|
|
17472
17392
|
}
|
|
17473
17393
|
/**
|
|
17474
|
-
* @internal —
|
|
17475
|
-
* vaults that never registered any guard
|
|
17476
|
-
* gate on null
|
|
17477
|
-
* `Collection` already do this transitively).
|
|
17394
|
+
* @internal — The gate handler in Noydb.#registerGuardGate calls into
|
|
17395
|
+
* this. Returns `null` for vaults that never registered any guard
|
|
17396
|
+
* strategy. Callers MUST gate on null.
|
|
17478
17397
|
*/
|
|
17479
17398
|
_getGuardRegistry() {
|
|
17480
17399
|
return this.guardRegistry;
|
|
@@ -17485,7 +17404,7 @@ var Vault = class {
|
|
|
17485
17404
|
* derivation strategies (async because `strategyHash` computation
|
|
17486
17405
|
* goes through `crypto.subtle.digest`). No-op when the handles
|
|
17487
17406
|
* array is empty — keeps the derivation subsystem out of the floor
|
|
17488
|
-
* bundle for consumers that don't use derivations
|
|
17407
|
+
* bundle for consumers that don't use derivations. Throws
|
|
17489
17408
|
* `DerivationCycleError` if a cycle is detected after registration.
|
|
17490
17409
|
*/
|
|
17491
17410
|
async _initDerivations(handles) {
|
|
@@ -17517,7 +17436,7 @@ var Vault = class {
|
|
|
17517
17436
|
* MV spec (which invokes its `query()` once for dependency
|
|
17518
17437
|
* analysis), then runs the unified cycle detection across the MV +
|
|
17519
17438
|
* derivation graphs. No-op when the handles array is empty — keeps
|
|
17520
|
-
* the MV subsystem out of the floor bundle (mirrors
|
|
17439
|
+
* the MV subsystem out of the floor bundle (mirrors the derivation lazy-import pattern).
|
|
17521
17440
|
* Throws `MaterializedViewCycleError` if a cycle is detected.
|
|
17522
17441
|
*/
|
|
17523
17442
|
async _initMaterializedViews(handles) {
|
|
@@ -17574,13 +17493,13 @@ var Vault = class {
|
|
|
17574
17493
|
return this.overlayedViewRegistry;
|
|
17575
17494
|
}
|
|
17576
17495
|
/**
|
|
17577
|
-
* Manual re-materialize for a single registered MV
|
|
17496
|
+
* Manual re-materialize for a single registered MV. Useful
|
|
17578
17497
|
* for `refresh: 'manual'` MVs (whose consumer drives refreshes
|
|
17579
17498
|
* externally), for stale-bit recovery on vault re-open, and as the
|
|
17580
17499
|
* explicit bulk-recompute escape hatch after a strategy change.
|
|
17581
17500
|
*
|
|
17582
|
-
* Returns `{ written, deleted, failed }`. `deleted` is always 0
|
|
17583
|
-
*
|
|
17501
|
+
* Returns `{ written, deleted, failed }`. `deleted` is always 0
|
|
17502
|
+
* when tombstoning is not enabled.
|
|
17584
17503
|
*
|
|
17585
17504
|
* Throws if `name` is not a registered MV.
|
|
17586
17505
|
*/
|
|
@@ -17676,22 +17595,19 @@ var Vault = class {
|
|
|
17676
17595
|
/**
|
|
17677
17596
|
* @internal — exposed for `runTransaction({ amendment: true })` so
|
|
17678
17597
|
* the amendment invariant runner can pass the SAME read-only vault
|
|
17679
|
-
* facade that the
|
|
17680
|
-
*
|
|
17681
|
-
* `
|
|
17682
|
-
*
|
|
17683
|
-
*
|
|
17598
|
+
* facade that the gate handler in Noydb.#registerGuardGate uses.
|
|
17599
|
+
* Eagerly instantiated by `_initGuards()` so this accessor stays
|
|
17600
|
+
* synchronous; returns `null` for vaults that never registered any
|
|
17601
|
+
* guard (amendments require at least one guard, so the caller should
|
|
17602
|
+
* never see null).
|
|
17684
17603
|
*/
|
|
17685
17604
|
_getReadOnlyFacade() {
|
|
17686
17605
|
return this.readOnlyFacade;
|
|
17687
17606
|
}
|
|
17688
17607
|
/**
|
|
17689
|
-
* Internal lazy-allocator for the read-only facade. Used
|
|
17690
|
-
*
|
|
17691
|
-
*
|
|
17692
|
-
* invocation (theoretically impossible — `Noydb.openVault` awaits
|
|
17693
|
-
* `_initGuards` before returning — but we keep the defensive lazy
|
|
17694
|
-
* path so the closure's contract stays "always returns a facade").
|
|
17608
|
+
* Internal lazy-allocator for the read-only facade. Used as a
|
|
17609
|
+
* defensive fallback; in practice `_initGuards()` eagerly
|
|
17610
|
+
* instantiates this, so the lazy path is a no-op.
|
|
17695
17611
|
*/
|
|
17696
17612
|
_ensureReadOnlyFacade() {
|
|
17697
17613
|
if (this.readOnlyFacade !== null) return this.readOnlyFacade;
|
|
@@ -18146,7 +18062,7 @@ var Vault = class {
|
|
|
18146
18062
|
const all = await this._loadPeriodsCache();
|
|
18147
18063
|
return all.find((p) => p.name === name) ?? null;
|
|
18148
18064
|
}
|
|
18149
|
-
/** @internal —
|
|
18065
|
+
/** @internal — called by the gate bus before put/delete. */
|
|
18150
18066
|
async _assertTsWritable(existing, incoming) {
|
|
18151
18067
|
if (existing === null && incoming === null) return;
|
|
18152
18068
|
if (this.periodCache === null) {
|
|
@@ -18234,7 +18150,7 @@ var Vault = class {
|
|
|
18234
18150
|
return dumpVaultSchema(this, opts);
|
|
18235
18151
|
}
|
|
18236
18152
|
/**
|
|
18237
|
-
* Lightweight read of the vault's registered schema
|
|
18153
|
+
* Lightweight read of the vault's registered schema: collections
|
|
18238
18154
|
* (+ doc counts), guards, materialized views, schema-update strategies,
|
|
18239
18155
|
* and the unlocked user's grants. Cheap — one `adapter.list` per
|
|
18240
18156
|
* collection, no decryption. For a full snapshot + stats use dumpSchema().
|
|
@@ -19021,6 +18937,110 @@ var WriteHookRegistry = class {
|
|
|
19021
18937
|
}
|
|
19022
18938
|
};
|
|
19023
18939
|
|
|
18940
|
+
// src/subsystem-bus.ts
|
|
18941
|
+
var SubsystemBus = class {
|
|
18942
|
+
#handlers = /* @__PURE__ */ new Map();
|
|
18943
|
+
#gateHandlers = /* @__PURE__ */ new Map();
|
|
18944
|
+
#depth = 0;
|
|
18945
|
+
/** Register a handler for an observe point. Returns an unsubscribe fn. */
|
|
18946
|
+
register(point, handler) {
|
|
18947
|
+
let arr = this.#handlers.get(point);
|
|
18948
|
+
if (!arr) {
|
|
18949
|
+
arr = [];
|
|
18950
|
+
this.#handlers.set(point, arr);
|
|
18951
|
+
}
|
|
18952
|
+
arr.push(handler);
|
|
18953
|
+
return () => {
|
|
18954
|
+
const a = this.#handlers.get(point);
|
|
18955
|
+
if (!a) return;
|
|
18956
|
+
const i = a.indexOf(handler);
|
|
18957
|
+
if (i >= 0) a.splice(i, 1);
|
|
18958
|
+
};
|
|
18959
|
+
}
|
|
18960
|
+
/** Cheap gate for the write path — true when any handler is registered for the point. */
|
|
18961
|
+
hasHandlers(point) {
|
|
18962
|
+
const a = this.#handlers.get(point);
|
|
18963
|
+
return a !== void 0 && a.length > 0;
|
|
18964
|
+
}
|
|
18965
|
+
/**
|
|
18966
|
+
* True while one or more dispatches are in flight. Backed by a depth counter
|
|
18967
|
+
* so that two concurrent async dispatches (`Promise.all([put('a'), put('b')])`
|
|
18968
|
+
* each captured `busAfterPut=true` at their respective put() tops while depth
|
|
18969
|
+
* was 0) both proceed independently — the counter stays > 0 until BOTH finish,
|
|
18970
|
+
* so any nested write attempted by a handler still sees `dispatching === true`
|
|
18971
|
+
* and is suppressed by the write-path gate in `collection.ts`
|
|
18972
|
+
* (`busAfterPut = hasHandlers('afterPut') && !dispatching`). Re-entrancy
|
|
18973
|
+
* suppression lives exclusively on that write-path gate; concurrent independent
|
|
18974
|
+
* dispatches must not drop each other's events.
|
|
18975
|
+
*/
|
|
18976
|
+
get dispatching() {
|
|
18977
|
+
return this.#depth > 0;
|
|
18978
|
+
}
|
|
18979
|
+
/**
|
|
18980
|
+
* Dispatch in registration order, awaited. Per-handler errors are warned, not
|
|
18981
|
+
* thrown — an observe handler must never abort a completed write. A
|
|
18982
|
+
* re-entrancy guard suppresses nested firing so a handler that itself writes
|
|
18983
|
+
* cannot loop (same rationale as WriteHookRegistry.#suppressed).
|
|
18984
|
+
*/
|
|
18985
|
+
async dispatch(point, event) {
|
|
18986
|
+
const a = this.#handlers.get(point);
|
|
18987
|
+
if (!a || a.length === 0) return;
|
|
18988
|
+
this.#depth++;
|
|
18989
|
+
try {
|
|
18990
|
+
for (const h of a.slice()) {
|
|
18991
|
+
try {
|
|
18992
|
+
await h(event);
|
|
18993
|
+
} catch (err) {
|
|
18994
|
+
console.warn(
|
|
18995
|
+
`[noy-db] subsystem observe handler failed at ${point}: ` + (err instanceof Error ? err.message : String(err))
|
|
18996
|
+
);
|
|
18997
|
+
}
|
|
18998
|
+
}
|
|
18999
|
+
} finally {
|
|
19000
|
+
this.#depth--;
|
|
19001
|
+
}
|
|
19002
|
+
}
|
|
19003
|
+
/** Register a write-gating handler. A throw from the handler ABORTS the write. Returns an unsubscribe fn. */
|
|
19004
|
+
registerGate(point, handler) {
|
|
19005
|
+
let arr = this.#gateHandlers.get(point);
|
|
19006
|
+
if (!arr) {
|
|
19007
|
+
arr = [];
|
|
19008
|
+
this.#gateHandlers.set(point, arr);
|
|
19009
|
+
}
|
|
19010
|
+
arr.push(handler);
|
|
19011
|
+
return () => {
|
|
19012
|
+
const a = this.#gateHandlers.get(point);
|
|
19013
|
+
if (!a) return;
|
|
19014
|
+
const i = a.indexOf(handler);
|
|
19015
|
+
if (i >= 0) a.splice(i, 1);
|
|
19016
|
+
};
|
|
19017
|
+
}
|
|
19018
|
+
/** Cheap gate for the write path — true when any gate handler is registered for the point. */
|
|
19019
|
+
hasGateHandlers(point) {
|
|
19020
|
+
const a = this.#gateHandlers.get(point);
|
|
19021
|
+
return a !== void 0 && a.length > 0;
|
|
19022
|
+
}
|
|
19023
|
+
/**
|
|
19024
|
+
* Run gate handlers in registration order, awaited. Unlike `dispatch`
|
|
19025
|
+
* (observe), a handler throw is NOT swallowed — it PROPAGATES, aborting the
|
|
19026
|
+
* write before it reaches the store. The first throw stops the remaining
|
|
19027
|
+
* handlers (fail-fast). This is the seam guards/periods migrate onto.
|
|
19028
|
+
*
|
|
19029
|
+
* Note: gate handlers are validators that read, not write. A gate handler
|
|
19030
|
+
* that writes back into the same collection would re-enter the write path
|
|
19031
|
+
* and re-dispatch this point; loop-suppression for that case is deferred to
|
|
19032
|
+
* the migration slice (contract: gate handlers must not perform writes that
|
|
19033
|
+
* re-trigger their own point).
|
|
19034
|
+
*/
|
|
19035
|
+
async dispatchGate(point, event) {
|
|
19036
|
+
const a = this.#gateHandlers.get(point);
|
|
19037
|
+
if (!a || a.length === 0) return;
|
|
19038
|
+
for (const h of a.slice()) {
|
|
19039
|
+
await h(event);
|
|
19040
|
+
}
|
|
19041
|
+
}
|
|
19042
|
+
};
|
|
19043
|
+
|
|
19024
19044
|
// src/tab-coordination.ts
|
|
19025
19045
|
var TabCoordinator = class {
|
|
19026
19046
|
tabId;
|
|
@@ -19336,9 +19356,9 @@ var PERSONAL_POLICY = Object.freeze({
|
|
|
19336
19356
|
minTier: 1,
|
|
19337
19357
|
enabled: true
|
|
19338
19358
|
},
|
|
19339
|
-
// rotate-recovery
|
|
19340
|
-
// when the user remembers their passphrase. PERSONAL
|
|
19341
|
-
//
|
|
19359
|
+
// rotate-recovery: deliberate paper-sheet regeneration
|
|
19360
|
+
// when the user remembers their passphrase. PERSONAL allows tier-1 —
|
|
19361
|
+
// knowing the passphrase is enough.
|
|
19342
19362
|
"rotate-recovery": { minTier: 1 },
|
|
19343
19363
|
"enroll-authenticator": { minTier: 1 },
|
|
19344
19364
|
"remove-authenticator": { minTier: 1 },
|
|
@@ -19372,7 +19392,7 @@ var PERSONAL_POLICY = Object.freeze({
|
|
|
19372
19392
|
minTier: 1,
|
|
19373
19393
|
enabled: false
|
|
19374
19394
|
},
|
|
19375
|
-
// ─── User envelope gates
|
|
19395
|
+
// ─── User envelope gates ──────────────────────────────────────────
|
|
19376
19396
|
// edit-own-profile: tier 3 floor — any active session can edit their
|
|
19377
19397
|
// own profile/preferences. Tightening to require a TOTP for
|
|
19378
19398
|
// profile changes is a one-line override.
|
|
@@ -19399,7 +19419,7 @@ var STRICT_POLICY = Object.freeze({
|
|
|
19399
19419
|
minTier: 1,
|
|
19400
19420
|
enabled: true
|
|
19401
19421
|
},
|
|
19402
|
-
// rotate-recovery
|
|
19422
|
+
// rotate-recovery: STRICT requires an off-device factor —
|
|
19403
19423
|
// rotating recovery is an off-site-trust event; a stolen unlocked
|
|
19404
19424
|
// laptop must not be able to silently mint a new sheet for the
|
|
19405
19425
|
// attacker. Matches the `peer-recover-user` STRICT default.
|
|
@@ -19472,7 +19492,7 @@ var STRICT_POLICY = Object.freeze({
|
|
|
19472
19492
|
minTier: 1,
|
|
19473
19493
|
enabled: false
|
|
19474
19494
|
},
|
|
19475
|
-
// ─── User envelope gates
|
|
19495
|
+
// ─── User envelope gates ──────────────────────────────────────────
|
|
19476
19496
|
// STRICT: profile edits require a TOTP/email-OTP factor (typical
|
|
19477
19497
|
// shared-workstation hardening — your name/avatar shouldn't change
|
|
19478
19498
|
// without a fresh second-factor proof).
|
|
@@ -19583,13 +19603,14 @@ var Noydb = class {
|
|
|
19583
19603
|
emitter = new NoydbEventEmitter();
|
|
19584
19604
|
writeQueueTracker = new WriteQueueTracker();
|
|
19585
19605
|
writeHooks = new WriteHookRegistry();
|
|
19606
|
+
subsystemBus = new SubsystemBus();
|
|
19586
19607
|
clientId = generateULID();
|
|
19587
19608
|
vaultCache = /* @__PURE__ */ new Map();
|
|
19588
19609
|
keyringCache = /* @__PURE__ */ new Map();
|
|
19589
19610
|
syncEngines = /* @__PURE__ */ new Map();
|
|
19590
19611
|
/**
|
|
19591
19612
|
* Per-vault active session tier — defaults to `1` after a passphrase
|
|
19592
|
-
* unlock; tier-2 / tier-3 unlocks
|
|
19613
|
+
* unlock; tier-2 / tier-3 unlocks downgrade it. Used by
|
|
19593
19614
|
* {@link checkGate} to evaluate `gate.minTier`.
|
|
19594
19615
|
*/
|
|
19595
19616
|
activeTier = /* @__PURE__ */ new Map();
|
|
@@ -19599,14 +19620,14 @@ var Noydb = class {
|
|
|
19599
19620
|
*/
|
|
19600
19621
|
policyCache = /* @__PURE__ */ new Map();
|
|
19601
19622
|
/**
|
|
19602
|
-
* One-shot bypass for the managed-mode strong-recovery check
|
|
19623
|
+
* One-shot bypass for the managed-mode strong-recovery check.
|
|
19603
19624
|
* Set true by {@link openVaultAndEnrollRecovery} for the duration of
|
|
19604
19625
|
* the bootstrap window so the keyring can be created before the
|
|
19605
19626
|
* strong recovery is enrolled. Always cleared (try/finally).
|
|
19606
19627
|
* @internal
|
|
19607
19628
|
*/
|
|
19608
19629
|
_skipNextManagedRecoveryCheck = false;
|
|
19609
|
-
/** Per-vault tier-3 (PIN / quick-resume) state
|
|
19630
|
+
/** Per-vault tier-3 (PIN / quick-resume) state. */
|
|
19610
19631
|
quickUnlock = new QuickUnlockStore();
|
|
19611
19632
|
/**
|
|
19612
19633
|
* Resolved public-envelope schema. Lazily computed once from
|
|
@@ -19616,9 +19637,9 @@ var Noydb = class {
|
|
|
19616
19637
|
publicEnvelopeSchema;
|
|
19617
19638
|
closed = false;
|
|
19618
19639
|
sessionTimer = null;
|
|
19619
|
-
/** Same-device multi-tab coordinator
|
|
19640
|
+
/** Same-device multi-tab coordinator; created on `enableTabCoordination()`. */
|
|
19620
19641
|
tabCoordinator;
|
|
19621
|
-
/** Cross-tab write relay
|
|
19642
|
+
/** Cross-tab write relay; created on `enableTabCoordination()`. */
|
|
19622
19643
|
writeRelay;
|
|
19623
19644
|
/** Per-vault policy enforcers. */
|
|
19624
19645
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
@@ -19631,8 +19652,8 @@ var Noydb = class {
|
|
|
19631
19652
|
* the same function's `finally` block. Side-effect writes triggered
|
|
19632
19653
|
* during a staged op's `Collection.put` (today: eager derivation
|
|
19633
19654
|
* outputs) register their pre-write envelope on `_executed` here so
|
|
19634
|
-
* a mid-batch failure rolls them back alongside the main staged ops
|
|
19635
|
-
*
|
|
19655
|
+
* a mid-batch failure rolls them back alongside the main staged ops.
|
|
19656
|
+
* `null` outside of Phase 2.
|
|
19636
19657
|
* @internal
|
|
19637
19658
|
*/
|
|
19638
19659
|
_activeTxContext = null;
|
|
@@ -19653,8 +19674,95 @@ var Noydb = class {
|
|
|
19653
19674
|
if (options.sessionPolicy) {
|
|
19654
19675
|
this.sessionStrategy.validateSessionPolicy(options.sessionPolicy);
|
|
19655
19676
|
}
|
|
19677
|
+
this.#registerGuardGate();
|
|
19678
|
+
this.#registerPeriodGate();
|
|
19656
19679
|
this.resetSessionTimer();
|
|
19657
19680
|
}
|
|
19681
|
+
// Track A — guards migration. Registers record-lock / field-freeze / onDelete
|
|
19682
|
+
// / amendment-collect as gate-bus handlers (only when guards are opted in, so
|
|
19683
|
+
// the write path is zero-cost otherwise). Resolves the live vault's
|
|
19684
|
+
// GuardRegistry per dispatch. Registered BEFORE the period gate so guard
|
|
19685
|
+
// checks run first. The amendment branch is a side-effect (collectChange),
|
|
19686
|
+
// NOT a throw — and runs even for internal deletes (an amendment invariant
|
|
19687
|
+
// must see system housekeeping tombstones); onDelete/checks run only for
|
|
19688
|
+
// user (non-internal) operations.
|
|
19689
|
+
#registerGuardGate() {
|
|
19690
|
+
if (this.options.guardStrategies === void 0) return;
|
|
19691
|
+
this.subsystemBus.registerGate("beforePut", async (e) => {
|
|
19692
|
+
const v = this.vaultCache.get(e.vault);
|
|
19693
|
+
if (!v) return;
|
|
19694
|
+
const registry = v._getGuardRegistry();
|
|
19695
|
+
if (!registry) return;
|
|
19696
|
+
const guards = registry.guardsFor(e.collection);
|
|
19697
|
+
if (guards.length === 0) return;
|
|
19698
|
+
const existing = e.existing ?? null;
|
|
19699
|
+
const incoming = e.incoming;
|
|
19700
|
+
if (registry.isAmendmentActive()) {
|
|
19701
|
+
registry.collectChange(e.collection, e.docId, existing, incoming, e.existingVersion, e.existingVersion + 1);
|
|
19702
|
+
return;
|
|
19703
|
+
}
|
|
19704
|
+
const facade = v._getReadOnlyFacade();
|
|
19705
|
+
if (!facade) return;
|
|
19706
|
+
const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
|
|
19707
|
+
await registry.runChecks(e.collection, incoming, ctx);
|
|
19708
|
+
const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
|
|
19709
|
+
for (const g of guards) {
|
|
19710
|
+
await GuardExecutor2.checkFrozenFields(g, e.docId, existing, incoming);
|
|
19711
|
+
}
|
|
19712
|
+
});
|
|
19713
|
+
this.subsystemBus.registerGate("beforeDelete", async (e) => {
|
|
19714
|
+
const v = this.vaultCache.get(e.vault);
|
|
19715
|
+
if (!v) return;
|
|
19716
|
+
const registry = v._getGuardRegistry();
|
|
19717
|
+
if (!registry) return;
|
|
19718
|
+
const guards = registry.guardsFor(e.collection);
|
|
19719
|
+
if (guards.length === 0) return;
|
|
19720
|
+
const existing = e.existing ?? null;
|
|
19721
|
+
if (registry.isAmendmentActive()) {
|
|
19722
|
+
registry.collectChange(e.collection, e.docId, existing, null, e.existingVersion, e.existingVersion);
|
|
19723
|
+
return;
|
|
19724
|
+
}
|
|
19725
|
+
if (e.internal) return;
|
|
19726
|
+
const facade = v._getReadOnlyFacade();
|
|
19727
|
+
if (!facade) return;
|
|
19728
|
+
const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
|
|
19729
|
+
await registry.runOnDelete(e.collection, existing ?? {}, ctx);
|
|
19730
|
+
});
|
|
19731
|
+
}
|
|
19732
|
+
/**
|
|
19733
|
+
* Register closed-period write guards on the subsystem bus when a
|
|
19734
|
+
* periodsStrategy is configured. Handlers resolve the live Vault from
|
|
19735
|
+
* vaultCache so they always use the up-to-date period cache.
|
|
19736
|
+
*/
|
|
19737
|
+
// Track A — periods migration. Registers the closed-period write guard as a
|
|
19738
|
+
// gate-bus handler (only when periods is opted in, so the write path is
|
|
19739
|
+
// zero-cost otherwise). Each handler resolves the LIVE vault from the cache
|
|
19740
|
+
// per dispatch and delegates to its `_assertTsWritable`, which owns all
|
|
19741
|
+
// period logic. Resolving the live vault makes eviction/re-creation
|
|
19742
|
+
// transparent. Semantics note: if a write reaches the gate through a retained
|
|
19743
|
+
// collection handle whose vault has been evicted from `vaultCache` (e.g. a
|
|
19744
|
+
// post-revocation write on a stale handle), the period check is skipped — the
|
|
19745
|
+
// guard binds to the live vault, not a captured instance. Periods is a
|
|
19746
|
+
// write-integrity guard, not a security boundary, and a re-open reloads the
|
|
19747
|
+
// period cache; the trade-off is intentional.
|
|
19748
|
+
#registerPeriodGate() {
|
|
19749
|
+
if (this.options.periodsStrategy === void 0) return;
|
|
19750
|
+
this.subsystemBus.registerGate("beforePut", async (e) => {
|
|
19751
|
+
const v = this.vaultCache.get(e.vault);
|
|
19752
|
+
if (!v) return;
|
|
19753
|
+
const existing = e.op === "create" ? null : { ts: e.existingTs ?? null, record: e.existing ?? null };
|
|
19754
|
+
await v._assertTsWritable(existing, e.incoming);
|
|
19755
|
+
});
|
|
19756
|
+
this.subsystemBus.registerGate("beforeDelete", async (e) => {
|
|
19757
|
+
if (e.internal) return;
|
|
19758
|
+
const v = this.vaultCache.get(e.vault);
|
|
19759
|
+
if (!v) return;
|
|
19760
|
+
await v._assertTsWritable(
|
|
19761
|
+
{ ts: e.existingTs ?? null, record: e.existing ?? null },
|
|
19762
|
+
null
|
|
19763
|
+
);
|
|
19764
|
+
});
|
|
19765
|
+
}
|
|
19658
19766
|
resetSessionTimer() {
|
|
19659
19767
|
if (this.sessionTimer) clearTimeout(this.sessionTimer);
|
|
19660
19768
|
const idleMs = this.options.sessionPolicy?.idleTimeoutMs ?? this.options.sessionTimeout;
|
|
@@ -19944,8 +20052,6 @@ var Noydb = class {
|
|
|
19944
20052
|
* @throws `NoAccessError` when no keyring exists for the target.
|
|
19945
20053
|
* @throws `PermissionDeniedError` when the role hierarchy rejects.
|
|
19946
20054
|
* @throws `ValidationError` when no field is provided.
|
|
19947
|
-
*
|
|
19948
|
-
* @see #54
|
|
19949
20055
|
*/
|
|
19950
20056
|
async updateUser(vault, options, factors) {
|
|
19951
20057
|
await this.checkGate(vault, "update-user", factors);
|
|
@@ -20281,7 +20387,7 @@ var Noydb = class {
|
|
|
20281
20387
|
* Phase 2. `Collection.dispatchDerivations` consults this so a
|
|
20282
20388
|
* recursive derived-output write inside `Collection.put` can register
|
|
20283
20389
|
* its envelope onto `ctx._executed` and roll back with the main
|
|
20284
|
-
* staged ops on mid-batch failure
|
|
20390
|
+
* staged ops on mid-batch failure.
|
|
20285
20391
|
*
|
|
20286
20392
|
* @internal
|
|
20287
20393
|
*/
|
|
@@ -20310,7 +20416,7 @@ var Noydb = class {
|
|
|
20310
20416
|
* `Collection.putManyAtomic` (via `derivationSource.createTxContext`)
|
|
20311
20417
|
* to publish an active context for the duration of its bulk-atomic
|
|
20312
20418
|
* Phase 2 loop, so recursive derivation-output writes register on
|
|
20313
|
-
* `ctx._executed` and roll back together with the source ops
|
|
20419
|
+
* `ctx._executed` and roll back together with the source ops.
|
|
20314
20420
|
*
|
|
20315
20421
|
* @internal
|
|
20316
20422
|
*/
|
|
@@ -20381,26 +20487,26 @@ var Noydb = class {
|
|
|
20381
20487
|
return this.writeQueueTracker;
|
|
20382
20488
|
}
|
|
20383
20489
|
/**
|
|
20384
|
-
* Register a hook that runs before each write
|
|
20490
|
+
* Register a hook that runs before each write. Awaited; a throw
|
|
20385
20491
|
* aborts the write. Returns an unsubscribe function.
|
|
20386
20492
|
*/
|
|
20387
20493
|
onBeforeWrite(handler) {
|
|
20388
20494
|
return this.writeHooks.onBeforeWrite(handler);
|
|
20389
20495
|
}
|
|
20390
20496
|
/**
|
|
20391
|
-
* Register a hook that runs after each committed write
|
|
20497
|
+
* Register a hook that runs after each committed write. Awaited;
|
|
20392
20498
|
* a handler error is warned, never rolled back. Returns an unsubscribe fn.
|
|
20393
20499
|
*/
|
|
20394
20500
|
onAfterWrite(handler) {
|
|
20395
20501
|
return this.writeHooks.onAfterWrite(handler);
|
|
20396
20502
|
}
|
|
20397
|
-
/** Subscribe to cross-tab write conflicts
|
|
20503
|
+
/** Subscribe to cross-tab write conflicts. Returns an unsubscribe. */
|
|
20398
20504
|
onWriteConflict(fn) {
|
|
20399
20505
|
this.on("write:conflict", fn);
|
|
20400
20506
|
return () => this.off("write:conflict", fn);
|
|
20401
20507
|
}
|
|
20402
20508
|
/**
|
|
20403
|
-
* Enable same-device multi-tab coordination
|
|
20509
|
+
* Enable same-device multi-tab coordination: primary/secondary
|
|
20404
20510
|
* election + presence. Browser-only — a graceful no-op (role 'unknown')
|
|
20405
20511
|
* when Web Locks / BroadcastChannel are unavailable and nothing is
|
|
20406
20512
|
* injected. Idempotent; returns a disposer.
|
|
@@ -20483,7 +20589,11 @@ var Noydb = class {
|
|
|
20483
20589
|
get _writeHooks() {
|
|
20484
20590
|
return this.writeHooks;
|
|
20485
20591
|
}
|
|
20486
|
-
/** @internal
|
|
20592
|
+
/** @internal The observe bus, threaded into every Collection. */
|
|
20593
|
+
get _subsystemBus() {
|
|
20594
|
+
return this.subsystemBus;
|
|
20595
|
+
}
|
|
20596
|
+
/** @internal Stable per-instance id for schema-cutover coordination. */
|
|
20487
20597
|
get _clientId() {
|
|
20488
20598
|
return this.clientId;
|
|
20489
20599
|
}
|
|
@@ -20503,10 +20613,6 @@ var Noydb = class {
|
|
|
20503
20613
|
* survives lock; nothing about it changes when DEKs are scrubbed).
|
|
20504
20614
|
*
|
|
20505
20615
|
* No-op when `vault` is not currently in cache (idempotent).
|
|
20506
|
-
*
|
|
20507
|
-
* Unblocks vLannaAi/niwat#33.
|
|
20508
|
-
*
|
|
20509
|
-
* @see #17
|
|
20510
20616
|
*/
|
|
20511
20617
|
lockVault(vault) {
|
|
20512
20618
|
this.syncEngines.get(vault)?.stopAutoSync();
|
|
@@ -20621,7 +20727,7 @@ var Noydb = class {
|
|
|
20621
20727
|
return merged;
|
|
20622
20728
|
}
|
|
20623
20729
|
/**
|
|
20624
|
-
* Read the current vault-level user-directory toggle
|
|
20730
|
+
* Read the current vault-level user-directory toggle. Returns
|
|
20625
20731
|
* the default-on shape (`{ enabled: true }`) when no `_meta/directory`
|
|
20626
20732
|
* document has been persisted yet.
|
|
20627
20733
|
*
|
|
@@ -20633,7 +20739,7 @@ var Noydb = class {
|
|
|
20633
20739
|
return persisted?.enabled ?? true;
|
|
20634
20740
|
}
|
|
20635
20741
|
/**
|
|
20636
|
-
* Toggle the vault's user-directory listing on or off
|
|
20742
|
+
* Toggle the vault's user-directory listing on or off.
|
|
20637
20743
|
* Owner-only. When disabled, `listUsersWithEnvelopes()` throws
|
|
20638
20744
|
* {@link import('./errors.js').DirectoryDisabledError} for callers
|
|
20639
20745
|
* whose role is neither `owner` nor `admin`.
|
|
@@ -20693,7 +20799,7 @@ var Noydb = class {
|
|
|
20693
20799
|
*
|
|
20694
20800
|
* Two enforcement modes:
|
|
20695
20801
|
*
|
|
20696
|
-
* 1. **Managed-mode mandatory strong-recovery
|
|
20802
|
+
* 1. **Managed-mode mandatory strong-recovery.** When
|
|
20697
20803
|
* `passphraseMode === 'managed'`, the vault MUST have at least
|
|
20698
20804
|
* one **strong** recovery profile (Shamir today). Paper alone is
|
|
20699
20805
|
* rejected because under managed mode the user has no memorized
|
|
@@ -20727,14 +20833,14 @@ var Noydb = class {
|
|
|
20727
20833
|
throw new RecoveryNotEnrolledError();
|
|
20728
20834
|
}
|
|
20729
20835
|
/**
|
|
20730
|
-
* Internal accessor used by tier-2/tier-3 unlock paths
|
|
20836
|
+
* Internal accessor used by tier-2/tier-3 unlock paths
|
|
20731
20837
|
* to mark the active session tier.
|
|
20732
20838
|
* @internal
|
|
20733
20839
|
*/
|
|
20734
20840
|
_setActiveTier(vault, tier) {
|
|
20735
20841
|
this.activeTier.set(vault, tier);
|
|
20736
20842
|
}
|
|
20737
|
-
// ─── Tier-2 enroll / remove
|
|
20843
|
+
// ─── Tier-2 enroll / remove ─────────────────────────────────────
|
|
20738
20844
|
/**
|
|
20739
20845
|
* Add a tier-2 authenticator slot to the calling user's keyring.
|
|
20740
20846
|
* Each slot independently wraps the SAME KEK under a method-specific
|
|
@@ -20764,7 +20870,7 @@ var Noydb = class {
|
|
|
20764
20870
|
const next = await removeAuthenticator(this.options.store, vault, keyring, slotId);
|
|
20765
20871
|
this.keyringCache.set(vault, next);
|
|
20766
20872
|
}
|
|
20767
|
-
/** Read the slot list for a vault. Internal — `describeAuthConfig`
|
|
20873
|
+
/** Read the slot list for a vault. Internal — `describeAuthConfig` consumes this. */
|
|
20768
20874
|
async listAuthenticators(vault) {
|
|
20769
20875
|
const keyring = await this.getKeyringInternal(vault);
|
|
20770
20876
|
return keyring.authenticators;
|
|
@@ -20776,7 +20882,7 @@ var Noydb = class {
|
|
|
20776
20882
|
* are immutable through this method. Anti-slot-swap is structural,
|
|
20777
20883
|
* not gate-driven.
|
|
20778
20884
|
*
|
|
20779
|
-
* `meta` patch semantics (
|
|
20885
|
+
* `meta` patch semantics (top-level merge):
|
|
20780
20886
|
* - Top-level merge — absent keys preserved
|
|
20781
20887
|
* - `null` value — delete that meta key
|
|
20782
20888
|
* - Other values — replace verbatim
|
|
@@ -20794,8 +20900,6 @@ var Noydb = class {
|
|
|
20794
20900
|
*
|
|
20795
20901
|
* @throws `NoAccessError` when no slot with the given id exists.
|
|
20796
20902
|
* @throws `ValidationError` when no patch field is provided.
|
|
20797
|
-
*
|
|
20798
|
-
* @see #55
|
|
20799
20903
|
*/
|
|
20800
20904
|
async updateAuthenticator(vault, slotId, options, factors) {
|
|
20801
20905
|
await this.checkGate(vault, "update-authenticator", factors);
|
|
@@ -20804,7 +20908,7 @@ var Noydb = class {
|
|
|
20804
20908
|
this.keyringCache.set(vault, next);
|
|
20805
20909
|
}
|
|
20806
20910
|
/**
|
|
20807
|
-
* Native WebAuthn enrollment using the **real** internal keyring
|
|
20911
|
+
* Native WebAuthn enrollment using the **real** internal keyring.
|
|
20808
20912
|
*
|
|
20809
20913
|
* Why this exists: when a consumer is using `createNoydb({ secret })`,
|
|
20810
20914
|
* they cannot reach the live `UnlockedKeyring` to feed it to
|
|
@@ -20847,8 +20951,6 @@ var Noydb = class {
|
|
|
20847
20951
|
* a server-side allowlist).
|
|
20848
20952
|
*
|
|
20849
20953
|
* Gated by `enroll-authenticator` like `enrollAuthenticator()` itself.
|
|
20850
|
-
*
|
|
20851
|
-
* @see #16
|
|
20852
20954
|
*/
|
|
20853
20955
|
async enrollWebAuthn(vault, ceremony, factors) {
|
|
20854
20956
|
await this.checkGate(vault, "enroll-authenticator", factors);
|
|
@@ -20875,8 +20977,6 @@ var Noydb = class {
|
|
|
20875
20977
|
* deciding when a new device prompt should appear. Identity is
|
|
20876
20978
|
* `id` + `enrolled_at`; the `meta.credentialId` (base64) is used by
|
|
20877
20979
|
* `allowCredentials` at unlock time.
|
|
20878
|
-
*
|
|
20879
|
-
* @see #16
|
|
20880
20980
|
*/
|
|
20881
20981
|
async listWebAuthnSlots(vault) {
|
|
20882
20982
|
const keyring = await this.getKeyringInternal(vault);
|
|
@@ -20958,7 +21058,7 @@ var Noydb = class {
|
|
|
20958
21058
|
async getPublicEnvelope(vault, opts = {}) {
|
|
20959
21059
|
return readPublicEnvelope(this.options.store, vault, opts);
|
|
20960
21060
|
}
|
|
20961
|
-
// ─── Auth introspection
|
|
21061
|
+
// ─── Auth introspection ─────────────────────────────────────────
|
|
20962
21062
|
/** English summary of the configured auth model. */
|
|
20963
21063
|
async describeAuthConfig(vault) {
|
|
20964
21064
|
return describeAuthConfig(this.options.store, vault);
|
|
@@ -20981,7 +21081,7 @@ var Noydb = class {
|
|
|
20981
21081
|
await this.checkGate(vault, "view-user-auth", factors);
|
|
20982
21082
|
return describeAllUsersAuth(this.options.store, vault);
|
|
20983
21083
|
}
|
|
20984
|
-
// ─── Tier-1 change flows
|
|
21084
|
+
// ─── Tier-1 change flows ────────────────────────────────────────
|
|
20985
21085
|
/**
|
|
20986
21086
|
* Rotate the user's passphrase (user remembers old). Validates the
|
|
20987
21087
|
* new phrase against the configured `passphrase` policy, runs the
|
|
@@ -20989,8 +21089,7 @@ var Noydb = class {
|
|
|
20989
21089
|
*
|
|
20990
21090
|
* Tier-2 authenticator slots are dropped — each slot wraps the old
|
|
20991
21091
|
* KEK and would need its derivation key to be re-presented. Re-enrol
|
|
20992
|
-
* via `db.enrollAuthenticator` after rotation.
|
|
20993
|
-
* v0.1.0-pre.5 limitation.
|
|
21092
|
+
* via `db.enrollAuthenticator` after rotation.
|
|
20994
21093
|
*
|
|
20995
21094
|
* @throws `WeakPassphraseError` on a weak new phrase.
|
|
20996
21095
|
* @throws `PolicyDeniedError` when the gate denies (missing factor, …).
|
|
@@ -21012,8 +21111,8 @@ var Noydb = class {
|
|
|
21012
21111
|
}
|
|
21013
21112
|
/**
|
|
21014
21113
|
* Reset the passphrase using a recovery proof (user forgot the old).
|
|
21015
|
-
*
|
|
21016
|
-
* other
|
|
21114
|
+
* Currently supports the `'paper'` profile end-to-end; the
|
|
21115
|
+
* other profiles throw {@link RecoveryProfileNotImplementedError}.
|
|
21017
21116
|
*
|
|
21018
21117
|
* Burns the used recovery entry on success.
|
|
21019
21118
|
*/
|
|
@@ -21042,7 +21141,7 @@ var Noydb = class {
|
|
|
21042
21141
|
return { newCodes: codes };
|
|
21043
21142
|
}
|
|
21044
21143
|
/**
|
|
21045
|
-
* Deliberate paper-recovery-code regeneration
|
|
21144
|
+
* Deliberate paper-recovery-code regeneration. User knows their
|
|
21046
21145
|
* passphrase but wants a fresh sheet — they lost the printout or
|
|
21047
21146
|
* suspect compromise of the off-site copy.
|
|
21048
21147
|
*
|
|
@@ -21052,7 +21151,7 @@ var Noydb = class {
|
|
|
21052
21151
|
*
|
|
21053
21152
|
* Gated by the `rotate-recovery` policy gate:
|
|
21054
21153
|
* - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase
|
|
21055
|
-
* suffices, matching the
|
|
21154
|
+
* suffices, matching the lower-level flow's bar.
|
|
21056
21155
|
* - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',
|
|
21057
21156
|
* 'email-otp', 'webauthn-roaming'] }] }` — rotation is an
|
|
21058
21157
|
* off-site-trust event; require an off-device factor so a
|
|
@@ -21157,7 +21256,7 @@ var Noydb = class {
|
|
|
21157
21256
|
return { newShares: shareStrings, entryId: targetEntryId };
|
|
21158
21257
|
}
|
|
21159
21258
|
/**
|
|
21160
|
-
* **Atomic create-and-enroll for managed-mode vaults
|
|
21259
|
+
* **Atomic create-and-enroll for managed-mode vaults.**
|
|
21161
21260
|
*
|
|
21162
21261
|
* Bootstraps a managed-mode vault and enrolls strong recovery in
|
|
21163
21262
|
* a single ceremony. Under `passphraseMode: 'managed'`, every
|
|
@@ -21228,7 +21327,7 @@ var Noydb = class {
|
|
|
21228
21327
|
return { vault: vaultHandle, recoveryEnrollments };
|
|
21229
21328
|
}
|
|
21230
21329
|
/**
|
|
21231
|
-
* **Recovery flow under managed-passphrase mode
|
|
21330
|
+
* **Recovery flow under managed-passphrase mode.**
|
|
21232
21331
|
*
|
|
21233
21332
|
* Replaces the sealed passphrase of a managed-mode vault with a
|
|
21234
21333
|
* fresh 256-bit random, sealed under the configured
|
|
@@ -21245,7 +21344,7 @@ var Noydb = class {
|
|
|
21245
21344
|
* 5. Drop the keyring cache so the next operation re-derives.
|
|
21246
21345
|
*
|
|
21247
21346
|
* The vault's strong-recovery enrollment is preserved across
|
|
21248
|
-
* recovery (Shamir entries are not burned on use
|
|
21347
|
+
* recovery (Shamir entries are not burned on use).
|
|
21249
21348
|
*
|
|
21250
21349
|
* @throws ValidationError if the Noydb instance is not in managed mode.
|
|
21251
21350
|
*/
|
|
@@ -21293,7 +21392,7 @@ var Noydb = class {
|
|
|
21293
21392
|
}
|
|
21294
21393
|
/**
|
|
21295
21394
|
* Atomic peer-recovery — re-wraps an EXISTING user's keyring under
|
|
21296
|
-
* a fresh temp passphrase in a single store write. Closes
|
|
21395
|
+
* a fresh temp passphrase in a single store write. Closes the
|
|
21297
21396
|
* partial-failure window (the previous compose-from-primitives
|
|
21298
21397
|
* pattern was `db.revoke + db.grant`, two writes — if the issuer
|
|
21299
21398
|
* cancelled between them the target was locked out entirely).
|
|
@@ -21303,7 +21402,7 @@ var Noydb = class {
|
|
|
21303
21402
|
* - Same `userId`, role, permissions, capabilities preserved.
|
|
21304
21403
|
* - DEKs unchanged → every other principal in the vault keeps
|
|
21305
21404
|
* access. No key rotation.
|
|
21306
|
-
* - Allows owner→owner natively
|
|
21405
|
+
* - Allows owner→owner natively. The existing
|
|
21307
21406
|
* `db.revoke` retains its block — peer-recovery is a separate,
|
|
21308
21407
|
* intentionally-named operation.
|
|
21309
21408
|
* - Tier-2 slots dropped (they wrap the old KEK).
|
|
@@ -21332,7 +21431,6 @@ var Noydb = class {
|
|
|
21332
21431
|
* @throws `PrivilegeEscalationError` when the caller lacks a DEK
|
|
21333
21432
|
* the target previously had access to.
|
|
21334
21433
|
*
|
|
21335
|
-
* @see #33 #34 — the issues this method closes.
|
|
21336
21434
|
*/
|
|
21337
21435
|
async recoverUser(vault, options, factors) {
|
|
21338
21436
|
await this.checkGate(vault, "peer-recover-user", factors);
|
|
@@ -21343,7 +21441,7 @@ var Noydb = class {
|
|
|
21343
21441
|
}
|
|
21344
21442
|
}
|
|
21345
21443
|
/**
|
|
21346
|
-
* Persist a recovery enrollment.
|
|
21444
|
+
* Persist a recovery enrollment. Accepts the `'paper'`
|
|
21347
21445
|
* profile.
|
|
21348
21446
|
*
|
|
21349
21447
|
* The hub wraps the user's DEK set (not the KEK) under a code-derived
|
|
@@ -21363,7 +21461,7 @@ var Noydb = class {
|
|
|
21363
21461
|
* showCodesToUser(codes)
|
|
21364
21462
|
* ```
|
|
21365
21463
|
*
|
|
21366
|
-
*
|
|
21464
|
+
* `@noy-db/on-recovery`'s `generateRecoveryCodeSet`
|
|
21367
21465
|
* delegates to `mintPaperRecoveryEntry` internally — its output is
|
|
21368
21466
|
* fed directly to this API. Pick whichever fits your code-gen layer:
|
|
21369
21467
|
*
|
|
@@ -21403,13 +21501,13 @@ var Noydb = class {
|
|
|
21403
21501
|
"#196"
|
|
21404
21502
|
);
|
|
21405
21503
|
}
|
|
21406
|
-
/** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig
|
|
21504
|
+
/** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig`. */
|
|
21407
21505
|
async listRecoveryEntries(vault) {
|
|
21408
21506
|
const paper = await loadPaperRecoveryEntries(this.options.store, vault);
|
|
21409
21507
|
const shamir = await loadShamirRecoveryEntries(this.options.store, vault);
|
|
21410
21508
|
return { paper, shamir };
|
|
21411
21509
|
}
|
|
21412
|
-
// ─── Tier-3 enroll / unlock
|
|
21510
|
+
// ─── Tier-3 enroll / unlock ─────────────────────────────────────
|
|
21413
21511
|
/**
|
|
21414
21512
|
* Register a tier-3 quick-unlock state for the vault. The state is
|
|
21415
21513
|
* an opaque blob produced by `@noy-db/on-pin/enrollPin` (or any
|
|
@@ -21445,11 +21543,11 @@ var Noydb = class {
|
|
|
21445
21543
|
this.quickUnlock.delete(vault);
|
|
21446
21544
|
}
|
|
21447
21545
|
/**
|
|
21448
|
-
* Public accessor for the unlocked keyring of a vault
|
|
21546
|
+
* Public accessor for the unlocked keyring of a vault.
|
|
21449
21547
|
*
|
|
21450
21548
|
* Returns a **defensive shallow copy** so consumers can read the DEK
|
|
21451
21549
|
* map and authenticator list without the risk of mutating the hub's
|
|
21452
|
-
* internal cache
|
|
21550
|
+
* internal cache. Internal hub code paths use a live reference
|
|
21453
21551
|
* via `getKeyringInternal`; ceremonies and external consumers always
|
|
21454
21552
|
* get a snapshot.
|
|
21455
21553
|
*
|
|
@@ -22388,7 +22486,7 @@ function withDerivation(spec) {
|
|
|
22388
22486
|
if (outputSpec.shape === "array") {
|
|
22389
22487
|
if (lifecycleMode !== "eager") {
|
|
22390
22488
|
throw new ValidationError(
|
|
22391
|
-
`withDerivation: shape 'array' supports lifecycle 'eager' only in this release
|
|
22489
|
+
`withDerivation: shape 'array' supports lifecycle 'eager' only in this release Output "${outputKey}" declared lifecycle '${lifecycleMode}'. Switch to \`lifecycle: "eager"\` or use shape: "record".`
|
|
22392
22490
|
);
|
|
22393
22491
|
}
|
|
22394
22492
|
if (typeof outputSpec.key !== "function") {
|