@noy-db/hub 0.6.0-pre.17 → 0.6.0-pre.18
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/{api-AEIN3KAQ.js → api-FRFEJ6WR.js} +6 -6
- package/dist/attestation/index.js +12 -12
- package/dist/{backup-7X3PFTSI.js → backup-OLBRFYY7.js} +10 -10
- package/dist/{backup-7X3PFTSI.js.map → backup-OLBRFYY7.js.map} +1 -1
- package/dist/blobs/index.js +8 -8
- package/dist/broker/index.js +6 -6
- package/dist/cargo/index.js +26 -26
- package/dist/{chunk-V3OLO4TK.js → chunk-2ION5ZRH.js} +17 -14
- package/dist/chunk-2ION5ZRH.js.map +1 -0
- package/dist/{chunk-466KGOQB.js → chunk-2NE7ILO7.js} +2 -2
- package/dist/{chunk-GWI65NDQ.js → chunk-3KXIGMZ4.js} +2 -2
- package/dist/{chunk-C3YP5KKS.js → chunk-3ODOWQMV.js} +13 -14
- package/dist/chunk-3ODOWQMV.js.map +1 -0
- package/dist/{chunk-X2FXEEQT.js → chunk-3RZ7QLJ4.js} +5 -5
- package/dist/{chunk-5UGEGEIJ.js → chunk-3Y4OFWQJ.js} +1 -1
- package/dist/chunk-3Y4OFWQJ.js.map +1 -0
- package/dist/{chunk-FZ2SSI5R.js → chunk-6AL6O4CS.js} +2 -2
- package/dist/{chunk-67XA44HH.js → chunk-6D3CN2JR.js} +2 -2
- package/dist/{chunk-5VVBZ6E4.js → chunk-6QCXMCLZ.js} +3 -3
- package/dist/{chunk-4GR422R5.js → chunk-7EH322GJ.js} +14 -7
- package/dist/chunk-7EH322GJ.js.map +1 -0
- package/dist/{chunk-AMKCY454.js → chunk-7WD2G6K4.js} +7 -2
- package/dist/{chunk-AMKCY454.js.map → chunk-7WD2G6K4.js.map} +1 -1
- package/dist/{chunk-URVL4EKU.js → chunk-A5QQKWCK.js} +14 -23
- package/dist/chunk-A5QQKWCK.js.map +1 -0
- package/dist/{chunk-2RGQNFJB.js → chunk-AA2WWTS3.js} +9 -7
- package/dist/chunk-AA2WWTS3.js.map +1 -0
- package/dist/{chunk-UKKBKEUI.js → chunk-AA7LM2A3.js} +2 -2
- package/dist/{chunk-SJOK23XT.js → chunk-AIZNLHKN.js} +4 -4
- package/dist/chunk-AIZNLHKN.js.map +1 -0
- package/dist/{chunk-UNR6G3EJ.js → chunk-AMMYIT7F.js} +35 -31
- package/dist/chunk-AMMYIT7F.js.map +1 -0
- package/dist/{chunk-NWPR2V5W.js → chunk-BBU3OD3D.js} +2 -2
- package/dist/{chunk-EEBMKMA7.js → chunk-BVIGGV6D.js} +33 -45
- package/dist/chunk-BVIGGV6D.js.map +1 -0
- package/dist/{chunk-OXRPH6AR.js → chunk-CFTPERGL.js} +14 -11
- package/dist/chunk-CFTPERGL.js.map +1 -0
- package/dist/{chunk-AKFNLRA2.js → chunk-CGQUSSW7.js} +26 -142
- package/dist/chunk-CGQUSSW7.js.map +1 -0
- package/dist/{chunk-JHYISAID.js → chunk-DFFSCYIW.js} +9 -9
- package/dist/{chunk-BMDF4TLU.js → chunk-DKYEVHZH.js} +7 -10
- package/dist/chunk-DKYEVHZH.js.map +1 -0
- package/dist/{chunk-YVOW35HH.js → chunk-DL4GDOWL.js} +4 -6
- package/dist/chunk-DL4GDOWL.js.map +1 -0
- package/dist/{chunk-Z2OFE6IX.js → chunk-DWO55ZWE.js} +13 -22
- package/dist/chunk-DWO55ZWE.js.map +1 -0
- package/dist/{chunk-XTC5EKST.js → chunk-ER3BQCCE.js} +2 -2
- package/dist/{chunk-JU3JKOY5.js → chunk-EWAGI3EM.js} +2 -2
- package/dist/{chunk-OAWVM5L6.js → chunk-EZ3UHHND.js} +13 -15
- package/dist/chunk-EZ3UHHND.js.map +1 -0
- package/dist/{chunk-IEA3TZJE.js → chunk-FDIZHZ3C.js} +29 -47
- package/dist/chunk-FDIZHZ3C.js.map +1 -0
- package/dist/{chunk-7TZTQS4H.js → chunk-FOABWV7L.js} +45 -18
- package/dist/chunk-FOABWV7L.js.map +1 -0
- package/dist/{chunk-QKRJUPAC.js → chunk-FRGF56PD.js} +11 -14
- package/dist/chunk-FRGF56PD.js.map +1 -0
- package/dist/{chunk-N4GAJSRQ.js → chunk-FY33DKPH.js} +2 -2
- package/dist/{chunk-JV2CNEX2.js → chunk-GSRW5MTU.js} +220 -169
- package/dist/chunk-GSRW5MTU.js.map +1 -0
- package/dist/{chunk-C52CJDFO.js → chunk-HJRTFXAA.js} +7 -10
- package/dist/chunk-HJRTFXAA.js.map +1 -0
- package/dist/chunk-HK67PX4J.js +27 -0
- package/dist/chunk-HK67PX4J.js.map +1 -0
- package/dist/{chunk-6JRIYG2C.js → chunk-IN5EZWYQ.js} +13 -14
- package/dist/chunk-IN5EZWYQ.js.map +1 -0
- package/dist/{chunk-QJBXB4WU.js → chunk-ITXPKEDP.js} +23 -29
- package/dist/chunk-ITXPKEDP.js.map +1 -0
- package/dist/{chunk-EMFFWSAD.js → chunk-JYFJNSTW.js} +9 -9
- package/dist/chunk-JYFJNSTW.js.map +1 -0
- package/dist/{chunk-D6E5N2O3.js → chunk-KLVGF2D4.js} +11 -9
- package/dist/{chunk-D6E5N2O3.js.map → chunk-KLVGF2D4.js.map} +1 -1
- package/dist/{chunk-7LLKPODL.js → chunk-LPAGIMXJ.js} +2 -2
- package/dist/{chunk-3JQVA527.js → chunk-LU6NPJWN.js} +3 -3
- package/dist/{chunk-VUQZYVYX.js → chunk-NAAPSNCS.js} +90 -33
- package/dist/chunk-NAAPSNCS.js.map +1 -0
- package/dist/{chunk-DEP24EE3.js → chunk-NFPSC2HL.js} +3 -3
- package/dist/{chunk-JAHIL6WO.js → chunk-NRNTXBHT.js} +7 -10
- package/dist/chunk-NRNTXBHT.js.map +1 -0
- package/dist/{chunk-N7HOHWKF.js → chunk-O4QDBAB6.js} +7 -10
- package/dist/{chunk-N7HOHWKF.js.map → chunk-O4QDBAB6.js.map} +1 -1
- package/dist/{chunk-VQQUOEON.js → chunk-OCCNYGKG.js} +7 -7
- package/dist/{chunk-VEA35MQP.js → chunk-OZD3D2DZ.js} +10 -13
- package/dist/chunk-OZD3D2DZ.js.map +1 -0
- package/dist/{chunk-IRHOQFA7.js → chunk-PBPYQZDW.js} +2 -2
- package/dist/{chunk-FHFMWYGM.js → chunk-PBRIHUQ5.js} +5 -5
- package/dist/{chunk-FHFMWYGM.js.map → chunk-PBRIHUQ5.js.map} +1 -1
- package/dist/{chunk-LUFZPROB.js → chunk-Q67P7S2I.js} +14 -15
- package/dist/chunk-Q67P7S2I.js.map +1 -0
- package/dist/{chunk-ES36R3U2.js → chunk-RAYGDC4A.js} +50 -63
- package/dist/chunk-RAYGDC4A.js.map +1 -0
- package/dist/{chunk-RVTOCAHQ.js → chunk-SQSZRCYT.js} +1 -1
- package/dist/chunk-SQSZRCYT.js.map +1 -0
- package/dist/{chunk-JN64WH3U.js → chunk-T7IR4N2S.js} +1 -1
- package/dist/{chunk-JN64WH3U.js.map → chunk-T7IR4N2S.js.map} +1 -1
- package/dist/{chunk-SDEHNGWW.js → chunk-TNOQNU3R.js} +5 -4
- package/dist/{chunk-SDEHNGWW.js.map → chunk-TNOQNU3R.js.map} +1 -1
- package/dist/{chunk-P5ARVLNW.js → chunk-TXDOMADN.js} +9 -6
- package/dist/chunk-TXDOMADN.js.map +1 -0
- package/dist/{chunk-6VAM5THN.js → chunk-U6D5XTI5.js} +13 -13
- package/dist/{chunk-6VAM5THN.js.map → chunk-U6D5XTI5.js.map} +1 -1
- package/dist/{chunk-APUP2J5M.js → chunk-UE72AQDZ.js} +6 -6
- package/dist/chunk-UIYZ5SOU.js +190 -0
- package/dist/chunk-UIYZ5SOU.js.map +1 -0
- package/dist/{chunk-QC2HGYFX.js → chunk-VXVKEWYC.js} +3 -3
- package/dist/{chunk-BKYQNNO3.js → chunk-X3AXSYBL.js} +198 -132
- package/dist/chunk-X3AXSYBL.js.map +1 -0
- package/dist/{chunk-I7JF5TNL.js → chunk-ZDATYUYG.js} +2 -2
- package/dist/{chunk-WBHQX6ME.js → chunk-ZSOTZ2RZ.js} +12 -13
- package/dist/chunk-ZSOTZ2RZ.js.map +1 -0
- package/dist/{chunk-RGPB25DB.js → chunk-ZVAFJQZB.js} +25 -19
- package/dist/chunk-ZVAFJQZB.js.map +1 -0
- package/dist/classified/index.js +4 -4
- package/dist/{classified-marker-W2PUYC3K.js → classified-marker-GWQTNDSL.js} +3 -3
- package/dist/{collection-facade-CRXN72DU.js → collection-facade-56RTPO4G.js} +12 -12
- package/dist/consent/index.js +4 -4
- package/dist/cover/index.js +9 -2
- package/dist/custody/index.js +6 -6
- package/dist/{dead-filter-MVFQCLXY.js → dead-filter-XP7MPHCJ.js} +2 -2
- package/dist/{delegation-I7XXO5LL.js → delegation-Q3HEAVLA.js} +18 -19
- package/dist/delegation-Q3HEAVLA.js.map +1 -0
- package/dist/derivations/index.js +3 -3
- package/dist/{derive-NL7X2EFT.js → derive-SVDY36ER.js} +4 -4
- package/dist/directory/index.js +5 -5
- package/dist/{dispatch-K3MDLCXG.js → dispatch-MG5KNMWG.js} +5 -5
- package/dist/{dispatch-PY5RUHJF.js → dispatch-RVJQ4BOL.js} +11 -11
- package/dist/{enclave-77K7UDLS.js → enclave-AOZTWFZK.js} +12 -4
- package/dist/{executor-FNGVGDW3.js → executor-FRUVM36Q.js} +12 -12
- package/dist/{export-accessible-QWUYY2OY.js → export-accessible-HDDHZYPM.js} +7 -7
- package/dist/{extract-partition-CGCURVS4.js → extract-partition-TRADP234.js} +12 -12
- package/dist/{fanout-sidecar-JI3MUPQV.js → fanout-sidecar-NZA6J7QB.js} +13 -22
- package/dist/fanout-sidecar-NZA6J7QB.js.map +1 -0
- package/dist/forget/index.js +4 -4
- package/dist/history/index.js +18 -15
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +9 -9
- package/dist/index.d.ts +16 -0
- package/dist/index.js +129 -127
- package/dist/index.js.map +1 -1
- package/dist/introspection/index.js +5 -5
- package/dist/{issue-7IPUNAFA.js → issue-GZQDW5TQ.js} +6 -6
- package/dist/kernel/enclave/index.d.ts +6 -5
- package/dist/kernel/enclave/record-aad.d.ts +79 -7
- package/dist/kernel/enclave/record-envelope.d.ts +54 -7
- package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
- package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
- package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
- package/dist/kernel/enclave/record-keys/rekey.d.ts +8 -2
- package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -3
- package/dist/kernel/merge-authority.d.ts +25 -0
- package/dist/kernel/tier-visibility.d.ts +15 -0
- package/dist/kernel/types.d.ts +8 -0
- package/dist/kernel/vault.d.ts +1 -1
- package/dist/kernel/via/index.d.ts +11 -2
- package/dist/{ledger-3RDSSMGD.js → ledger-KQAJPEMZ.js} +6 -6
- package/dist/liberate-Z2EXJY2S.js +24 -0
- package/dist/{link-set-VJIWWUZO.js → link-set-XKN5V7VM.js} +5 -5
- package/dist/{managed-secret-XGGJ3BC3.js → managed-secret-7WIIVMJ5.js} +11 -3
- package/dist/materialized-views/index.js +14 -14
- package/dist/money/index.js +3 -3
- package/dist/{noydb-CLQP36JR.js → noydb-NPW3C2EX.js} +39 -38
- package/dist/periods/index.js +6 -6
- package/dist/pod/index.js +42 -41
- package/dist/{pod-handle-7BVSWP5B.js → pod-handle-SKDKRPOR.js} +6 -6
- package/dist/{pod-handle-7BVSWP5B.js.map → pod-handle-SKDKRPOR.js.map} +1 -1
- package/dist/policy/index.js +10 -3
- package/dist/port/with/merge-authority.d.ts +52 -0
- package/dist/port/with/strategies.d.ts +2 -0
- package/dist/portability/index.js +6 -6
- package/dist/{post-register-W3T4U57K.js → post-register-CVS7CCJE.js} +4 -4
- package/dist/reduce/index.js +3 -3
- package/dist/{register-YUVC4G27.js → register-GJ6PW4KP.js} +10 -10
- package/dist/{registry-BPKJX562.js → registry-6LUJ5IJW.js} +4 -4
- package/dist/{registry-HRFRBFDV.js → registry-KI77RVK7.js} +4 -4
- package/dist/{request-withdrawal-CXTQLMJK.js → request-withdrawal-LICOUGXG.js} +9 -9
- package/dist/{reveal-G7W7N3AZ.js → reveal-PWM47FQN.js} +3 -3
- package/dist/{revoke-ZX3TZUI6.js → revoke-AWZKZLZB.js} +6 -6
- package/dist/sealed-record/index.js +14 -7
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +3 -3
- package/dist/{seed-2FUULTQV.js → seed-LNTLU2A3.js} +14 -17
- package/dist/seed-LNTLU2A3.js.map +1 -0
- package/dist/sequence/index.js +4 -4
- package/dist/session/index.js +3 -3
- package/dist/{signer-FPUIX63Z.js → signer-JE6NEURY.js} +5 -5
- package/dist/snapshots/index.js +5 -5
- package/dist/{stale-42FPALK2.js → stale-ZWTKUVZX.js} +5 -5
- package/dist/{storage-6QODT6K5.js → storage-WCZOCA46.js} +5 -5
- package/dist/{store-coordination-provider-Y3VXBHPL.js → store-coordination-provider-7H62VO2C.js} +18 -12
- package/dist/store-coordination-provider-7H62VO2C.js.map +1 -0
- package/dist/sync/index.js +5 -5
- package/dist/team/index.js +10 -9
- package/dist/tiers/index.js +6 -5
- package/dist/vault-head/index.js +111 -0
- package/dist/vault-head/index.js.map +1 -0
- package/dist/{walk-5PGJI3ND.js → walk-HSS3NLQ3.js} +6 -6
- package/dist/with-audit/periods/periods.d.ts +1 -1
- package/dist/with-audit/periods/strategy.d.ts +1 -1
- package/dist/with-audit/sealed-record/index.d.ts +13 -0
- package/dist/with-audit/tiers/index.d.ts +17 -2
- package/dist/with-commit/history/history.d.ts +26 -0
- package/dist/with-commit/history/index.d.ts +2 -1
- package/dist/with-commit/history/ledger/store.d.ts +7 -1
- package/dist/with-commit/history/strategy.d.ts +11 -1
- package/dist/with-commit/vault-head/active.d.ts +2 -0
- package/dist/with-commit/vault-head/head.d.ts +28 -0
- package/dist/with-commit/vault-head/index.d.ts +10 -0
- package/dist/with-commit/vault-head/strategy.d.ts +127 -0
- package/dist/with-commit/vault-head/verify.d.ts +50 -0
- package/dist/with-party/directory/cover/storage.d.ts +0 -8
- package/dist/with-party/directory/storage.d.ts +0 -11
- package/dist/with-party/directory/visibility.d.ts +0 -21
- package/dist/with-party/policy/storage.d.ts +0 -10
- package/dist/with-party/team/tiers.d.ts +2 -1
- package/dist/with-sync/engine.d.ts +33 -5
- package/dist/with-sync/strategy.d.ts +3 -0
- package/dist/{withdraw-accessible-PFCQQGNP.js → withdraw-accessible-GHBA4DAA.js} +8 -8
- package/package.json +7 -3
- package/dist/chunk-2RGQNFJB.js.map +0 -1
- package/dist/chunk-4GR422R5.js.map +0 -1
- package/dist/chunk-5UGEGEIJ.js.map +0 -1
- package/dist/chunk-6JRIYG2C.js.map +0 -1
- package/dist/chunk-7TZTQS4H.js.map +0 -1
- package/dist/chunk-AFHQATY4.js +0 -47
- package/dist/chunk-AFHQATY4.js.map +0 -1
- package/dist/chunk-AKFNLRA2.js.map +0 -1
- package/dist/chunk-BKYQNNO3.js.map +0 -1
- package/dist/chunk-BMDF4TLU.js.map +0 -1
- package/dist/chunk-C3YP5KKS.js.map +0 -1
- package/dist/chunk-C52CJDFO.js.map +0 -1
- package/dist/chunk-EEBMKMA7.js.map +0 -1
- package/dist/chunk-EMFFWSAD.js.map +0 -1
- package/dist/chunk-ES36R3U2.js.map +0 -1
- package/dist/chunk-IEA3TZJE.js.map +0 -1
- package/dist/chunk-JAHIL6WO.js.map +0 -1
- package/dist/chunk-JV2CNEX2.js.map +0 -1
- package/dist/chunk-LUFZPROB.js.map +0 -1
- package/dist/chunk-OAWVM5L6.js.map +0 -1
- package/dist/chunk-OXRPH6AR.js.map +0 -1
- package/dist/chunk-P5ARVLNW.js.map +0 -1
- package/dist/chunk-QJBXB4WU.js.map +0 -1
- package/dist/chunk-QKRJUPAC.js.map +0 -1
- package/dist/chunk-RGPB25DB.js.map +0 -1
- package/dist/chunk-RVTOCAHQ.js.map +0 -1
- package/dist/chunk-SJOK23XT.js.map +0 -1
- package/dist/chunk-UNR6G3EJ.js.map +0 -1
- package/dist/chunk-URVL4EKU.js.map +0 -1
- package/dist/chunk-V3OLO4TK.js.map +0 -1
- package/dist/chunk-VEA35MQP.js.map +0 -1
- package/dist/chunk-VUQZYVYX.js.map +0 -1
- package/dist/chunk-WBHQX6ME.js.map +0 -1
- package/dist/chunk-YVOW35HH.js.map +0 -1
- package/dist/chunk-Z2OFE6IX.js.map +0 -1
- package/dist/delegation-I7XXO5LL.js.map +0 -1
- package/dist/fanout-sidecar-JI3MUPQV.js.map +0 -1
- package/dist/liberate-2DNJ7AJ7.js +0 -24
- package/dist/seed-2FUULTQV.js.map +0 -1
- package/dist/store-coordination-provider-Y3VXBHPL.js.map +0 -1
- /package/dist/{api-AEIN3KAQ.js.map → api-FRFEJ6WR.js.map} +0 -0
- /package/dist/{chunk-466KGOQB.js.map → chunk-2NE7ILO7.js.map} +0 -0
- /package/dist/{chunk-GWI65NDQ.js.map → chunk-3KXIGMZ4.js.map} +0 -0
- /package/dist/{chunk-X2FXEEQT.js.map → chunk-3RZ7QLJ4.js.map} +0 -0
- /package/dist/{chunk-FZ2SSI5R.js.map → chunk-6AL6O4CS.js.map} +0 -0
- /package/dist/{chunk-67XA44HH.js.map → chunk-6D3CN2JR.js.map} +0 -0
- /package/dist/{chunk-5VVBZ6E4.js.map → chunk-6QCXMCLZ.js.map} +0 -0
- /package/dist/{chunk-UKKBKEUI.js.map → chunk-AA7LM2A3.js.map} +0 -0
- /package/dist/{chunk-NWPR2V5W.js.map → chunk-BBU3OD3D.js.map} +0 -0
- /package/dist/{chunk-JHYISAID.js.map → chunk-DFFSCYIW.js.map} +0 -0
- /package/dist/{chunk-XTC5EKST.js.map → chunk-ER3BQCCE.js.map} +0 -0
- /package/dist/{chunk-JU3JKOY5.js.map → chunk-EWAGI3EM.js.map} +0 -0
- /package/dist/{chunk-N4GAJSRQ.js.map → chunk-FY33DKPH.js.map} +0 -0
- /package/dist/{chunk-7LLKPODL.js.map → chunk-LPAGIMXJ.js.map} +0 -0
- /package/dist/{chunk-3JQVA527.js.map → chunk-LU6NPJWN.js.map} +0 -0
- /package/dist/{chunk-DEP24EE3.js.map → chunk-NFPSC2HL.js.map} +0 -0
- /package/dist/{chunk-VQQUOEON.js.map → chunk-OCCNYGKG.js.map} +0 -0
- /package/dist/{chunk-IRHOQFA7.js.map → chunk-PBPYQZDW.js.map} +0 -0
- /package/dist/{chunk-APUP2J5M.js.map → chunk-UE72AQDZ.js.map} +0 -0
- /package/dist/{chunk-QC2HGYFX.js.map → chunk-VXVKEWYC.js.map} +0 -0
- /package/dist/{chunk-I7JF5TNL.js.map → chunk-ZDATYUYG.js.map} +0 -0
- /package/dist/{classified-marker-W2PUYC3K.js.map → classified-marker-GWQTNDSL.js.map} +0 -0
- /package/dist/{collection-facade-CRXN72DU.js.map → collection-facade-56RTPO4G.js.map} +0 -0
- /package/dist/{dead-filter-MVFQCLXY.js.map → dead-filter-XP7MPHCJ.js.map} +0 -0
- /package/dist/{derive-NL7X2EFT.js.map → derive-SVDY36ER.js.map} +0 -0
- /package/dist/{dispatch-K3MDLCXG.js.map → dispatch-MG5KNMWG.js.map} +0 -0
- /package/dist/{dispatch-PY5RUHJF.js.map → dispatch-RVJQ4BOL.js.map} +0 -0
- /package/dist/{enclave-77K7UDLS.js.map → enclave-AOZTWFZK.js.map} +0 -0
- /package/dist/{executor-FNGVGDW3.js.map → executor-FRUVM36Q.js.map} +0 -0
- /package/dist/{export-accessible-QWUYY2OY.js.map → export-accessible-HDDHZYPM.js.map} +0 -0
- /package/dist/{extract-partition-CGCURVS4.js.map → extract-partition-TRADP234.js.map} +0 -0
- /package/dist/{issue-7IPUNAFA.js.map → issue-GZQDW5TQ.js.map} +0 -0
- /package/dist/{ledger-3RDSSMGD.js.map → ledger-KQAJPEMZ.js.map} +0 -0
- /package/dist/{liberate-2DNJ7AJ7.js.map → liberate-Z2EXJY2S.js.map} +0 -0
- /package/dist/{link-set-VJIWWUZO.js.map → link-set-XKN5V7VM.js.map} +0 -0
- /package/dist/{managed-secret-XGGJ3BC3.js.map → managed-secret-7WIIVMJ5.js.map} +0 -0
- /package/dist/{noydb-CLQP36JR.js.map → noydb-NPW3C2EX.js.map} +0 -0
- /package/dist/{post-register-W3T4U57K.js.map → post-register-CVS7CCJE.js.map} +0 -0
- /package/dist/{register-YUVC4G27.js.map → register-GJ6PW4KP.js.map} +0 -0
- /package/dist/{registry-BPKJX562.js.map → registry-6LUJ5IJW.js.map} +0 -0
- /package/dist/{registry-HRFRBFDV.js.map → registry-KI77RVK7.js.map} +0 -0
- /package/dist/{request-withdrawal-CXTQLMJK.js.map → request-withdrawal-LICOUGXG.js.map} +0 -0
- /package/dist/{reveal-G7W7N3AZ.js.map → reveal-PWM47FQN.js.map} +0 -0
- /package/dist/{revoke-ZX3TZUI6.js.map → revoke-AWZKZLZB.js.map} +0 -0
- /package/dist/{signer-FPUIX63Z.js.map → signer-JE6NEURY.js.map} +0 -0
- /package/dist/{stale-42FPALK2.js.map → stale-ZWTKUVZX.js.map} +0 -0
- /package/dist/{storage-6QODT6K5.js.map → storage-WCZOCA46.js.map} +0 -0
- /package/dist/{walk-5PGJI3ND.js.map → walk-HSS3NLQ3.js.map} +0 -0
- /package/dist/{withdraw-accessible-PFCQQGNP.js.map → withdraw-accessible-GHBA4DAA.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/via/blob/binding.ts","../src/port/with/blob-strategy.ts","../src/with-shape/blobs/export-blobs.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * The blob `ViaBinding` (#629 Task 7) — wires the `blobFields` declaration\n * into the kernel's generic Via port as a deliberately THIN binding:\n * declaration + posture + `describeFragment` + `erase` glue, and nothing\n * else. Blob content is fully out-of-band (`collection.blob(id)` →\n * `BlobSet` writing `_blob_*` side-collections; it never touches\n * `_putInternal` or the record codec), so this binding declares **no\n * write/read pipeline hooks** — in particular no `encodeAtRest`/\n * `decodeAtRest`: a blobFields collection must not flip the pipeline's\n * `hasAtRestHooks`, or the codec would abandon its inline seal path for a\n * feature that never seals record fields. The routing/compaction/TTL/\n * legal-hold/exportBlobs machinery stays service-side in\n * `with-shape/blobs/` (the i18n-dictionary precedent — that machinery\n * does real chunk AEAD + key-lifecycle work `via-enclave-isolation`\n * forbids under `via/*`).\n *\n * Like classified (see `port/with/classified-strategy.ts`), the link is\n * EAGER — `port/with/blob-strategy.ts` calls {@link linkBlobVia} at module\n * load. `blobFields` policies are plain object literals (there is no\n * `blob.*()` declaration factory to hang a lazy link on), so the binder\n * must be installed before `compileViaBindings` ever sees one.\n *\n * There is no `declare`-time validation: `blobFields` has never had a\n * construction-time refusal matrix (policies are consulted lazily by\n * `vault.compact()` and `BlobSet.put`), and inventing one here would\n * break the behavior lock.\n *\n * `covers` (#629 Task 8) is a passive coverage predicate, not a pipeline\n * hook — it lets `ViaPipeline.postureFor` recognize a `blobFields` slot so\n * `.where()`/`.orderBy()`/`.aggregate()` can refuse it (posture\n * `queryable: 'none'`); it does not participate in clause building or\n * evaluation.\n */\nimport type { ViaBinding, ViaEraseCtx, ViaEraseReport } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\n\n/**\n * Config a blob collection's `blobFields` declaration resolves to — the\n * binding's construction input. `purgeBlobsForRecord` is an OPTIONAL\n * vault/service-provided closure for the `erase` hook, mapping\n * `collection.blob(id).shredAllForRecord()`'s `{shredded, retainedShared,\n * residue}: string[]` accounting onto `ViaEraseReport` — `shredded`/\n * `retainedShared` become counts, each residue eTag becomes a tagged\n * `{kind: 'blob-residue', eTag}` entry.\n *\n * DELIBERATELY left unwired by `compileViaBindings` (#629 Task 10, resolved\n * within parity-first — see task-10-report.md \"blob purge\" section):\n * `per-blob-cek.test.ts`/`forget.test.ts` prove blob-shred-on-forget is\n * gated SOLELY on the vault's `blobsStrategy` being configured, never on a\n * given collection's OWN `blobFields` declaration (`.blob(id)` is callable,\n * and IS exercised by those tests, on collections with no `blobFields` at\n * all). Routing it exclusively through this binding would silently stop\n * blob crypto-shredding for every such collection; `vault.forget()` keeps\n * calling `collection.blob(id).shredAllForRecord()` directly for that\n * reason. The hook stays real/tested (see `blob-binding.test.ts`) for a\n * future caller that wants it.\n */\nexport interface BlobViaConfig {\n readonly fields: BlobFieldsConfig\n readonly collectionName: string\n /** Purge one record's blob slots (crypto-shred participation). Returns the erase report for `forget()`'s summary. */\n readonly purgeBlobsForRecord?: (id: string) => Promise<ViaEraseReport>\n}\n\n/**\n * `forget()`'s per-ref blob-purge participation: delegate to the wired\n * closure, or report a clean zero-shredded, zero-residue no-op — the\n * default in production too (#629 Task 10 deliberately leaves this unwired;\n * see `BlobViaConfig`'s doc comment).\n */\nasync function eraseBlobs(ctx: ViaEraseCtx, cfg: BlobViaConfig): Promise<ViaEraseReport> {\n if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id)\n return { shredded: 0, residue: [] }\n}\n\n/**\n * One field's blob knobs as they appear on `describeFragment()`'s payload\n * (#657 — the second real `ViaBinding.describeFragment` consumer after\n * `lookup`'s; see `with-shape/introspection/describe.ts`'s `buildDescription`).\n * Declarative scalars (`retainDays`/`external`/`public`/`backlink`) verbatim;\n * predicate knobs (`evictWhen`/`legalHold`/`retainUntil` — functions over the\n * decrypted record) as presence flags, since a predicate has no serializable\n * form — mirrors `buildBlobDescribeFragment`'s per-field shape below.\n */\nexport interface BlobDescribeFragmentEntry {\n readonly retainDays?: number\n readonly evictWhen?: true\n readonly legalHold?: true\n readonly retainUntil?: true\n readonly external?: true\n readonly public?: true\n readonly backlink?: string\n}\n\n/** The `'blob'` binding's `describeFragment()` payload shape. */\nexport interface BlobDescribeFragment {\n readonly blobFields: Record<string, BlobDescribeFragmentEntry>\n}\n\n/**\n * `{ blobFields: { <field>: <knobs> } }` — declarative scalars\n * (`retainDays`/`external`/`public`/`backlink`) verbatim; predicate knobs\n * (`evictWhen`/`legalHold`/`retainUntil` — functions over the decrypted\n * record) as presence flags, since a predicate has no serializable form.\n */\nfunction buildBlobDescribeFragment(fields: BlobFieldsConfig): Record<string, unknown> {\n return {\n blobFields: Object.fromEntries(\n Object.entries(fields).map(([field, p]) => [field, {\n ...(p.retainDays !== undefined ? { retainDays: p.retainDays } : {}),\n ...(p.evictWhen !== undefined ? { evictWhen: true } : {}),\n ...(p.legalHold !== undefined ? { legalHold: true } : {}),\n ...(p.retainUntil !== undefined ? { retainUntil: true } : {}),\n ...(p.external === true ? { external: true } : {}),\n ...(p.public === true ? { public: true } : {}),\n ...(p.backlink !== undefined ? { backlink: p.backlink } : {}),\n }]),\n ),\n }\n}\n\nexport function blobBinding(cfg: BlobViaConfig): ViaBinding {\n return {\n brand: 'blob',\n // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes\n // in `_blob_chunks` (service-side), not sealed record fields; queryable:\n // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()\n // is a first-class door; forgettable — shredAllForRecord participates in\n // vault.forget().\n posture: { encryptedAtRest: 'envelope', queryable: 'none', exportable: true, forgettable: true },\n covers: (field) => field in cfg.fields,\n erase: (ctx) => eraseBlobs(ctx, cfg),\n describeFragment: () => buildBlobDescribeFragment(cfg.fields),\n }\n}\n\nexport function linkBlobVia(): void {\n installViaBinder('blob', (c) => blobBinding(c as BlobViaConfig))\n}\n","/**\n * Strategy seam between core Collection and the optional blob service\n * (#629 Task 7 — moved out of `with-shape/blobs/strategy.ts`, precedent:\n * `port/with/classified-strategy.ts`/`i18n-strategy.ts`). Lives on the\n * `/with` port (the one seam the kernel spine may import statically) so\n * `Collection`/`Vault` can hold the `NO_BLOBS` default without a\n * spine→service static import.\n *\n * Core imports `BlobsStrategy` as a TYPE-ONLY symbol and `NO_BLOBS` as a\n * minimal runtime stub. Neither pulls in the heavy `BlobSet` / chunk /\n * MIME machinery — those only arrive when the consumer explicitly\n * imports `@noy-db/hub/blobs` (see `via/blob/index.ts` →\n * `withBlobs()` factory).\n *\n * This file is intentionally tiny so the bundler keeps it in the graph\n * without dragging everything else in.\n *\n * @internal\n */\n\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { ObjectProjection } from '../../with-shape/blobs/object-projection.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { linkBlobVia } from '../../via/blob/binding.js'\n\n// Install the blob Via binder EAGERLY (#629 Task 7) — `blobFields` policies\n// are plain object literals (there is no `blob.*()` declaration factory to\n// hang a lazy money/i18n-style link on), so the binder must be installed\n// before `compileViaBindings` ever compiles one. This port module is\n// already unconditionally imported by the kernel spine (for the `NO_BLOBS`\n// strategy default below), so linking here guarantees it — same rationale\n// as `port/with/classified-strategy.ts`'s eager `linkClassifiedVia()`. The\n// binding module is hook-free glue (declare/describe/erase) and imports no\n// machinery, so the eager cost is a few hundred bytes, not `BlobSet`.\nlinkBlobVia()\n\n/**\n * Args forwarded by `Collection.blob(id)` to the active strategy's\n * `openSlot`. The strategy is responsible for returning a live\n * `BlobSet` bound to the given record.\n *\n * @internal\n */\nexport interface BlobsStrategyOpenArgs {\n readonly store: NoydbStore\n readonly vault: string\n readonly collection: string\n readonly recordId: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly encrypted: boolean\n readonly userId: string\n /**\n * Collection opts into per-record keys (`perRecordKeys`), so its blobs are\n * erasable: new uploads mint a per-blob content CEK (crypto-shreddable at\n * `refCount → 0`). Off → legacy shared-`_blob`-DEK chunks. See the per-blob\n * CEK design spec.\n */\n readonly erasableBlobs?: boolean\n /**\n * Collection has `tiers` active (#724 I1 completion). Combined with\n * `erasableBlobs` false, a content write is refused —\n * `UnsupportedTierCompositionError` — since a legacy blob has no\n * per-blob `_cek` and can never be tier-isolated on elevate/demote.\n */\n readonly tiersActive?: boolean\n /**\n * Vault is in debug-plaintext mode (`encrypt: false` + `debugPlaintext: true`).\n * Blobs are then written as a single un-gzipped object (one base64 chunk) so\n * the stored object is directly recoverable with native tools (`base64 -d`),\n * matching the directly-inspectable record layout. Dev-only — see the\n * plaintext/debug-store-mode design.\n */\n readonly debugPlaintext?: boolean\n /**\n * Object projection for `external` blob fields (`createNoydb({ objectStore })`).\n * When present, fields declared `external` in `blobFields` route their raw\n * bytes here instead of the encrypted-chunk path.\n */\n readonly objectStore?: ObjectProjection\n /** Per-collection blob field policies — used to resolve `external` / `public`. */\n readonly blobFields?: BlobFieldsConfig\n /**\n * The caller's unlocked keyring (#749). Threaded through so `BlobSet.atTier()`\n * can run `assertTierAccess` — the SAME clearance gate `putAtTier`/`elevate`/\n * `demote` run before any tier `getDEK` call — rather than relying on a bare\n * `getDEK`, which auto-mints a DEK for ANY role and never throws (verified: it\n * is not itself a gate). Omitted only by a construction path that never calls\n * `atTier()`.\n */\n readonly keyring?: UnlockedKeyring\n}\n\n/**\n * The seam interface. `@internal` — do not build public APIs on this\n * shape; it can evolve freely until blobs are extracted into their\n * own package, at which point it will be promoted to public.\n *\n * @internal\n */\nexport interface BlobsStrategy {\n openSlot(args: BlobsStrategyOpenArgs): BlobSet\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store (#808).\n *\n * Lives on the seam rather than on the `withBlobs()` return type so the\n * service has exactly one name (#844b) — `NO_BLOBS` answers with zeros,\n * which is the truthful reading of \"blobs were never enabled\".\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Default strategy for collections that did not opt into blob storage.\n * Every operation surfaces an actionable error that points the caller\n * at the opt-in path.\n *\n * @internal\n */\nexport const NO_BLOBS: BlobsStrategy = {\n openSlot() {\n throw new Error(\n 'Blob storage is not enabled on this Noydb instance. ' +\n 'Import `{ withBlobs }` from \"@noy-db/hub/blobs\" and pass `withBlobs()` to `createNoydb({ blobsStrategy: withBlobs() })`.',\n )\n },\n cacheStats() {\n return { hits: 0, misses: 0, bytesDownloaded: 0 }\n },\n}\n","/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../../kernel/collection.js'\nimport type { SlotInfo } from '../../kernel/types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport { ValidationError } from '../../kernel/errors.js'\nimport { encrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Via port brand marker — lets a `BlobFieldPolicy` satisfy the kernel's\n * opaque `ViaDescriptor` (#629 Task 7). Optional (the\n * `ClassifiedFieldSpec._viaBrand` precedent): `blobFields` policies are\n * plain object literals with no declaration factory to stamp it, so a\n * mandatory field would break every existing declaration.\n */\n readonly _viaBrand?: 'blob'\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n /**\n * **Legal hold.** When this predicate returns `true`, the slot is\n * never evicted — `retainDays`/`evictWhen` are overridden. Use for a\n * litigation / audit hold on a fiscal document: the blob stays until\n * the predicate returns `false` (the hold is released). Fail-closed:\n * if the predicate throws, the slot is treated as held.\n */\n readonly legalHold?: (record: T) => boolean\n /**\n * **Period-bound retention.** Returns the date (Date / ISO string /\n * epoch ms) until which the slot must be retained — typically derived\n * from the record's fiscal period (e.g. period end + 10 years). While\n * `now < retainUntil`, the slot is never evicted, regardless of\n * `retainDays`. Return `null`/`undefined` to impose no floor.\n * Fail-closed: a throwing function holds the slot.\n */\n readonly retainUntil?: (record: T) => Date | string | number | null | undefined\n /**\n * **External projection.** When `true`, this field's bytes are stored in the\n * vault's `ObjectProjection` (`createNoydb({ objectStore })`) as a single raw,\n * **unencrypted** object — servable directly from S3/CDN and processable by\n * native tooling — instead of the encrypted-chunk path. The encrypted slot\n * record remains the catalog (anchoring invariant). Requires an `objectStore`;\n * **outside the zero-knowledge guarantee** — use only for assets meant to\n * leave the vault. See the as-aws-s3 design spec.\n */\n readonly external?: boolean\n /**\n * For an `external` field: make the object world-readable (CDN origin) rather\n * than presigned-only. Default `false` (presigned). Ignored unless `external`.\n */\n readonly public?: boolean\n /**\n * For an `external` field: how to stamp a **backlink** (this record's\n * vault/collection/id/field) onto the object's metadata — the self-describing\n * \"secondary store\" that powers reconcile / DR / import re-pairing.\n * - `'opaque-token'` (default): a random id; preserves the opaque-bucket\n * property (no names leak); the token is also recorded on the slot.\n * - `'encrypted'`: the reference encrypted under the blob DEK (ZK-preserving;\n * falls back to `'opaque-token'` on a plaintext vault).\n * - `'plain'`: the reference in cleartext metadata — **leaks structure** to\n * bucket readers; only for non-sensitive deployments.\n * - `'none'`: no backlink.\n */\n readonly backlink?: 'opaque-token' | 'encrypted' | 'plain' | 'none'\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n /** `'budget'` — evicted by the #808 cache-budget LRU pass, not a policy. */\n readonly reason: 'ttl' | 'predicate' | 'both' | 'budget'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted by the policy pass across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /**\n * Number of audit entries written — one per policy eviction plus one per\n * budget SLOT eviction (dropping a device-local cache copy writes none).\n */\n readonly auditEntries: number\n /**\n * Number of slots that would have evicted (TTL/predicate triggered)\n * but were retained by a `legalHold` or `retainUntil` floor.\n */\n readonly held: number\n /**\n * #808: number of slots whose due policy eviction was skipped because they\n * are pinned for offline on THIS device (pin state is device-local).\n */\n readonly pinned: number\n /**\n * #808: number of evictions performed by the `cacheBudget` LRU pass —\n * internal slots evicted through the standard eviction writer plus\n * device-local external cache copies dropped. 0 when no budget was given.\n */\n readonly budgetEvicted: number\n /** #808: plaintext bytes freed by the `cacheBudget` LRU pass. */\n readonly budgetBytesFreed: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n /**\n * #808 mobile cache budget: cap the locally-cached UNPINNED blob bytes.\n * After the policy pass, a dedicated LRU pass walks every collection's\n * slots (pinned slots and `legalHold`/`retainUntil`-held slots are exempt\n * and uncounted) and evicts oldest-access-first — internal slots through\n * the standard eviction writer (`deleteSlot` + a `'budget'` audit entry),\n * external slots by dropping their device-local encrypted cache copy only\n * (the object-store copy is authoritative and untouched) — until the\n * remaining unpinned bytes fit `maxBytes`. Internal slots count their\n * uncompressed `size`; external slots count `cachedBytes` (0 = nothing\n * local, nothing to evict). LRU order comes from the device-local\n * `lastAccessAt` stamp, falling back to `uploadedAt`.\n */\n readonly cacheBudget?: { readonly maxBytes: number }\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<EnclaveKey>\n /**\n * Resolve a collection's declared `blobFields` config. Returns an\n * empty map for collections without the config — the walk skips\n * those.\n */\n readonly getBlobFields: <T>(collection: string) => BlobFieldsConfig<T> | null\n /** List collection names in the vault. */\n readonly listCollections: () => Promise<string[]>\n /** List record ids in a collection. */\n readonly listRecords: (collection: string) => Promise<string[]>\n /** Decrypt and return the record. Null when absent. */\n readonly getRecord: <T>(collection: string, id: string) => Promise<T | null>\n /** Return the BlobSet-like handle for a record's slots. */\n readonly listSlots: (collection: string, id: string) => Promise<SlotInfo[]>\n /** Delete a slot and decrement its blob's refCount. */\n readonly deleteSlot: (collection: string, id: string, slotName: string) => Promise<void>\n /**\n * #808: drop a slot's DEVICE-LOCAL cached copy (the encrypted external\n * side-cache) without touching the slot or the object store. Returns the\n * plaintext bytes freed. Optional — absent (or a 0 return) means external\n * cache entries simply cannot be budget-evicted through this context.\n */\n readonly dropLocalCache?: (collection: string, id: string, slotName: string) => Promise<number>\n}\n\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n if (options.cacheBudget !== undefined\n && (!Number.isFinite(options.cacheBudget.maxBytes) || options.cacheBudget.maxBytes < 0)) {\n throw new ValidationError('compact(): cacheBudget.maxBytes must be a non-negative finite number (#808)')\n }\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let held = 0\n let pinned = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n // #808: device-local offline pin — an exemption inside this existing\n // pass, exactly like a hold: counted, never evicted. `slot.pinned` is\n // the device-local annotation `BlobSet.list()` stamps from the\n // withBlobs() pin registry (never synced state).\n if (slot.pinned === true) {\n pinned += 1\n continue\n }\n\n // Retention floor: a legal hold or period-bound retainUntil\n // blocks an otherwise-due eviction. Counted, never evicted.\n if (isHeld(policy, record, now)) {\n held += 1\n continue\n }\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n // #808: cache-budget LRU pass — runs AFTER the policy pass so a slot that\n // just evicted no longer counts toward the budget. Reuses this module's own\n // eviction writer (deleteSlot + audit), never a parallel one.\n let budgetEvicted = 0\n let budgetBytesFreed = 0\n if (options.cacheBudget !== undefined) {\n const pass = await runBudgetPass(ctx, options.cacheBudget.maxBytes, {\n now, dryRun, remainingEvictions: maxEvictions - evicted,\n })\n budgetEvicted = pass.evicted\n budgetBytesFreed = pass.bytesFreed\n auditEntries += pass.auditEntries\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n pinned,\n budgetEvicted,\n budgetBytesFreed,\n byCollection,\n }\n}\n\n// ─── #808: cache-budget LRU pass ───────────────────────────────────────\n\ninterface BudgetCandidate {\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly eTag: string\n readonly bytes: number\n /** `'slot'` — internal blob, evicted via `deleteSlot`; `'cache'` — external device-local copy, dropped via `dropLocalCache`. */\n readonly kind: 'slot' | 'cache'\n readonly lastAccessMs: number\n}\n\n/**\n * Enforce `cacheBudget.maxBytes` over the locally-cached UNPINNED blob bytes\n * (see {@link CompactRunOptions.cacheBudget} for the full contract). Walks\n * EVERY collection — unlike the policy pass, the budget is not gated on a\n * declared `blobFields` config — but still honors a declared policy's\n * `legalHold`/`retainUntil` floor where one exists.\n */\nasync function runBudgetPass(\n ctx: CompactionContext,\n maxBytes: number,\n opts: { now: Date; dryRun: boolean; remainingEvictions: number },\n): Promise<{ evicted: number; bytesFreed: number; auditEntries: number }> {\n const candidates: BudgetCandidate[] = []\n let total = 0\n\n for (const collectionName of await ctx.listCollections()) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n for (const recordId of await ctx.listRecords(collectionName)) {\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n if (slots.length === 0) continue\n // The record is only needed to evaluate a declared policy's retention\n // floor — skip the decrypt entirely for policy-less collections.\n const record = config\n ? await ctx.getRecord<unknown>(collectionName, recordId).catch(() => null)\n : null\n for (const slot of slots) {\n if (slot.pinned === true) continue // pinned: exempt AND uncounted (#808)\n const policy = config?.[slot.name]\n if (policy && record !== null && isHeld(policy, record, opts.now)) continue // floor blocks budget too\n const lastAccessMs = Date.parse(slot.lastAccessAt ?? slot.uploadedAt) || 0\n const isExternal = slot.external !== undefined\n const bytes = isExternal ? slot.cachedBytes ?? 0 : slot.size\n if (bytes <= 0) continue // external with no local copy: nothing cached to evict\n total += bytes\n candidates.push({\n collection: collectionName, recordId, slotName: slot.name, eTag: slot.eTag,\n bytes, kind: isExternal ? 'cache' : 'slot', lastAccessMs,\n })\n }\n }\n }\n\n // Oldest access first; deterministic tiebreak so equal timestamps (common\n // within one ms) never make the eviction order flap between runs.\n candidates.sort((a, b) =>\n a.lastAccessMs - b.lastAccessMs\n || a.collection.localeCompare(b.collection)\n || a.recordId.localeCompare(b.recordId)\n || a.slotName.localeCompare(b.slotName))\n\n let evicted = 0\n let bytesFreed = 0\n let auditEntries = 0\n for (const c of candidates) {\n if (total <= maxBytes || evicted >= opts.remainingEvictions) break\n if (!opts.dryRun) {\n if (c.kind === 'slot') {\n await ctx.deleteSlot(c.collection, c.recordId, c.slotName)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(c.collection, c.recordId, c.slotName),\n collection: c.collection,\n recordId: c.recordId,\n slotName: c.slotName,\n blobHash: c.eTag,\n reason: 'budget',\n evictedAt: opts.now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n } else {\n // Device-local cache drop only — the slot (catalog) and the\n // object-store copy are untouched, so no eviction audit entry.\n await ctx.dropLocalCache?.(c.collection, c.recordId, c.slotName)\n }\n }\n evicted += 1\n bytesFreed += c.bytes\n total -= c.bytes\n }\n\n return { evicted, bytesFreed, auditEntries }\n}\n\n/**\n * Whether a retention floor (legal hold or period-bound `retainUntil`)\n * currently blocks eviction of this record's slots. Fail-closed: a\n * throwing predicate holds the slot.\n */\nfunction isHeld<T>(policy: BlobFieldPolicy<T>, record: T, now: Date): boolean {\n if (policy.legalHold) {\n try {\n if (policy.legalHold(record)) return true\n } catch {\n return true\n }\n }\n if (policy.retainUntil) {\n try {\n const until = policy.retainUntil(record)\n if (until !== null && until !== undefined) {\n const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(String(until))\n if (!Number.isFinite(t)) return true // fail-closed: unparseable retainUntil holds the slot\n if (t > now.getTime()) return true\n }\n } catch {\n return true\n }\n }\n return false\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-party/team/delegation.ts"],"sourcesContent":["/**\n * Time-boxed cross-tier delegation tokens.\n *\n * A higher-tier user can issue a delegation that grants another user\n * temporary access to records at a specified tier. The delegation is\n * persisted as an encrypted envelope in the reserved `_delegations`\n * collection. The target user's runtime scans this collection on every\n * open and, while `until` is still in the future, merges the\n * unwrapped tier DEKs into their in-memory DEK map.\n *\n * ## Token shape\n *\n * ```\n * {\n * id, // ULID, also the _delegations record id\n * toUser, // grantee user id\n * fromUser, // grantor user id (owner/admin/higher-tier principal)\n * tier, // tier being delegated\n * collection, // collection name OR null for \"every collection\"\n * record, // optional specific record id\n * until, // ISO timestamp — token expires at this instant\n * wrappedDek, // base64 AES-KW-wrapped tier DEK, wrapped under target KEK\n * createdAt, // ISO timestamp\n * }\n * ```\n *\n * The ciphertext is stored as a normal noy-db envelope — the\n * `_delegations` collection has its own DEK shared across all vault\n * users, so an operator can enumerate active delegations for audit\n * without being able to *use* them (the `wrappedDek` inside is still\n * keyed to the target user's KEK).\n *\n * ## Revocation\n *\n * Delete the `_delegations/<id>` envelope. The target user's runtime\n * reloads the delegation list at each open and at periodic intervals\n * (tracked by the caller — this module is pure logic).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { encrypt, openEnvelopeJson, wrapKey, unwrapKey, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { dekKey } from './tiers.js'\nimport { DelegationTargetMissingError } from '../../kernel/errors.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\n\nexport const DELEGATIONS_COLLECTION = '_delegations'\n\n/**\n * Durable payload of a delegation token. Encrypted under the vault's\n * `_delegations` DEK; the `wrappedDek` inside is additionally wrapped\n * under the target user's KEK.\n */\nexport interface DelegationToken {\n readonly id: string\n readonly toUser: string\n readonly fromUser: string\n readonly tier: number\n /** Collection name or `null` for all collections. */\n readonly collection: string | null\n /** Optional specific record id scope. */\n readonly record?: string\n readonly until: string\n readonly wrappedDek: string\n readonly createdAt: string\n}\n\nexport interface IssueDelegationOptions {\n readonly toUser: string\n readonly tier: number\n readonly collection?: string\n readonly record?: string\n readonly until: Date | string\n}\n\n/**\n * Build and persist a delegation token. The caller must hold a tier-N\n * DEK and must have already located the target user's keyring file\n * (so the `wrappedDek` can be re-wrapped against their KEK).\n */\nexport async function issueDelegation(\n store: NoydbStore,\n vault: string,\n grantor: UnlockedKeyring,\n targetKek: EnclaveKey | null,\n delegationsDek: EnclaveKey,\n opts: IssueDelegationOptions,\n): Promise<DelegationToken> {\n if (!targetKek) {\n throw new DelegationTargetMissingError(opts.toUser)\n }\n const tier = opts.tier\n const collectionName = opts.collection ?? null\n const dekLookupCollection = collectionName ?? ''\n // Tier DEK to delegate — fetched from the grantor's own keyring.\n const sourceDek = collectionName\n ? grantor.deks.get(dekKey(collectionName, tier))\n : undefined\n if (!sourceDek) {\n throw new DelegationTargetMissingError(\n `grantor cannot find tier ${tier} DEK for ${dekLookupCollection || '(any)'}`,\n )\n }\n const wrappedDek = await wrapKey(sourceDek, targetKek)\n\n const until = typeof opts.until === 'string' ? opts.until : opts.until.toISOString()\n const token: DelegationToken = {\n id: generateULID(),\n toUser: opts.toUser,\n fromUser: grantor.userId,\n tier,\n collection: collectionName,\n ...(opts.record && { record: opts.record }),\n until,\n wrappedDek,\n createdAt: new Date().toISOString(),\n }\n\n const plaintext = JSON.stringify(token)\n const { iv, data } = await encrypt(plaintext, delegationsDek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: token.createdAt,\n _iv: iv,\n _data: data,\n _by: grantor.userId,\n }\n await store.put(vault, DELEGATIONS_COLLECTION, token.id, envelope)\n return token\n}\n\n/**\n * Enumerate every live (non-expired) delegation addressed to `toUser`\n * and merge the unwrapped tier DEKs into their keyring. Returns the\n * list of merged delegations so the caller can register per-access\n * audit context.\n */\nexport async function loadActiveDelegations(\n store: NoydbStore,\n vault: string,\n user: UnlockedKeyring,\n delegationsDek: EnclaveKey,\n now: Date = new Date(),\n): Promise<DelegationToken[]> {\n const ids = await store.list(vault, DELEGATIONS_COLLECTION)\n const merged: DelegationToken[] = []\n const nowIso = now.toISOString()\n for (const id of ids) {\n const env = await store.get(vault, DELEGATIONS_COLLECTION, id)\n if (!env) continue\n let token: DelegationToken\n try {\n const plaintext = await openEnvelopeJson(env, delegationsDek)\n token = JSON.parse(plaintext) as DelegationToken\n } catch {\n continue\n }\n if (token.toUser !== user.userId) continue\n if (token.until <= nowIso) continue\n\n // A user without a KEK in memory (tier-3 PIN resume, wrap-DEKs\n // tier-2 unlock, session restore) cannot unwrap delegation tokens\n // — those were wrapped under the user's KEK at issue time. Skip\n // this token; the consumer reaches it again at tier-1 unlock.\n if (!user.kek) continue\n let dek: EnclaveKey\n try {\n dek = await unwrapKey(token.wrappedDek, user.kek)\n } catch {\n continue\n }\n const k = token.collection\n ? dekKey(token.collection, token.tier)\n : `__any#${token.tier}`\n user.deks.set(k, dek)\n merged.push(token)\n }\n return merged\n}\n\n/**\n * Revoke a delegation by id — the caller resolves the envelope and\n * issues a `delete`. Provided as a stable helper so the naming is\n * symmetric to `issueDelegation`.\n */\nexport async function revokeDelegation(\n store: NoydbStore,\n vault: string,\n id: string,\n): Promise<void> {\n await store.delete(vault, DELEGATIONS_COLLECTION, id)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDO,IAAM,yBAAyB;AAkCtC,eAAsB,gBACpB,OACA,OACA,SACA,WACA,gBACA,MAC0B;AAC1B,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,6BAA6B,KAAK,MAAM;AAAA,EACpD;AACA,QAAM,OAAO,KAAK;AAClB,QAAM,iBAAiB,KAAK,cAAc;AAC1C,QAAM,sBAAsB,kBAAkB;AAE9C,QAAM,YAAY,iBACd,QAAQ,KAAK,IAAI,OAAO,gBAAgB,IAAI,CAAC,IAC7C;AACJ,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR,4BAA4B,IAAI,YAAY,uBAAuB,OAAO;AAAA,IAC5E;AAAA,EACF;AACA,QAAM,aAAa,MAAM,QAAQ,WAAW,SAAS;AAErD,QAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK,MAAM,YAAY;AACnF,QAAM,QAAyB;AAAA,IAC7B,IAAI,aAAa;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,YAAY;AAAA,IACZ,GAAI,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO;AAAA,IACzC;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,QAAM,YAAY,KAAK,UAAU,KAAK;AACtC,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,QAAQ,WAAW,cAAc;AAC5D,QAAM,WAA8B;AAAA,IAClC,QAAQ;AAAA,IACR,IAAI;AAAA,IACJ,KAAK,MAAM;AAAA,IACX,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK,QAAQ;AAAA,EACf;AACA,QAAM,MAAM,IAAI,OAAO,wBAAwB,MAAM,IAAI,QAAQ;AACjE,SAAO;AACT;AAQA,eAAsB,sBACpB,OACA,OACA,MACA,gBACA,MAAY,oBAAI,KAAK,GACO;AAC5B,QAAM,MAAM,MAAM,MAAM,KAAK,OAAO,sBAAsB;AAC1D,QAAM,SAA4B,CAAC;AACnC,QAAM,SAAS,IAAI,YAAY;AAC/B,aAAW,MAAM,KAAK;AACpB,UAAM,MAAM,MAAM,MAAM,IAAI,OAAO,wBAAwB,EAAE;AAC7D,QAAI,CAAC,IAAK;AACV,QAAI;AACJ,QAAI;AACF,YAAM,YAAY,MAAM,iBAAiB,KAAK,cAAc;AAC5D,cAAQ,KAAK,MAAM,SAAS;AAAA,IAC9B,QAAQ;AACN;AAAA,IACF;AACA,QAAI,MAAM,WAAW,KAAK,OAAQ;AAClC,QAAI,MAAM,SAAS,OAAQ;AAM3B,QAAI,CAAC,KAAK,IAAK;AACf,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,UAAU,MAAM,YAAY,KAAK,GAAG;AAAA,IAClD,QAAQ;AACN;AAAA,IACF;AACA,UAAM,IAAI,MAAM,aACZ,OAAO,MAAM,YAAY,MAAM,IAAI,IACnC,SAAS,MAAM,IAAI;AACvB,SAAK,KAAK,IAAI,GAAG,GAAG;AACpB,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,SAAO;AACT;AAOA,eAAsB,iBACpB,OACA,OACA,IACe;AACf,QAAM,MAAM,OAAO,OAAO,wBAAwB,EAAE;AACtD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-formula/derivations/fanout-sidecar.ts"],"sourcesContent":["/**\n * Per-source-row fanout sidecar for `shape: 'array'` derivations.\n *\n * Each `(sourceCollection, sourceId, outputKey)` triple gets its own\n * envelope at:\n *\n * _meta/derivations-fanout/<sourceCollection>/<sourceId>/<outputKey>\n *\n * The envelope records the last-emitted derived row ids so the\n * dispatcher can compute the diff on every source-row update in O(1):\n * read prior keys, compute `toDelete = prev \\ new`, write new, persist\n * back.\n *\n * The body is encrypted (AES-GCM under the `_meta` collection DEK) when\n * the vault is encrypted — the `keys[]` are derived-row ids produced by a\n * user-supplied key extractor and can be content-bearing (SKU, tag, email),\n * so a ciphertext-only store must not read them, the derivation graph, or\n * `emittedAt`. Back-compat: sidecars written before this fix are plaintext\n * (`_iv === ''`); `loadFanoutSidecar` dual-reads them.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport { encrypt, openEnvelopeJson, type EnclaveKey } from '../../kernel/enclave/index.js'\n\ntype GetDEK = (collectionName: string) => Promise<EnclaveKey>\n\n/** The `_meta` collection name whose DEK encrypts the sidecar body. */\nconst FANOUT_DEK_COLLECTION = '_meta'\n\n/** Magic-prefixed JSON payload at `_meta/<recordId>`. */\nexport interface FanoutSidecar {\n readonly _noydb_fanout: 1\n /** Source collection name. */\n readonly source: string\n /** Source record id. */\n readonly sourceId: string\n /** Strategy output key (the key in `strategy.outputs`). */\n readonly outputKey: string\n /** Output collection name (audit / forensics). */\n readonly outputCollection: string\n /** Derived-row ids last emitted for this (source, output) pair. */\n readonly keys: ReadonlyArray<string>\n /** ISO timestamp of last dispatch. */\n readonly emittedAt: string\n}\n\n/**\n * Build the canonical `_meta` record id for a fanout sidecar.\n *\n * The full path inside the store is `_meta/<this-string>`. We pack the\n * full triple into the id so a single `_meta` collection holds all\n * sidecars (collection-per-source would proliferate names; the\n * existing `_meta` flat namespace is cheaper).\n */\nfunction recordId(source: string, sourceId: string, outputKey: string): string {\n // Use `/` as a separator. None of the components is supposed to\n // contain it (collection names + output keys are bare identifiers;\n // source ids are typically ULIDs / UUIDs / app-chosen strings\n // without slashes). If a future source carries `/` in its id, we'd\n // need an escape; deferred until that's a real case.\n return `derivations-fanout/${source}/${sourceId}/${outputKey}`\n}\n\n/**\n * Read the sidecar; returns `undefined` only when it's legitimately absent\n * (no envelope at that id) — the correct signal for callers to skip\n * orphan-row reconciliation because there's nothing to reconcile against.\n *\n * A PRESENT envelope that fails to decrypt or parse is a data-integrity\n * problem, not an absence, and is deliberately NOT caught here: swallowing\n * it to `undefined` would look identical to \"no sidecar\" and make callers\n * (the array-derivation dispatch in `vault.ts`/`collection.ts`) silently\n * skip deleting stale derived rows on a shrink. Let `openEnvelopeJson`'s\n * `DecryptionError`/`TamperedError` and `JSON.parse` failures propagate,\n * matching the no-catch convention of sibling decrypt call-sites (e.g.\n * `with-audit/consent/consent.ts`'s `decryptEntry`).\n *\n * Dual-reads for back-compat: an envelope with `_iv === ''` is a legacy\n * plaintext sidecar (parse `_data` directly); otherwise the body was\n * encrypted under the `_meta` DEK and is decrypted first.\n */\nexport async function loadFanoutSidecar(\n store: NoydbStore,\n vault: string,\n source: string,\n sourceId: string,\n outputKey: string,\n getDEK: GetDEK,\n encrypted: boolean,\n): Promise<FanoutSidecar | undefined> {\n const envelope = await store.get(vault, '_meta', recordId(source, sourceId, outputKey))\n if (!envelope) return undefined\n // Legacy plaintext (`_iv === ''`) reads directly; encrypted bodies decrypt.\n const json = (!encrypted || envelope._iv === '')\n ? envelope._data\n : await openEnvelopeJson(envelope, await getDEK(FANOUT_DEK_COLLECTION))\n const parsed = JSON.parse(json) as FanoutSidecar\n if (parsed._noydb_fanout !== 1) return undefined\n if (!Array.isArray(parsed.keys)) return undefined\n return parsed\n}\n\n/**\n * Persist (insert/replace) the sidecar with a fresh key set. The body is\n * encrypted under the `_meta` DEK when the vault is encrypted (the `keys[]`\n * can be content-bearing); plaintext only in debug/unencrypted vaults.\n */\nexport async function saveFanoutSidecar(\n store: NoydbStore,\n vault: string,\n payload: {\n readonly source: string\n readonly sourceId: string\n readonly outputKey: string\n readonly outputCollection: string\n readonly keys: ReadonlyArray<string>\n },\n getDEK: GetDEK,\n encrypted: boolean,\n): Promise<void> {\n const doc: FanoutSidecar = {\n _noydb_fanout: 1,\n source: payload.source,\n sourceId: payload.sourceId,\n outputKey: payload.outputKey,\n outputCollection: payload.outputCollection,\n keys: payload.keys,\n emittedAt: new Date().toISOString(),\n }\n const id = recordId(payload.source, payload.sourceId, payload.outputKey)\n const prior = await store.get(vault, '_meta', id)\n const json = JSON.stringify(doc)\n let envelope: EncryptedEnvelope\n if (!encrypted) {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n }\n } else {\n const { iv, data } = await encrypt(json, await getDEK(FANOUT_DEK_COLLECTION))\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n }\n await store.put(vault, '_meta', id, envelope)\n}\n\n/** Delete the sidecar (used on source-row delete cascade). */\nexport async function deleteFanoutSidecar(\n store: NoydbStore,\n vault: string,\n source: string,\n sourceId: string,\n outputKey: string,\n): Promise<void> {\n await store.delete(vault, '_meta', recordId(source, sourceId, outputKey))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,IAAM,wBAAwB;AA2B9B,SAAS,SAAS,QAAgB,UAAkB,WAA2B;AAM7E,SAAO,sBAAsB,MAAM,IAAI,QAAQ,IAAI,SAAS;AAC9D;AAoBA,eAAsB,kBACpB,OACA,OACA,QACA,UACA,WACA,QACA,WACoC;AACpC,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,SAAS,SAAS,QAAQ,UAAU,SAAS,CAAC;AACtF,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,OAAQ,CAAC,aAAa,SAAS,QAAQ,KACzC,SAAS,QACT,MAAM,iBAAiB,UAAU,MAAM,OAAO,qBAAqB,CAAC;AACxE,QAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,OAAO,kBAAkB,EAAG,QAAO;AACvC,MAAI,CAAC,MAAM,QAAQ,OAAO,IAAI,EAAG,QAAO;AACxC,SAAO;AACT;AAOA,eAAsB,kBACpB,OACA,OACA,SAOA,QACA,WACe;AACf,QAAM,MAAqB;AAAA,IACzB,eAAe;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,UAAU,QAAQ;AAAA,IAClB,WAAW,QAAQ;AAAA,IACnB,kBAAkB,QAAQ;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,QAAM,KAAK,SAAS,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,SAAS;AACvE,QAAM,QAAQ,MAAM,MAAM,IAAI,OAAO,SAAS,EAAE;AAChD,QAAM,OAAO,KAAK,UAAU,GAAG;AAC/B,MAAI;AACJ,MAAI,CAAC,WAAW;AACd,eAAW;AAAA,MACT,QAAQ;AAAA,MACR,KAAK,OAAO,MAAM,KAAK;AAAA,MACvB,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC5B,KAAK;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF,OAAO;AACL,UAAM,EAAE,IAAI,KAAK,IAAI,MAAM,QAAQ,MAAM,MAAM,OAAO,qBAAqB,CAAC;AAC5E,eAAW;AAAA,MACT,QAAQ;AAAA,MACR,KAAK,OAAO,MAAM,KAAK;AAAA,MACvB,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC5B,KAAK;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,MAAM,IAAI,OAAO,SAAS,IAAI,QAAQ;AAC9C;AAGA,eAAsB,oBACpB,OACA,OACA,QACA,UACA,WACe;AACf,QAAM,MAAM,OAAO,OAAO,SAAS,SAAS,QAAQ,UAAU,SAAS,CAAC;AAC1E;","names":[]}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
liberateVault
|
|
3
|
-
} from "./chunk-QKRJUPAC.js";
|
|
4
|
-
import "./chunk-P5ARVLNW.js";
|
|
5
|
-
import "./chunk-NWPR2V5W.js";
|
|
6
|
-
import "./chunk-JAHIL6WO.js";
|
|
7
|
-
import "./chunk-SDEHNGWW.js";
|
|
8
|
-
import "./chunk-N7HOHWKF.js";
|
|
9
|
-
import "./chunk-WBHQX6ME.js";
|
|
10
|
-
import "./chunk-QJBXB4WU.js";
|
|
11
|
-
import "./chunk-BKYQNNO3.js";
|
|
12
|
-
import "./chunk-RGPB25DB.js";
|
|
13
|
-
import "./chunk-FSZPQCDR.js";
|
|
14
|
-
import "./chunk-HVR74IBG.js";
|
|
15
|
-
import "./chunk-RBTZLXZJ.js";
|
|
16
|
-
import "./chunk-LP7BEXCT.js";
|
|
17
|
-
import "./chunk-QTDAPDDV.js";
|
|
18
|
-
import "./chunk-5UGEGEIJ.js";
|
|
19
|
-
import "./chunk-I64WQSN3.js";
|
|
20
|
-
import "./chunk-PZ5AY32C.js";
|
|
21
|
-
export {
|
|
22
|
-
liberateVault
|
|
23
|
-
};
|
|
24
|
-
//# sourceMappingURL=liberate-2DNJ7AJ7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-party/broker/seed.ts"],"sourcesContent":["/**\n * `_broker` reserved-collection seed lifecycle (#479, credential-broker\n * spec §2/§3). Modeled on `with-sync/credentials.ts`: a\n * `_`-prefixed reserved collection, record id = `brokerId`, encrypted under\n * the collection's own DEK, `requireAdminAccess` role gate (owner/admin\n * only — custodian intentionally excluded, same rationale as\n * `_sync_credentials`: this is transport-auth material, not the\n * custodian's operational scope).\n *\n * Three operations, each a free function taking a {@link BrokerSeedCtx}\n * (the {@link BrokerCtx} threaded through `with-party/broker/active.ts`,\n * augmented there with the `config` its closure was built with):\n *\n * - {@link enrollSeed} — two-phase commit (I9): create-if-absent CAS the\n * seed (I4), then register its derived proof key with the broker host,\n * then (only on a 2xx) mark the record `registered: true`. A seed\n * persisted whose `/enroll` POST failed stays `registered: false` so a\n * later `credentialSource()` fails fast instead of degrading into an\n * opaque `BrokerProofError` (I9/V23).\n * - {@link rotateSeed} — quiesce-then-swap (I5): mint a fresh seed,\n * register-new-first, THEN overwrite the local record — closing the\n * torn-proof window a naive local-overwrite-then-register order would\n * open. The single-flight cache quiesce itself (await in-flight, bump\n * epoch) is the caller's job (`with-party/broker/active.ts`) since the\n * cache lives in that closure, not here.\n * - {@link mintStoreCredentials} — the challenge/response round trip\n * (`/challenge` then `/credentials`) that produces one `StoreCredentials`\n * value; the single-flight cache wrapping it lives in `active.ts`.\n *\n * First-seed creation calls `ensureCollectionDEK`, which throws\n * (`ValidationError`) when the keyring's KEK is `null` (PIN quick-resume /\n * session-restore) — this module intercepts that and re-surfaces it as\n * {@link BrokerEnrolmentError} (R-B8/I3): *enrol* needs the KEK; *use* of an\n * already-enrolled seed needs only the DEK.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope, StoreCredentials } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { ensureCollectionDEK } from '../../with-party/team/keyring.js'\nimport { BROKER_COLLECTION } from '../../with-party/team/reserved-secret-collections.js'\nimport {\n openEnvelopeJson,\n writeEnvelopeBody,\n bufferToBase64,\n base64ToBuffer,\n deriveBrokerProofBits,\n computeBrokerProof,\n} from '../../kernel/enclave/index.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport {\n PermissionDeniedError,\n ValidationError,\n isConflictError,\n NetworkError,\n BrokerEnrolmentError,\n BrokerProofError,\n} from '../../kernel/errors.js'\nimport type { BrokerSeedCtx, BrokerConfig } from '../../port/with/broker-strategy.js'\n\n/** The `_broker/<brokerId>` record payload (spec §3). */\nexport interface BrokerSeedRecord {\n readonly brokerId: string\n /** base64(32 random bytes). */\n readonly seed: string\n readonly endpoint: string\n readonly createdAt: string\n /** `true` only once `/enroll` has returned a 2xx for this seed (I9). */\n readonly registered: boolean\n}\n\nfunction requireAdminAccess(keyring: UnlockedKeyring): void {\n // Custodian is INTENTIONALLY excluded — the _broker seed is transport-auth\n // material (mints cloud store credentials), not the custodian's\n // operational data scope. Same rationale as _sync_credentials' FR-6 note.\n if (keyring.role !== 'owner' && keyring.role !== 'admin') {\n throw new PermissionDeniedError(\n `Credential-broker operations require owner or admin role. Current role: \"${keyring.role}\"`,\n )\n }\n}\n\n// `ensureCollectionDEK` only dedupes concurrent first-time DEK creates WITHIN one\n// closure instance — since every call site here asks for a fresh closure, two\n// concurrent `enrollSeed()` calls sharing the same (store, vault, keyring) would\n// otherwise each mint a DIFFERENT `_broker` DEK and race `persistKeyring`, leaving\n// the seed record encrypted under a DEK the keyring no longer holds (TamperedError\n// on the next read). Dedupe across calls here, keyed by store identity + vault\n// (V20 — the CAS enrol vector actually exercises this).\nconst inFlightDekByStore = new WeakMap<NoydbStore, Map<string, Promise<EnclaveKey>>>()\n\nasync function brokerDek(store: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<EnclaveKey> {\n const existing = keyring.deks.get(BROKER_COLLECTION)\n if (existing) return existing\n\n let perStore = inFlightDekByStore.get(store)\n if (!perStore) { perStore = new Map(); inFlightDekByStore.set(store, perStore) }\n const pending = perStore.get(vault)\n if (pending) return pending\n\n const promise = (async () => {\n try {\n const getDek = await ensureCollectionDEK(store, vault, keyring)\n return await getDek(BROKER_COLLECTION)\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new BrokerEnrolmentError(\n 'enroll()/rotate() need the KEK to provision the _broker DEK on first use — ' +\n 're-authenticate at tier 1 (secret) before enrolling. Using an already-' +\n 'enrolled seed (credentialSource()) needs only the DEK.',\n )\n }\n throw err\n }\n })()\n perStore.set(vault, promise)\n try {\n return await promise\n } finally {\n perStore.delete(vault)\n }\n}\n\nasync function readRecord(\n store: NoydbStore,\n vault: string,\n brokerId: string,\n dek: EnclaveKey,\n): Promise<{ envelope: EncryptedEnvelope; record: BrokerSeedRecord } | null> {\n const envelope = await store.get(vault, BROKER_COLLECTION, brokerId)\n if (!envelope) return null\n const record = JSON.parse(await openEnvelopeJson(envelope, dek)) as BrokerSeedRecord\n return { envelope, record }\n}\n\nasync function persistRecord(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n dek: EnclaveKey,\n record: BrokerSeedRecord,\n expectedVersion: number,\n): Promise<void> {\n const body = await writeEnvelopeBody(JSON.stringify(record), dek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: expectedVersion + 1,\n _ts: new Date().toISOString(),\n ...body,\n _by: keyring.userId,\n }\n await store.put(vault, BROKER_COLLECTION, record.brokerId, envelope, expectedVersion)\n}\n\ninterface SeedState {\n readonly seedBytes: Uint8Array\n readonly record: BrokerSeedRecord\n readonly dek: EnclaveKey\n readonly version: number\n}\n\n/** Create-if-absent CAS (I4/V20): a concurrent enrol that loses the race\n * re-reads the winner's seed instead of persisting a second one. */\nasync function ensureSeedRecord(store: NoydbStore, vault: string, keyring: UnlockedKeyring, config: BrokerConfig): Promise<SeedState> {\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (existing) {\n return { seedBytes: base64ToBuffer(existing.record.seed), record: existing.record, dek, version: existing.envelope._v }\n }\n\n const seedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(seedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: false,\n }\n try {\n await persistRecord(store, vault, keyring, dek, record, 0)\n return { seedBytes, record, dek, version: 1 }\n } catch (err) {\n if (!(isConflictError(err))) throw err\n const winner = await readRecord(store, vault, config.brokerId, dek)\n if (!winner) throw err\n return { seedBytes: base64ToBuffer(winner.record.seed), record: winner.record, dek, version: winner.envelope._v }\n }\n}\n\nasync function postEnroll(config: BrokerConfig, vault: string, proofBits: Uint8Array): Promise<void> {\n const fetchFn = config.fetch ?? globalThis.fetch\n const attestation = config.attestation ? await config.attestation() : undefined\n let res: Response\n try {\n res = await fetchFn(`${config.endpoint}/enroll`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(attestation !== undefined ? { authorization: `Bearer ${attestation}` } : {}),\n },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, proofKey: bufferToBase64(proofBits) }),\n })\n } catch (err) {\n throw new BrokerEnrolmentError(`broker /enroll request failed: ${(err as Error).message}`)\n }\n if (!res.ok) {\n throw new BrokerEnrolmentError(`broker refused enrolment (status ${res.status}) — attestation likely missing or invalid`)\n }\n}\n\n/**\n * Enrol (idempotent, two-phase commit — I9): create-if-absent the `_broker`\n * seed (I4), derive+register its proof key with the broker host, and only\n * on a 2xx mark the record `registered: true`. Requires owner/admin role\n * and (for first-seed creation only) the KEK.\n */\nexport async function enrollSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n const { seedBytes, record, dek, version } = await ensureSeedRecord(store, vault, keyring, config)\n if (record.registered) {\n seedBytes.fill(0)\n return\n }\n\n const proofBits = await deriveBrokerProofBits(seedBytes, vault, config.brokerId)\n seedBytes.fill(0)\n try {\n await postEnroll(config, vault, proofBits)\n } finally {\n proofBits.fill(0)\n }\n\n try {\n await persistRecord(store, vault, keyring, dek, { ...record, registered: true }, version)\n } catch (err) {\n if (isConflictError(err)) {\n const latest = await readRecord(store, vault, config.brokerId, dek)\n if (latest?.record.registered) return // a concurrent enrol already committed the same outcome\n }\n throw err\n }\n}\n\n/**\n * Rotate (quiesce-then-swap — I5): mint a fresh seed, register its proof\n * key with the broker host FIRST, then overwrite the local `_broker`\n * record. The broker host is expected to accept both the old and new\n * registration for a short grace window, so an in-flight proof computed\n * under the old key (from before rotation started) still verifies. Cache\n * quiescing (awaiting any in-flight round-trip, bumping the refresh-cache\n * epoch) is the caller's job — `with-party/broker/active.ts` — since the\n * cache lives in that closure, not here.\n */\nexport async function rotateSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (!existing) {\n // Nothing to rotate yet — behaves like a first enrol.\n await enrollSeed(ctx)\n return\n }\n\n const newSeedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const newProofBits = await deriveBrokerProofBits(newSeedBytes, vault, config.brokerId)\n try {\n await postEnroll(config, vault, newProofBits)\n } finally {\n newProofBits.fill(0)\n }\n\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(newSeedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: true,\n }\n newSeedBytes.fill(0)\n await persistRecord(store, vault, keyring, dek, record, existing.envelope._v)\n}\n\n/**\n * The challenge/response round trip: `POST /challenge`, derive+sign the\n * proof from the decrypted seed, `POST /credentials`. One network round\n * trip per call — the single-flight/cache wrapping lives in `active.ts`.\n *\n * Fails fast with {@link BrokerEnrolmentError} if the seed hasn't completed\n * enrolment yet (`registered !== true` — I9/V23), never degrading into an\n * opaque {@link BrokerProofError}.\n */\nexport async function mintStoreCredentials(ctx: BrokerSeedCtx, profile?: string): Promise<StoreCredentials> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const found = await readRecord(store, vault, config.brokerId, dek)\n if (!found) throw new BrokerEnrolmentError(`No _broker seed enrolled for brokerId \"${config.brokerId}\" — call enroll() first.`)\n if (!found.record.registered) {\n throw new BrokerEnrolmentError(\n `The _broker seed for \"${config.brokerId}\" has not completed enrolment with the broker host — call enroll() again.`,\n )\n }\n\n const fetchFn = config.fetch ?? globalThis.fetch\n const endpointOrigin = new URL(config.endpoint).origin\n\n let challengeRes: Response\n try {\n challengeRes = await fetchFn(`${config.endpoint}/challenge`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId }),\n })\n } catch (err) {\n throw new NetworkError(`broker /challenge request failed: ${(err as Error).message}`)\n }\n if (!challengeRes.ok) throw new NetworkError(`broker /challenge failed with status ${challengeRes.status}`)\n const { challenge, expiresAt } = (await challengeRes.json()) as { challenge: string; expiresAt: string }\n\n const seedBytes = base64ToBuffer(found.record.seed)\n const proof = await computeBrokerProof(seedBytes, vault, config.brokerId, { endpointOrigin, profile, challenge, expiresAt })\n\n let credsRes: Response\n try {\n credsRes = await fetchFn(`${config.endpoint}/credentials`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, challenge, proof, ...(profile !== undefined ? { profile } : {}) }),\n })\n } catch (err) {\n throw new NetworkError(`broker /credentials request failed: ${(err as Error).message}`)\n }\n if (credsRes.status === 401 || credsRes.status === 403) throw new BrokerProofError('broker rejected the challenge proof')\n if (!credsRes.ok) throw new NetworkError(`broker /credentials failed with status ${credsRes.status}`)\n return (await credsRes.json()) as StoreCredentials\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAS,mBAAmB,SAAgC;AAI1D,MAAI,QAAQ,SAAS,WAAW,QAAQ,SAAS,SAAS;AACxD,UAAM,IAAI;AAAA,MACR,4EAA4E,QAAQ,IAAI;AAAA,IAC1F;AAAA,EACF;AACF;AASA,IAAM,qBAAqB,oBAAI,QAAsD;AAErF,eAAe,UAAU,OAAmB,OAAe,SAA+C;AACxG,QAAM,WAAW,QAAQ,KAAK,IAAI,iBAAiB;AACnD,MAAI,SAAU,QAAO;AAErB,MAAI,WAAW,mBAAmB,IAAI,KAAK;AAC3C,MAAI,CAAC,UAAU;AAAE,eAAW,oBAAI,IAAI;AAAG,uBAAmB,IAAI,OAAO,QAAQ;AAAA,EAAE;AAC/E,QAAM,UAAU,SAAS,IAAI,KAAK;AAClC,MAAI,QAAS,QAAO;AAEpB,QAAM,WAAW,YAAY;AAC3B,QAAI;AACF,YAAM,SAAS,MAAM,oBAAoB,OAAO,OAAO,OAAO;AAC9D,aAAO,MAAM,OAAO,iBAAiB;AAAA,IACvC,SAAS,KAAK;AACZ,UAAI,eAAe,iBAAiB;AAClC,cAAM,IAAI;AAAA,UACR;AAAA,QAGF;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF,GAAG;AACH,WAAS,IAAI,OAAO,OAAO;AAC3B,MAAI;AACF,WAAO,MAAM;AAAA,EACf,UAAE;AACA,aAAS,OAAO,KAAK;AAAA,EACvB;AACF;AAEA,eAAe,WACb,OACA,OACA,UACA,KAC2E;AAC3E,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,mBAAmB,QAAQ;AACnE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAS,KAAK,MAAM,MAAM,iBAAiB,UAAU,GAAG,CAAC;AAC/D,SAAO,EAAE,UAAU,OAAO;AAC5B;AAEA,eAAe,cACb,OACA,OACA,SACA,KACA,QACA,iBACe;AACf,QAAM,OAAO,MAAM,kBAAkB,KAAK,UAAU,MAAM,GAAG,GAAG;AAChE,QAAM,WAA8B;AAAA,IAClC,QAAQ;AAAA,IACR,IAAI,kBAAkB;AAAA,IACtB,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC5B,GAAG;AAAA,IACH,KAAK,QAAQ;AAAA,EACf;AACA,QAAM,MAAM,IAAI,OAAO,mBAAmB,OAAO,UAAU,UAAU,eAAe;AACtF;AAWA,eAAe,iBAAiB,OAAmB,OAAe,SAA0B,QAA0C;AACpI,qBAAmB,OAAO;AAC1B,QAAM,MAAM,MAAM,UAAU,OAAO,OAAO,OAAO;AAEjD,QAAM,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,UAAU,GAAG;AACpE,MAAI,UAAU;AACZ,WAAO,EAAE,WAAW,eAAe,SAAS,OAAO,IAAI,GAAG,QAAQ,SAAS,QAAQ,KAAK,SAAS,SAAS,SAAS,GAAG;AAAA,EACxH;AAEA,QAAM,YAAY,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AACtE,QAAM,SAA2B;AAAA,IAC/B,UAAU,OAAO;AAAA,IACjB,MAAM,eAAe,SAAS;AAAA,IAC9B,UAAU,OAAO;AAAA,IACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,YAAY;AAAA,EACd;AACA,MAAI;AACF,UAAM,cAAc,OAAO,OAAO,SAAS,KAAK,QAAQ,CAAC;AACzD,WAAO,EAAE,WAAW,QAAQ,KAAK,SAAS,EAAE;AAAA,EAC9C,SAAS,KAAK;AACZ,QAAI,CAAE,gBAAgB,GAAG,EAAI,OAAM;AACnC,UAAM,SAAS,MAAM,WAAW,OAAO,OAAO,OAAO,UAAU,GAAG;AAClE,QAAI,CAAC,OAAQ,OAAM;AACnB,WAAO,EAAE,WAAW,eAAe,OAAO,OAAO,IAAI,GAAG,QAAQ,OAAO,QAAQ,KAAK,SAAS,OAAO,SAAS,GAAG;AAAA,EAClH;AACF;AAEA,eAAe,WAAW,QAAsB,OAAe,WAAsC;AACnG,QAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,QAAM,cAAc,OAAO,cAAc,MAAM,OAAO,YAAY,IAAI;AACtE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,WAAW;AAAA,MAC/C,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,gBAAgB,SAAY,EAAE,eAAe,UAAU,WAAW,GAAG,IAAI,CAAC;AAAA,MAChF;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,UAAU,OAAO,UAAU,UAAU,eAAe,SAAS,EAAE,CAAC;AAAA,IACzG,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI,qBAAqB,kCAAmC,IAAc,OAAO,EAAE;AAAA,EAC3F;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,qBAAqB,oCAAoC,IAAI,MAAM,gDAA2C;AAAA,EAC1H;AACF;AAQA,eAAsB,WAAW,KAAmC;AAClE,QAAM,EAAE,OAAO,OAAO,SAAS,OAAO,IAAI;AAC1C,QAAM,EAAE,WAAW,QAAQ,KAAK,QAAQ,IAAI,MAAM,iBAAiB,OAAO,OAAO,SAAS,MAAM;AAChG,MAAI,OAAO,YAAY;AACrB,cAAU,KAAK,CAAC;AAChB;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,sBAAsB,WAAW,OAAO,OAAO,QAAQ;AAC/E,YAAU,KAAK,CAAC;AAChB,MAAI;AACF,UAAM,WAAW,QAAQ,OAAO,SAAS;AAAA,EAC3C,UAAE;AACA,cAAU,KAAK,CAAC;AAAA,EAClB;AAEA,MAAI;AACF,UAAM,cAAc,OAAO,OAAO,SAAS,KAAK,EAAE,GAAG,QAAQ,YAAY,KAAK,GAAG,OAAO;AAAA,EAC1F,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,YAAM,SAAS,MAAM,WAAW,OAAO,OAAO,OAAO,UAAU,GAAG;AAClE,UAAI,QAAQ,OAAO,WAAY;AAAA,IACjC;AACA,UAAM;AAAA,EACR;AACF;AAYA,eAAsB,WAAW,KAAmC;AAClE,QAAM,EAAE,OAAO,OAAO,SAAS,OAAO,IAAI;AAC1C,qBAAmB,OAAO;AAC1B,QAAM,MAAM,MAAM,UAAU,OAAO,OAAO,OAAO;AAEjD,QAAM,WAAW,MAAM,WAAW,OAAO,OAAO,OAAO,UAAU,GAAG;AACpE,MAAI,CAAC,UAAU;AAEb,UAAM,WAAW,GAAG;AACpB;AAAA,EACF;AAEA,QAAM,eAAe,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AACzE,QAAM,eAAe,MAAM,sBAAsB,cAAc,OAAO,OAAO,QAAQ;AACrF,MAAI;AACF,UAAM,WAAW,QAAQ,OAAO,YAAY;AAAA,EAC9C,UAAE;AACA,iBAAa,KAAK,CAAC;AAAA,EACrB;AAEA,QAAM,SAA2B;AAAA,IAC/B,UAAU,OAAO;AAAA,IACjB,MAAM,eAAe,YAAY;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,YAAY;AAAA,EACd;AACA,eAAa,KAAK,CAAC;AACnB,QAAM,cAAc,OAAO,OAAO,SAAS,KAAK,QAAQ,SAAS,SAAS,EAAE;AAC9E;AAWA,eAAsB,qBAAqB,KAAoB,SAA6C;AAC1G,QAAM,EAAE,OAAO,OAAO,SAAS,OAAO,IAAI;AAC1C,qBAAmB,OAAO;AAC1B,QAAM,MAAM,MAAM,UAAU,OAAO,OAAO,OAAO;AAEjD,QAAM,QAAQ,MAAM,WAAW,OAAO,OAAO,OAAO,UAAU,GAAG;AACjE,MAAI,CAAC,MAAO,OAAM,IAAI,qBAAqB,0CAA0C,OAAO,QAAQ,+BAA0B;AAC9H,MAAI,CAAC,MAAM,OAAO,YAAY;AAC5B,UAAM,IAAI;AAAA,MACR,yBAAyB,OAAO,QAAQ;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,QAAM,iBAAiB,IAAI,IAAI,OAAO,QAAQ,EAAE;AAEhD,MAAI;AACJ,MAAI;AACF,mBAAe,MAAM,QAAQ,GAAG,OAAO,QAAQ,cAAc;AAAA,MAC3D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,UAAU,OAAO,SAAS,CAAC;AAAA,IACpE,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI,aAAa,qCAAsC,IAAc,OAAO,EAAE;AAAA,EACtF;AACA,MAAI,CAAC,aAAa,GAAI,OAAM,IAAI,aAAa,wCAAwC,aAAa,MAAM,EAAE;AAC1G,QAAM,EAAE,WAAW,UAAU,IAAK,MAAM,aAAa,KAAK;AAE1D,QAAM,YAAY,eAAe,MAAM,OAAO,IAAI;AAClD,QAAM,QAAQ,MAAM,mBAAmB,WAAW,OAAO,OAAO,UAAU,EAAE,gBAAgB,SAAS,WAAW,UAAU,CAAC;AAE3H,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,QAAQ,GAAG,OAAO,QAAQ,gBAAgB;AAAA,MACzD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,UAAU,OAAO,UAAU,WAAW,OAAO,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC,EAAG,CAAC;AAAA,IACrI,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI,aAAa,uCAAwC,IAAc,OAAO,EAAE;AAAA,EACxF;AACA,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IAAK,OAAM,IAAI,iBAAiB,qCAAqC;AACxH,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,aAAa,0CAA0C,SAAS,MAAM,EAAE;AACpG,SAAQ,MAAM,SAAS,KAAK;AAC9B;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-shape/schema-update/client-registry.ts","../src/with-shape/schema-update/store-coordination-provider.ts"],"sourcesContent":["/**\n * Schema-cutover client registry. Each client keeps a\n * heartbeat doc at `_meta/schema-fence:client:<clientId>` carrying its\n * liveness (`lastSeen`) and the fence generation it has quiesced for\n * (`quiescedAtVersion`). Plaintext envelope, like the fence doc.\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\n\nconst META_COLLECTION = '_meta'\nconst CLIENT_PREFIX = 'schema-fence:client:'\n\nexport interface ClientDoc {\n readonly clientId: string\n readonly lastSeen: number\n readonly quiescedAtVersion: number | null\n /**\n * Session that owns this writer (one user's writers across vaults). Additive\n * and optional so legacy client docs (written without it) keep parsing;\n * readers default it.\n */\n readonly sessionId?: string\n}\n\nexport async function writeClientDoc(\n store: NoydbStore,\n vault: string,\n clientId: string,\n doc: { lastSeen: number; quiescedAtVersion: number | null; sessionId?: string },\n): Promise<void> {\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify({ clientId, ...doc }),\n }\n await store.put(vault, META_COLLECTION, `${CLIENT_PREFIX}${clientId}`, envelope)\n}\n\nexport async function listClientDocs(store: NoydbStore, vault: string): Promise<ClientDoc[]> {\n const ids = await store.list(vault, META_COLLECTION)\n const out: ClientDoc[] = []\n for (const id of ids) {\n if (!id.startsWith(CLIENT_PREFIX)) continue\n const env = await store.get(vault, META_COLLECTION, id)\n if (!env) continue\n try {\n const parsed = JSON.parse(env._data) as unknown\n if (isClientDoc(parsed)) out.push(parsed)\n } catch { /* skip corrupt */ }\n }\n return out\n}\n\n/**\n * True when every *active* client (lastSeen within staleMs of now) has\n * `quiescedAtVersion === generation`. Stale clients are ignored. An empty\n * active set is vacuously quiesced.\n */\nexport async function activeQuiesced(\n store: NoydbStore,\n vault: string,\n opts: { generation: number; now: number; staleMs: number; excludeClientId?: string },\n): Promise<boolean> {\n const docs = await listClientDocs(store, vault)\n const active = docs.filter(\n d => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId,\n )\n return active.every(d => d.quiescedAtVersion === opts.generation)\n}\n\nfunction isClientDoc(x: unknown): x is ClientDoc {\n if (x === null || typeof x !== 'object') return false\n const o = x as Record<string, unknown>\n return typeof o['clientId'] === 'string'\n && typeof o['lastSeen'] === 'number'\n && (o['quiescedAtVersion'] === null || typeof o['quiescedAtVersion'] === 'number')\n && (o['sessionId'] === undefined || typeof o['sessionId'] === 'string')\n}\n","/**\n * Store-backed default {@link CoordinationProvider}.\n *\n * Maps the five coordination-port methods onto today's `_meta/schema-fence`\n * store ops so that, with no `by-*` provider injected, the fence service\n * reproduces its current store-polling behavior byte-for-byte. The `observe*`\n * methods are poll-emit (the legacy {@link FenceWatcher} model); `by-tabs` /\n * `by-peer` override them with real-time push.\n *\n * @module\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { Unsubscribe } from '../../port/with/write-hooks.js'\nimport { loadFence, saveFence, type FenceDoc } from './fence.js'\nimport { writeClientDoc, listClientDocs, type ClientDoc } from './client-registry.js'\nimport type { CoordinationProvider, FenceState, WriterPresence } from '../../port/by/types.js'\n\n/**\n * Default poll cadence for the `observe*` fallbacks (ms). Matches the\n * store-poll granularity the fence-controller uses while waiting for quorum\n * (`delay(50)`); the same value the migration cutover falls back to when no\n * real-time provider is injected.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 50\n\n/** Map a stored {@link ClientDoc} to the port's {@link WriterPresence}. */\nfunction toPresence(doc: ClientDoc): WriterPresence {\n return {\n writerId: doc.clientId,\n // Legacy docs written without a sessionId (and explicit empty) have no\n // session — fall back to the writerId so every presence is session-addressable.\n sessionId: doc.sessionId && doc.sessionId.length > 0 ? doc.sessionId : doc.clientId,\n lastSeen: doc.lastSeen,\n quiescedAtVersion: doc.quiescedAtVersion,\n }\n}\n\nfunction fenceEqual(a: FenceState, b: FenceState): boolean {\n return a.currentSchemaVersion === b.currentSchemaVersion && a.fenceState === b.fenceState\n}\n\nfunction presenceListEqual(a: readonly WriterPresence[], b: readonly WriterPresence[]): boolean {\n if (a.length !== b.length) return false\n const key = (w: WriterPresence) => `${w.writerId}\\u0000${w.sessionId}\\u0000${w.lastSeen}\\u0000${String(w.quiescedAtVersion)}`\n const bk = new Set(b.map(key))\n return a.every((w) => bk.has(key(w)))\n}\n\nexport class StoreCoordinationProvider implements CoordinationProvider {\n readonly #store: NoydbStore\n readonly #pollIntervalMs: number\n\n constructor(store: NoydbStore, opts?: { pollIntervalMs?: number }) {\n this.#store = store\n this.#pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n }\n\n async setFence(vault: string, fence: FenceState): Promise<void> {\n // FenceState is structurally FenceDoc (currentSchemaVersion + fenceState).\n await saveFence(this.#store, vault, fence as FenceDoc)\n }\n\n async readFence(vault: string): Promise<FenceState> {\n // FenceDoc is structurally FenceState (currentSchemaVersion + fenceState).\n return loadFence(this.#store, vault)\n }\n\n observeFence(vault: string, onChange: (f: FenceState) => void): Unsubscribe {\n let last: FenceState | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const fence = await loadFence(this.#store, vault)\n if (last === null || !fenceEqual(last, fence)) {\n last = fence\n onChange(fence)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current state once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reportPresence(vault: string, p: WriterPresence): Promise<void> {\n await writeClientDoc(this.#store, vault, p.writerId, {\n lastSeen: p.lastSeen,\n quiescedAtVersion: p.quiescedAtVersion,\n sessionId: p.sessionId,\n })\n }\n\n observePresence(vault: string, onChange: (writers: readonly WriterPresence[]) => void): Unsubscribe {\n let last: readonly WriterPresence[] | null = null\n let busy = false\n const poll = async () => {\n if (busy) return\n busy = true\n try {\n const docs = await listClientDocs(this.#store, vault)\n const writers = docs.map(toPresence)\n if (last === null || !presenceListEqual(last, writers)) {\n last = writers\n onChange(writers)\n }\n } catch {\n /* transient store error — retry next tick */\n } finally {\n busy = false\n }\n }\n void poll() // emit current set once immediately\n const timer = setInterval(() => void poll(), this.#pollIntervalMs)\n unref(timer)\n return () => clearInterval(timer)\n }\n\n async reachableWriters(vault: string, o: { staleMs: number; now: number }): Promise<readonly WriterPresence[]> {\n const docs = await listClientDocs(this.#store, vault)\n return docs.map(toPresence).filter((w) => o.now - w.lastSeen <= o.staleMs)\n }\n}\n\n/** Best-effort `unref` so a poll timer never holds the process open (Node only). */\nfunction unref(timer: ReturnType<typeof setInterval>): void {\n const t = timer as unknown as { unref?: () => void }\n if (typeof t.unref === 'function') t.unref()\n}\n"],"mappings":";;;;;;;;;;AASA,IAAM,kBAAkB;AACxB,IAAM,gBAAgB;AActB,eAAsB,eACpB,OACA,OACA,UACA,KACe;AACf,QAAM,WAA8B;AAAA,IAClC,QAAQ;AAAA,IACR,IAAI;AAAA,IACJ,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC5B,KAAK;AAAA,IACL,OAAO,KAAK,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;AAAA,EAC5C;AACA,QAAM,MAAM,IAAI,OAAO,iBAAiB,GAAG,aAAa,GAAG,QAAQ,IAAI,QAAQ;AACjF;AAEA,eAAsB,eAAe,OAAmB,OAAqC;AAC3F,QAAM,MAAM,MAAM,MAAM,KAAK,OAAO,eAAe;AACnD,QAAM,MAAmB,CAAC;AAC1B,aAAW,MAAM,KAAK;AACpB,QAAI,CAAC,GAAG,WAAW,aAAa,EAAG;AACnC,UAAM,MAAM,MAAM,MAAM,IAAI,OAAO,iBAAiB,EAAE;AACtD,QAAI,CAAC,IAAK;AACV,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI,KAAK;AACnC,UAAI,YAAY,MAAM,EAAG,KAAI,KAAK,MAAM;AAAA,IAC1C,QAAQ;AAAA,IAAqB;AAAA,EAC/B;AACA,SAAO;AACT;AAmBA,SAAS,YAAY,GAA4B;AAC/C,MAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,UAAU,MAAM,YAC3B,OAAO,EAAE,UAAU,MAAM,aACxB,EAAE,mBAAmB,MAAM,QAAQ,OAAO,EAAE,mBAAmB,MAAM,cACrE,EAAE,WAAW,MAAM,UAAa,OAAO,EAAE,WAAW,MAAM;AAClE;;;ACvDA,IAAM,2BAA2B;AAGjC,SAAS,WAAW,KAAgC;AAClD,SAAO;AAAA,IACL,UAAU,IAAI;AAAA;AAAA;AAAA,IAGd,WAAW,IAAI,aAAa,IAAI,UAAU,SAAS,IAAI,IAAI,YAAY,IAAI;AAAA,IAC3E,UAAU,IAAI;AAAA,IACd,mBAAmB,IAAI;AAAA,EACzB;AACF;AAEA,SAAS,WAAW,GAAe,GAAwB;AACzD,SAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,eAAe,EAAE;AACjF;AAEA,SAAS,kBAAkB,GAA8B,GAAuC;AAC9F,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,QAAM,MAAM,CAAC,MAAsB,GAAG,EAAE,QAAQ,KAAS,EAAE,SAAS,KAAS,EAAE,QAAQ,KAAS,OAAO,EAAE,iBAAiB,CAAC;AAC3H,QAAM,KAAK,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC;AAC7B,SAAO,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC;AACtC;AAEO,IAAM,4BAAN,MAAgE;AAAA,EAC5D;AAAA,EACA;AAAA,EAET,YAAY,OAAmB,MAAoC;AACjE,SAAK,SAAS;AACd,SAAK,kBAAkB,MAAM,kBAAkB;AAAA,EACjD;AAAA,EAEA,MAAM,SAAS,OAAe,OAAkC;AAE9D,UAAM,UAAU,KAAK,QAAQ,OAAO,KAAiB;AAAA,EACvD;AAAA,EAEA,MAAM,UAAU,OAAoC;AAElD,WAAO,UAAU,KAAK,QAAQ,KAAK;AAAA,EACrC;AAAA,EAEA,aAAa,OAAe,UAAgD;AAC1E,QAAI,OAA0B;AAC9B,QAAI,OAAO;AACX,UAAM,OAAO,YAAY;AACvB,UAAI,KAAM;AACV,aAAO;AACP,UAAI;AACF,cAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,KAAK;AAChD,YAAI,SAAS,QAAQ,CAAC,WAAW,MAAM,KAAK,GAAG;AAC7C,iBAAO;AACP,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF,QAAQ;AAAA,MAER,UAAE;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,SAAK,KAAK;AACV,UAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,GAAG,KAAK,eAAe;AACjE,UAAM,KAAK;AACX,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,eAAe,OAAe,GAAkC;AACpE,UAAM,eAAe,KAAK,QAAQ,OAAO,EAAE,UAAU;AAAA,MACnD,UAAU,EAAE;AAAA,MACZ,mBAAmB,EAAE;AAAA,MACrB,WAAW,EAAE;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,gBAAgB,OAAe,UAAqE;AAClG,QAAI,OAAyC;AAC7C,QAAI,OAAO;AACX,UAAM,OAAO,YAAY;AACvB,UAAI,KAAM;AACV,aAAO;AACP,UAAI;AACF,cAAM,OAAO,MAAM,eAAe,KAAK,QAAQ,KAAK;AACpD,cAAM,UAAU,KAAK,IAAI,UAAU;AACnC,YAAI,SAAS,QAAQ,CAAC,kBAAkB,MAAM,OAAO,GAAG;AACtD,iBAAO;AACP,mBAAS,OAAO;AAAA,QAClB;AAAA,MACF,QAAQ;AAAA,MAER,UAAE;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,SAAK,KAAK;AACV,UAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,GAAG,KAAK,eAAe;AACjE,UAAM,KAAK;AACX,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,iBAAiB,OAAe,GAAyE;AAC7G,UAAM,OAAO,MAAM,eAAe,KAAK,QAAQ,KAAK;AACpD,WAAO,KAAK,IAAI,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO;AAAA,EAC3E;AACF;AAGA,SAAS,MAAM,OAA6C;AAC1D,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,UAAU,WAAY,GAAE,MAAM;AAC7C;","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|