@noy-db/hub 0.2.0-pre.9 → 0.3.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -3
- package/dist/adapter/index.d.ts +5 -0
- package/dist/adapter/index.js +15 -0
- package/dist/aggregate/index.d.ts +6 -2
- package/dist/aggregate/index.js +24 -16
- package/dist/aggregate/index.js.map +1 -1
- package/dist/api-RW3KP7WU.js +14 -0
- package/dist/as/index.d.ts +7 -0
- package/dist/as/index.js +24 -0
- package/dist/at/index.d.ts +5 -0
- package/dist/at/index.js +1 -0
- package/dist/attestation/index.d.ts +15 -47
- package/dist/attestation/index.js +54 -10
- package/dist/attestation/index.js.map +1 -1
- package/dist/backup-XV3IOEGB.js +217 -0
- package/dist/backup-XV3IOEGB.js.map +1 -0
- package/dist/blobs/index.d.ts +6 -8
- package/dist/blobs/index.js +19 -12
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +16 -70
- package/dist/bundle/index.js +39 -476
- package/dist/bundle/index.js.map +1 -1
- package/dist/{ulid-CJ8RzRrm.d.ts → bundle-CH-H06dp.d.ts} +31 -86
- package/dist/by/index.d.ts +1 -0
- package/dist/by/index.js +11 -0
- package/dist/cargo/index.d.ts +9 -0
- package/dist/cargo/index.js +89 -0
- package/dist/chunk-26LGBE4T.js +42 -0
- package/dist/chunk-26LGBE4T.js.map +1 -0
- package/dist/chunk-2P2HZEXU.js +233 -0
- package/dist/chunk-2P2HZEXU.js.map +1 -0
- package/dist/{chunk-K3DK3NU5.js → chunk-3YFXJ3ZP.js} +20 -5
- package/dist/chunk-3YFXJ3ZP.js.map +1 -0
- package/dist/chunk-4Q6HSHHL.js +90 -0
- package/dist/chunk-4Q6HSHHL.js.map +1 -0
- package/dist/chunk-5LUY7UTV.js +380 -0
- package/dist/chunk-5LUY7UTV.js.map +1 -0
- package/dist/chunk-5N6CZTCY.js +367 -0
- package/dist/chunk-5N6CZTCY.js.map +1 -0
- package/dist/chunk-5O3AOPDT.js +992 -0
- package/dist/chunk-5O3AOPDT.js.map +1 -0
- package/dist/chunk-5R3ERCDH.js +239 -0
- package/dist/chunk-5R3ERCDH.js.map +1 -0
- package/dist/{chunk-6CME4UEK.js → chunk-5R5JW2JT.js} +7000 -4827
- package/dist/chunk-5R5JW2JT.js.map +1 -0
- package/dist/chunk-5TDJ56JE.js +32 -0
- package/dist/chunk-5TDJ56JE.js.map +1 -0
- package/dist/{chunk-T7JEBOGN.js → chunk-62QYRORB.js} +18 -11
- package/dist/chunk-62QYRORB.js.map +1 -0
- package/dist/{chunk-J66GRPNH.js → chunk-6S7T6WC4.js} +2 -2
- package/dist/chunk-6S7T6WC4.js.map +1 -0
- package/dist/chunk-76722EBH.js +451 -0
- package/dist/chunk-76722EBH.js.map +1 -0
- package/dist/{chunk-Z6FNBOTC.js → chunk-AIWULCUO.js} +13 -17
- package/dist/chunk-AIWULCUO.js.map +1 -0
- package/dist/{chunk-O3VZPBZG.js → chunk-AQTBSL7R.js} +47 -11
- package/dist/chunk-AQTBSL7R.js.map +1 -0
- package/dist/chunk-AURFOK3D.js +35 -0
- package/dist/chunk-AURFOK3D.js.map +1 -0
- package/dist/{chunk-53XBRIRT.js → chunk-AXRXJTE2.js} +9 -9
- package/dist/chunk-AXRXJTE2.js.map +1 -0
- package/dist/chunk-AZAOEIKW.js +155 -0
- package/dist/chunk-AZAOEIKW.js.map +1 -0
- package/dist/{chunk-HNRHLRDC.js → chunk-BG3SPSR4.js} +3 -3
- package/dist/chunk-BG3SPSR4.js.map +1 -0
- package/dist/chunk-BGIZAFY7.js +1 -0
- package/dist/chunk-CHFZDSE4.js +1 -0
- package/dist/{chunk-DJHHA6XH.js → chunk-CMGR4ZEW.js} +50 -20
- package/dist/chunk-CMGR4ZEW.js.map +1 -0
- package/dist/chunk-CWPPNPOH.js +23 -0
- package/dist/chunk-CWPPNPOH.js.map +1 -0
- package/dist/{chunk-QODLLGQ7.js → chunk-DFEMTNPJ.js} +371 -38
- package/dist/chunk-DFEMTNPJ.js.map +1 -0
- package/dist/{chunk-ZYWZWG6G.js → chunk-DGDI2D4M.js} +10 -10
- package/dist/chunk-DGDI2D4M.js.map +1 -0
- package/dist/{chunk-SYMWGEET.js → chunk-EIZUR2XW.js} +3 -3
- package/dist/chunk-EIZUR2XW.js.map +1 -0
- package/dist/{chunk-BVRYKATC.js → chunk-FISN7WE4.js} +36 -33
- package/dist/chunk-FISN7WE4.js.map +1 -0
- package/dist/chunk-GHVIKOHT.js +1 -0
- package/dist/chunk-GYGWYIOZ.js +200 -0
- package/dist/chunk-GYGWYIOZ.js.map +1 -0
- package/dist/chunk-HCIPRAGS.js +45 -0
- package/dist/chunk-HCIPRAGS.js.map +1 -0
- package/dist/{chunk-PX35GA7L.js → chunk-I2NOIW44.js} +10 -10
- package/dist/chunk-I2NOIW44.js.map +1 -0
- package/dist/{chunk-OIF6LZUR.js → chunk-I3TIKTJO.js} +3 -3
- package/dist/chunk-I3TIKTJO.js.map +1 -0
- package/dist/chunk-I7FD46FF.js +9 -0
- package/dist/chunk-I7FD46FF.js.map +1 -0
- package/dist/chunk-IAGBDSCS.js +40 -0
- package/dist/chunk-IAGBDSCS.js.map +1 -0
- package/dist/{chunk-JWRVQKRZ.js → chunk-IELYAOGG.js} +6 -18
- package/dist/chunk-IELYAOGG.js.map +1 -0
- package/dist/chunk-IGUYVGGI.js +205 -0
- package/dist/chunk-IGUYVGGI.js.map +1 -0
- package/dist/{chunk-U26HQ6KJ.js → chunk-IO6GRPT6.js} +4 -4
- package/dist/chunk-IO6GRPT6.js.map +1 -0
- package/dist/chunk-IPB7FTQX.js +273 -0
- package/dist/chunk-IPB7FTQX.js.map +1 -0
- package/dist/chunk-ITNUCOXM.js +148 -0
- package/dist/chunk-ITNUCOXM.js.map +1 -0
- package/dist/{chunk-WPLVTJ5D.js → chunk-IUEN57TV.js} +10 -10
- package/dist/chunk-IUEN57TV.js.map +1 -0
- package/dist/{chunk-DL3HWOQ5.js → chunk-JNUWZ6D3.js} +9 -9
- package/dist/chunk-JNUWZ6D3.js.map +1 -0
- package/dist/chunk-K2WCO3NX.js +1 -0
- package/dist/chunk-KVOXU4T5.js +30 -0
- package/dist/chunk-KVOXU4T5.js.map +1 -0
- package/dist/chunk-KXGNJJXB.js +135 -0
- package/dist/chunk-KXGNJJXB.js.map +1 -0
- package/dist/chunk-LB7FD65R.js +163 -0
- package/dist/chunk-LB7FD65R.js.map +1 -0
- package/dist/{chunk-22DWZL57.js → chunk-LFLXAY2W.js} +43 -218
- package/dist/chunk-LFLXAY2W.js.map +1 -0
- package/dist/chunk-LMTH2RM6.js +129 -0
- package/dist/chunk-LMTH2RM6.js.map +1 -0
- package/dist/{aggregate/index.cjs → chunk-LV7M27WM.js} +390 -219
- package/dist/chunk-LV7M27WM.js.map +1 -0
- package/dist/{chunk-PE2FR4J3.js → chunk-LXQT4WMP.js} +16 -18
- package/dist/chunk-LXQT4WMP.js.map +1 -0
- package/dist/chunk-M2YKN37S.js +524 -0
- package/dist/chunk-M2YKN37S.js.map +1 -0
- package/dist/chunk-M6OST55S.js +14 -0
- package/dist/chunk-M6OST55S.js.map +1 -0
- package/dist/{chunk-F3GDRNUT.js → chunk-MD3Y6J3L.js} +35 -69
- package/dist/chunk-MD3Y6J3L.js.map +1 -0
- package/dist/{chunk-XJFLDA7A.js → chunk-MTMJVJ5W.js} +8 -7
- package/dist/chunk-MTMJVJ5W.js.map +1 -0
- package/dist/{chunk-DFMLEQZB.js → chunk-NF5JWERG.js} +5 -10
- package/dist/chunk-NF5JWERG.js.map +1 -0
- package/dist/{chunk-DO7C2TOG.js → chunk-PKHL44ER.js} +3 -3
- package/dist/{chunk-DO7C2TOG.js.map → chunk-PKHL44ER.js.map} +1 -1
- package/dist/chunk-PSMV73A5.js +117 -0
- package/dist/chunk-PSMV73A5.js.map +1 -0
- package/dist/chunk-PY4KJPYU.js +9 -0
- package/dist/chunk-PY4KJPYU.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/{chunk-DE6GKS6G.js → chunk-Q7SS3IME.js} +50 -9
- package/dist/chunk-Q7SS3IME.js.map +1 -0
- package/dist/chunk-QHJW5EXU.js +54 -0
- package/dist/chunk-QHJW5EXU.js.map +1 -0
- package/dist/{chunk-NONAAENK.js → chunk-RUEKROOS.js} +11 -4
- package/dist/chunk-RUEKROOS.js.map +1 -0
- package/dist/chunk-RUIMKQTA.js +23 -0
- package/dist/chunk-RUIMKQTA.js.map +1 -0
- package/dist/{chunk-TFBP23BX.js → chunk-SKF73JOP.js} +66 -7
- package/dist/chunk-SKF73JOP.js.map +1 -0
- package/dist/chunk-SM3AVUHC.js +42 -0
- package/dist/chunk-SM3AVUHC.js.map +1 -0
- package/dist/{chunk-ML236QKC.js → chunk-SMXWYP24.js} +5 -5
- package/dist/chunk-SMXWYP24.js.map +1 -0
- package/dist/chunk-SRB2XEWB.js +148 -0
- package/dist/chunk-SRB2XEWB.js.map +1 -0
- package/dist/chunk-SVLR4POP.js +64 -0
- package/dist/chunk-SVLR4POP.js.map +1 -0
- package/dist/chunk-TA66UMI4.js +123 -0
- package/dist/chunk-TA66UMI4.js.map +1 -0
- package/dist/chunk-TEILUWOI.js +664 -0
- package/dist/chunk-TEILUWOI.js.map +1 -0
- package/dist/chunk-TJYQIGAP.js +82 -0
- package/dist/chunk-TJYQIGAP.js.map +1 -0
- package/dist/{chunk-H2X3ISXG.js → chunk-UAG5KWVA.js} +7 -7
- package/dist/chunk-UAG5KWVA.js.map +1 -0
- package/dist/chunk-UDRIWL7A.js +382 -0
- package/dist/chunk-UDRIWL7A.js.map +1 -0
- package/dist/{chunk-2GLDA55J.js → chunk-UIU2THRG.js} +498 -133
- package/dist/chunk-UIU2THRG.js.map +1 -0
- package/dist/{chunk-3YPCK6JX.js → chunk-UPJNJGGA.js} +165 -423
- package/dist/chunk-UPJNJGGA.js.map +1 -0
- package/dist/chunk-UV5MFVO3.js +21 -0
- package/dist/chunk-UV5MFVO3.js.map +1 -0
- package/dist/{chunk-2WUSG3IT.js → chunk-V7RWQRG7.js} +5 -5
- package/dist/chunk-V7RWQRG7.js.map +1 -0
- package/dist/{chunk-VTKGMEPP.js → chunk-VCTFO5CO.js} +13 -3
- package/dist/chunk-VCTFO5CO.js.map +1 -0
- package/dist/{chunk-5T22KDPN.js → chunk-VFQGRQIH.js} +8 -8
- package/dist/chunk-VFQGRQIH.js.map +1 -0
- package/dist/{chunk-2QR2PQTT.js → chunk-VQNJ7UZL.js} +5 -3
- package/dist/chunk-VQNJ7UZL.js.map +1 -0
- package/dist/{chunk-DONMZAD2.js → chunk-WWGT2MDV.js} +43 -165
- package/dist/chunk-WWGT2MDV.js.map +1 -0
- package/dist/{chunk-EMIGCR7X.js → chunk-WXSYDNBT.js} +2 -2
- package/dist/chunk-WXSYDNBT.js.map +1 -0
- package/dist/chunk-WYSO2NIH.js +30 -0
- package/dist/chunk-WYSO2NIH.js.map +1 -0
- package/dist/chunk-XXYIOFUB.js +164 -0
- package/dist/chunk-XXYIOFUB.js.map +1 -0
- package/dist/{chunk-3BFJOWSU.js → chunk-Y22T3KQE.js} +35 -7
- package/dist/chunk-Y22T3KQE.js.map +1 -0
- package/dist/{chunk-A3JMGXPG.js → chunk-YMUGBZN2.js} +2 -2
- package/dist/chunk-YMUGBZN2.js.map +1 -0
- package/dist/{chunk-I5FOWO4J.js → chunk-ZCGAHGUS.js} +52 -73
- package/dist/chunk-ZCGAHGUS.js.map +1 -0
- package/dist/{chunk-FZU343FL.js → chunk-ZJADGNYF.js} +2 -2
- package/dist/chunk-ZJADGNYF.js.map +1 -0
- package/dist/{chunk-B6PB7JLN.js → chunk-ZYUC53LT.js} +427 -6
- package/dist/chunk-ZYUC53LT.js.map +1 -0
- package/dist/collection-facade-GQAOGJP2.js +33 -0
- package/dist/consent/index.d.ts +5 -7
- package/dist/consent/index.js +7 -5
- package/dist/consent/index.js.map +1 -1
- package/dist/crdt/index.d.ts +6 -2
- package/dist/crdt/index.js +3 -2
- package/dist/crdt/index.js.map +1 -1
- package/dist/decrypt-partition-IHtxEnTi.d.ts +21 -0
- package/dist/delegation-UL5HKLER.js +19 -0
- package/dist/derivations/index.d.ts +7 -9
- package/dist/derivations/index.js +11 -6
- package/dist/describe/index.d.ts +1 -0
- package/dist/describe/index.js +1 -0
- package/dist/describe-aBkJR2sd.d.ts +131 -0
- package/dist/{dev-unlock-B4kDxep_.d.ts → dev-unlock-C9Ro3v_O.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/enclave-UL5LW66V.js +88 -0
- package/dist/executor-2FWQRLMG.js +15 -0
- package/dist/executor-QZ7BXICW.js +9 -0
- package/dist/executor-Z5ZLJVTV.js +9 -0
- package/dist/export-accessible-KRV5W4GH.js +17 -0
- package/dist/extract-partition-GLKBOXFQ.js +30 -0
- package/dist/{fanout-sidecar-OKPMMPLG.js → fanout-sidecar-GF3DJ6KR.js} +34 -14
- package/dist/fanout-sidecar-GF3DJ6KR.js.map +1 -0
- package/dist/forget/index.d.ts +36 -0
- package/dist/forget/index.js +19 -0
- package/dist/forget/index.js.map +1 -0
- package/dist/guards/index.d.ts +13 -9
- package/dist/guards/index.js +12 -5
- package/dist/{hash-DdpVypUp.d.cts → hash-Cp5uwiox.d.ts} +5 -1
- package/dist/history/index.d.ts +6 -8
- package/dist/history/index.js +19 -12
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.d.ts +5 -7
- package/dist/i18n/index.js +104 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/in/index.d.ts +5 -0
- package/dist/in/index.js +1 -0
- package/dist/in/index.js.map +1 -0
- package/dist/index-B9QdHytu.d.ts +123 -0
- package/dist/index-BF9_96A5.d.ts +123 -0
- package/dist/{types-Df28QFKb.d.ts → index-BzUo1B6n.d.ts} +16270 -8358
- package/dist/index.d.ts +1088 -450
- package/dist/index.js +1165 -293
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +6 -3
- package/dist/indexing/index.js +6 -5
- package/dist/indexing/index.js.map +1 -1
- package/dist/issue-Y6UVIR43.js +13 -0
- package/dist/issue-Y6UVIR43.js.map +1 -0
- package/dist/kernel/index.d.ts +32 -0
- package/dist/kernel/index.js +53 -0
- package/dist/kernel/index.js.map +1 -0
- package/dist/{ledger-TMRZYNVJ.js → ledger-RYI35CDS.js} +12 -9
- package/dist/ledger-RYI35CDS.js.map +1 -0
- package/dist/liberate-CRPZEV7O.js +18 -0
- package/dist/liberate-CRPZEV7O.js.map +1 -0
- package/dist/materialized-views/index.d.ts +6 -19
- package/dist/materialized-views/index.js +16 -12
- package/dist/mime-magic-CGhw37_E.d.ts +103 -0
- package/dist/noydb-367AM4HT.js +50 -0
- package/dist/noydb-367AM4HT.js.map +1 -0
- package/dist/on/index.d.ts +5 -0
- package/dist/on/index.js +11 -0
- package/dist/on/index.js.map +1 -0
- package/dist/overlay-views/index.d.ts +27 -11
- package/dist/overlay-views/index.js +7 -6
- package/dist/periods/index.d.ts +5 -7
- package/dist/periods/index.js +13 -9
- package/dist/pod/index.d.ts +63 -0
- package/dist/pod/index.js +61 -0
- package/dist/pod/index.js.map +1 -0
- package/dist/policy-IXK4FVXP.js +41 -0
- package/dist/policy-IXK4FVXP.js.map +1 -0
- package/dist/portability/index.d.ts +20 -0
- package/dist/portability/index.js +43 -0
- package/dist/portability/index.js.map +1 -0
- package/dist/{public-envelope-BW6OXORV.js → public-envelope-JHDQCPSX.js} +6 -5
- package/dist/public-envelope-JHDQCPSX.js.map +1 -0
- package/dist/query/index.d.ts +5 -3
- package/dist/query/index.js +21 -13
- package/dist/read-only-facade-5ADRJVTM.js +8 -0
- package/dist/read-only-facade-5ADRJVTM.js.map +1 -0
- package/dist/registry-45RJNWGU.js +9 -0
- package/dist/registry-45RJNWGU.js.map +1 -0
- package/dist/registry-5GRV5VXI.js +13 -0
- package/dist/registry-5GRV5VXI.js.map +1 -0
- package/dist/registry-VW6P6A3J.js +8 -0
- package/dist/registry-VW6P6A3J.js.map +1 -0
- package/dist/registry-WEUCHBO4.js +11 -0
- package/dist/registry-WEUCHBO4.js.map +1 -0
- package/dist/request-withdrawal-GBPH2FDY.js +25 -0
- package/dist/request-withdrawal-GBPH2FDY.js.map +1 -0
- package/dist/revoke-TUFRGI6S.js +18 -0
- package/dist/revoke-TUFRGI6S.js.map +1 -0
- package/dist/sealed-record/index.d.ts +69 -0
- package/dist/sealed-record/index.js +65 -0
- package/dist/sealed-record/index.js.map +1 -0
- package/dist/session/index.d.ts +6 -8
- package/dist/session/index.js +7 -5
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.d.ts +5 -7
- package/dist/shadow/index.js +4 -3
- package/dist/shadow/index.js.map +1 -1
- package/dist/{signer-72QAUSVW.js → signer-PNKTBVF7.js} +6 -5
- package/dist/signer-PNKTBVF7.js.map +1 -0
- package/dist/snapshots/index.d.ts +6 -8
- package/dist/snapshots/index.js +13 -11
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-6ZDBTQT7.js → stale-3JWHNDIY.js} +3 -2
- package/dist/stale-3JWHNDIY.js.map +1 -0
- package/dist/store-coordination-provider-3I7XWZZK.js +142 -0
- package/dist/store-coordination-provider-3I7XWZZK.js.map +1 -0
- package/dist/subject-index-O75wKshW.d.ts +362 -0
- package/dist/sync/index.d.ts +4 -6
- package/dist/sync/index.js +7 -6
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.d.ts +5 -7
- package/dist/team/index.js +20 -16
- package/dist/tiers/index.d.ts +5 -0
- package/dist/tiers/index.js +33 -0
- package/dist/tiers/index.js.map +1 -0
- package/dist/to/index.d.ts +5 -0
- package/dist/to/index.js +17 -0
- package/dist/to/index.js.map +1 -0
- package/dist/transition-guard-C7ol6oPw.d.ts +165 -0
- package/dist/tx/index.d.ts +23 -9
- package/dist/tx/index.js +13 -8
- package/dist/tx/index.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ulid-DRH25k3y.d.ts +66 -0
- package/dist/ulid-PTAIMDG4.js +10 -0
- package/dist/ulid-PTAIMDG4.js.map +1 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +7 -2
- package/dist/util/index.js.map +1 -1
- package/dist/vault-diff-B5fYNBL7.d.ts +147 -0
- package/dist/with/index.d.ts +38 -0
- package/dist/with/index.js +25 -0
- package/dist/with/index.js.map +1 -0
- package/dist/{with-materialized-view-BLkQxoQN.d.ts → with-materialized-view-6p0Fk8bL.d.ts} +1 -1
- package/dist/{with-overlayed-view-CRsSEwHR.d.ts → with-overlayed-view-BJ6mXGMd.d.ts} +2 -3
- package/dist/with-rollup-BvQo3ROo.d.ts +47 -0
- package/dist/withdraw-accessible-FFS46EOD.js +22 -0
- package/dist/withdraw-accessible-FFS46EOD.js.map +1 -0
- package/dist/zod-HAJM7LMS.js +14763 -0
- package/dist/zod-HAJM7LMS.js.map +1 -0
- package/package.json +121 -201
- package/dist/aggregate/index.cjs.map +0 -1
- package/dist/aggregate/index.d.cts +0 -38
- package/dist/attestation/index.cjs +0 -305
- package/dist/attestation/index.cjs.map +0 -1
- package/dist/attestation/index.d.cts +0 -52
- package/dist/blobs/index.cjs +0 -1480
- package/dist/blobs/index.cjs.map +0 -1
- package/dist/blobs/index.d.cts +0 -46
- package/dist/bundle/index.cjs +0 -19264
- package/dist/bundle/index.cjs.map +0 -1
- package/dist/bundle/index.d.cts +0 -176
- package/dist/chunk-22DWZL57.js.map +0 -1
- package/dist/chunk-2GLDA55J.js.map +0 -1
- package/dist/chunk-2QR2PQTT.js.map +0 -1
- package/dist/chunk-2WUSG3IT.js.map +0 -1
- package/dist/chunk-3BFJOWSU.js.map +0 -1
- package/dist/chunk-3YPCK6JX.js.map +0 -1
- package/dist/chunk-53XBRIRT.js.map +0 -1
- package/dist/chunk-5T22KDPN.js.map +0 -1
- package/dist/chunk-5XHKQ56N.js +0 -340
- package/dist/chunk-5XHKQ56N.js.map +0 -1
- package/dist/chunk-6CME4UEK.js.map +0 -1
- package/dist/chunk-6STK5TQP.js +0 -139
- package/dist/chunk-6STK5TQP.js.map +0 -1
- package/dist/chunk-A3JMGXPG.js.map +0 -1
- package/dist/chunk-B6PB7JLN.js.map +0 -1
- package/dist/chunk-BVRYKATC.js.map +0 -1
- package/dist/chunk-DE6GKS6G.js.map +0 -1
- package/dist/chunk-DFMLEQZB.js.map +0 -1
- package/dist/chunk-DJHHA6XH.js.map +0 -1
- package/dist/chunk-DL3HWOQ5.js.map +0 -1
- package/dist/chunk-DONMZAD2.js.map +0 -1
- package/dist/chunk-EMIGCR7X.js.map +0 -1
- package/dist/chunk-F3GDRNUT.js.map +0 -1
- package/dist/chunk-FZU343FL.js.map +0 -1
- package/dist/chunk-H2X3ISXG.js.map +0 -1
- package/dist/chunk-HNRHLRDC.js.map +0 -1
- package/dist/chunk-I5FOWO4J.js.map +0 -1
- package/dist/chunk-J66GRPNH.js.map +0 -1
- package/dist/chunk-JWRVQKRZ.js.map +0 -1
- package/dist/chunk-K3DK3NU5.js.map +0 -1
- package/dist/chunk-ML236QKC.js.map +0 -1
- package/dist/chunk-NONAAENK.js.map +0 -1
- package/dist/chunk-O3VZPBZG.js.map +0 -1
- package/dist/chunk-OIF6LZUR.js.map +0 -1
- package/dist/chunk-OQ6PIGHA.js +0 -19
- package/dist/chunk-OQ6PIGHA.js.map +0 -1
- package/dist/chunk-PE2FR4J3.js.map +0 -1
- package/dist/chunk-PP6W64Y3.js +0 -275
- package/dist/chunk-PP6W64Y3.js.map +0 -1
- package/dist/chunk-PX35GA7L.js.map +0 -1
- package/dist/chunk-QEILDE6R.js +0 -793
- package/dist/chunk-QEILDE6R.js.map +0 -1
- package/dist/chunk-QODLLGQ7.js.map +0 -1
- package/dist/chunk-RAZ4OVLL.js +0 -51
- package/dist/chunk-RAZ4OVLL.js.map +0 -1
- package/dist/chunk-SYMWGEET.js.map +0 -1
- package/dist/chunk-T7JEBOGN.js.map +0 -1
- package/dist/chunk-TFBP23BX.js.map +0 -1
- package/dist/chunk-TV3YZ35S.js +0 -90
- package/dist/chunk-TV3YZ35S.js.map +0 -1
- package/dist/chunk-U26HQ6KJ.js.map +0 -1
- package/dist/chunk-UF3BUNQZ.js +0 -1
- package/dist/chunk-UMLVJTYV.js +0 -20
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-VTKGMEPP.js.map +0 -1
- package/dist/chunk-W6EQLGMB.js +0 -100
- package/dist/chunk-W6EQLGMB.js.map +0 -1
- package/dist/chunk-WIRRPTFH.js +0 -17
- package/dist/chunk-WIRRPTFH.js.map +0 -1
- package/dist/chunk-WPLVTJ5D.js.map +0 -1
- package/dist/chunk-XJFLDA7A.js.map +0 -1
- package/dist/chunk-Z6FNBOTC.js.map +0 -1
- package/dist/chunk-ZYWZWG6G.js.map +0 -1
- package/dist/consent/index.cjs +0 -204
- package/dist/consent/index.cjs.map +0 -1
- package/dist/consent/index.d.cts +0 -25
- package/dist/crdt/index.cjs +0 -152
- package/dist/crdt/index.cjs.map +0 -1
- package/dist/crdt/index.d.cts +0 -30
- package/dist/crypto-HTZ4FJOP.js +0 -44
- package/dist/delegation-RI54P6I5.js +0 -17
- package/dist/derivations/index.cjs +0 -351
- package/dist/derivations/index.cjs.map +0 -1
- package/dist/derivations/index.d.cts +0 -72
- package/dist/dev-unlock-BIoEFbwm.d.cts +0 -263
- package/dist/executor-5PNY7LGW.js +0 -8
- package/dist/executor-B4QIYGZX.js +0 -8
- package/dist/executor-BWXXDQ7K.js +0 -11
- package/dist/fanout-sidecar-OKPMMPLG.js.map +0 -1
- package/dist/guards/index.cjs +0 -322
- package/dist/guards/index.cjs.map +0 -1
- package/dist/guards/index.d.cts +0 -31
- package/dist/hash-HJqD14vS.d.ts +0 -63
- package/dist/history/index.cjs +0 -1222
- package/dist/history/index.cjs.map +0 -1
- package/dist/history/index.d.cts +0 -63
- package/dist/i18n/index.cjs +0 -1100
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/index.d.cts +0 -39
- package/dist/index-4fBVt8j9.d.cts +0 -2387
- package/dist/index-D8I_pyJD.d.ts +0 -2387
- package/dist/index.cjs +0 -24487
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -776
- package/dist/indexing/index.cjs +0 -742
- package/dist/indexing/index.cjs.map +0 -1
- package/dist/indexing/index.d.cts +0 -36
- package/dist/issue-VGDPP4B5.js +0 -12
- package/dist/lazy-builder-7tIpFyWN.d.ts +0 -304
- package/dist/lazy-builder-wY4pMCEe.d.cts +0 -304
- package/dist/materialized-views/index.cjs +0 -854
- package/dist/materialized-views/index.cjs.map +0 -1
- package/dist/materialized-views/index.d.cts +0 -186
- package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
- package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
- package/dist/noydb-G725ZSZG.js +0 -34
- package/dist/overlay-views/index.cjs +0 -359
- package/dist/overlay-views/index.cjs.map +0 -1
- package/dist/overlay-views/index.d.cts +0 -82
- package/dist/periods/index.cjs +0 -1041
- package/dist/periods/index.cjs.map +0 -1
- package/dist/periods/index.d.cts +0 -22
- package/dist/predicate-BSAGEyu5.d.cts +0 -209
- package/dist/predicate-BSAGEyu5.d.ts +0 -209
- package/dist/query/index.cjs +0 -2375
- package/dist/query/index.cjs.map +0 -1
- package/dist/query/index.d.cts +0 -3
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-3QP3YKQS.js +0 -8
- package/dist/registry-DKEXOJVO.js +0 -7
- package/dist/registry-OJIOSBV6.js +0 -10
- package/dist/registry-USRVT6YF.js +0 -8
- package/dist/revoke-JCC7N56X.js +0 -17
- package/dist/session/index.cjs +0 -495
- package/dist/session/index.cjs.map +0 -1
- package/dist/session/index.d.cts +0 -46
- package/dist/shadow/index.cjs +0 -133
- package/dist/shadow/index.cjs.map +0 -1
- package/dist/shadow/index.d.cts +0 -17
- package/dist/snapshots/index.cjs +0 -937
- package/dist/snapshots/index.cjs.map +0 -1
- package/dist/snapshots/index.d.cts +0 -28
- package/dist/store/index.cjs +0 -1083
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -492
- package/dist/store/index.d.ts +0 -492
- package/dist/store/index.js +0 -37
- package/dist/strategy-BSxFXGzb.d.cts +0 -110
- package/dist/strategy-BSxFXGzb.d.ts +0 -110
- package/dist/strategy-DSTrsZ8t.d.cts +0 -601
- package/dist/strategy-DSTrsZ8t.d.ts +0 -601
- package/dist/sync/index.cjs +0 -1062
- package/dist/sync/index.cjs.map +0 -1
- package/dist/sync/index.d.cts +0 -43
- package/dist/team/index.cjs +0 -2798
- package/dist/team/index.cjs.map +0 -1
- package/dist/team/index.d.cts +0 -118
- package/dist/tx/index.cjs +0 -544
- package/dist/tx/index.cjs.map +0 -1
- package/dist/tx/index.d.cts +0 -21
- package/dist/types-DyXYr8rE.d.cts +0 -12818
- package/dist/ulid-COREQ2RQ.js +0 -9
- package/dist/ulid-Drr7ykr6.d.cts +0 -603
- package/dist/util/index.cjs +0 -230
- package/dist/util/index.cjs.map +0 -1
- package/dist/util/index.d.cts +0 -77
- package/dist/with-derivation-DFnFByiQ.d.ts +0 -13
- package/dist/with-derivation-DU3Sjazm.d.cts +0 -13
- package/dist/with-guard-ByyxmEe7.d.cts +0 -18
- package/dist/with-guard-qube6BMI.d.ts +0 -18
- package/dist/with-materialized-view-BmEToIR1.d.cts +0 -27
- package/dist/with-overlayed-view-BMsQQbWm.d.cts +0 -13
- /package/dist/{store → adapter}/index.js.map +0 -0
- /package/dist/{chunk-UF3BUNQZ.js.map → api-RW3KP7WU.js.map} +0 -0
- /package/dist/{crypto-HTZ4FJOP.js.map → as/index.js.map} +0 -0
- /package/dist/{delegation-RI54P6I5.js.map → at/index.js.map} +0 -0
- /package/dist/{executor-5PNY7LGW.js.map → by/index.js.map} +0 -0
- /package/dist/{executor-B4QIYGZX.js.map → cargo/index.js.map} +0 -0
- /package/dist/{executor-BWXXDQ7K.js.map → chunk-BGIZAFY7.js.map} +0 -0
- /package/dist/{issue-VGDPP4B5.js.map → chunk-CHFZDSE4.js.map} +0 -0
- /package/dist/{ledger-TMRZYNVJ.js.map → chunk-GHVIKOHT.js.map} +0 -0
- /package/dist/{noydb-G725ZSZG.js.map → chunk-K2WCO3NX.js.map} +0 -0
- /package/dist/{public-envelope-BW6OXORV.js.map → chunk-PZ5AY32C.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → collection-facade-GQAOGJP2.js.map} +0 -0
- /package/dist/{registry-3QP3YKQS.js.map → delegation-UL5HKLER.js.map} +0 -0
- /package/dist/{registry-DKEXOJVO.js.map → describe/index.js.map} +0 -0
- /package/dist/{registry-OJIOSBV6.js.map → enclave-UL5LW66V.js.map} +0 -0
- /package/dist/{registry-USRVT6YF.js.map → executor-2FWQRLMG.js.map} +0 -0
- /package/dist/{revoke-JCC7N56X.js.map → executor-QZ7BXICW.js.map} +0 -0
- /package/dist/{signer-72QAUSVW.js.map → executor-Z5ZLJVTV.js.map} +0 -0
- /package/dist/{stale-6ZDBTQT7.js.map → export-accessible-KRV5W4GH.js.map} +0 -0
- /package/dist/{ulid-COREQ2RQ.js.map → extract-partition-GLKBOXFQ.js.map} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
freezeAndDeleteClosure,
|
|
3
|
+
randomId
|
|
4
|
+
} from "./chunk-PSMV73A5.js";
|
|
5
|
+
import {
|
|
6
|
+
buildAccessibleBundle,
|
|
7
|
+
resolveAccessibleCollections
|
|
8
|
+
} from "./chunk-HCIPRAGS.js";
|
|
9
|
+
import {
|
|
10
|
+
NOYDB_FORMAT_VERSION
|
|
11
|
+
} from "./chunk-5TDJ56JE.js";
|
|
12
|
+
import {
|
|
13
|
+
NoydbError
|
|
14
|
+
} from "./chunk-ZYUC53LT.js";
|
|
15
|
+
|
|
16
|
+
// src/with-audit/portability/request-withdrawal.ts
|
|
17
|
+
var WITHDRAWAL_REQUESTS_COLLECTION = "_user_withdrawal_requests";
|
|
18
|
+
var WithdrawalRequestError = class extends NoydbError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super("WITHDRAWAL_REQUEST", message);
|
|
21
|
+
this.name = "WithdrawalRequestError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function writeRequest(vault, req, expectedVersion) {
|
|
25
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
26
|
+
const body = JSON.stringify(req);
|
|
27
|
+
const env = {
|
|
28
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
29
|
+
_v: expectedVersion + 1,
|
|
30
|
+
_ts: req.decidedAt ?? req.requestedAt,
|
|
31
|
+
_iv: "",
|
|
32
|
+
_data: body,
|
|
33
|
+
_by: req.decidedBy ?? req.requester
|
|
34
|
+
};
|
|
35
|
+
return adapter.put(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, req.requestId, env, expectedVersion);
|
|
36
|
+
}
|
|
37
|
+
async function readRequest(vault, requestId) {
|
|
38
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
39
|
+
const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, requestId);
|
|
40
|
+
if (!env) throw new WithdrawalRequestError(`withdrawal request "${requestId}" not found`);
|
|
41
|
+
return { req: JSON.parse(env._data), version: env._v };
|
|
42
|
+
}
|
|
43
|
+
async function requestWithdrawal(vault, opts = {}) {
|
|
44
|
+
const { keyring } = vault._introspectState();
|
|
45
|
+
const collections = resolveAccessibleCollections(keyring, opts.scope, false);
|
|
46
|
+
if (!collections || collections.length === 0) {
|
|
47
|
+
throw new WithdrawalRequestError(
|
|
48
|
+
"requestWithdrawal needs a concrete scope.collections (the caller has all-collection access \u2014 name the collections to withdraw)"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const requestId = `wr-${randomId()}`;
|
|
52
|
+
const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
+
const req = {
|
|
54
|
+
requestId,
|
|
55
|
+
requester: keyring.userId,
|
|
56
|
+
collections,
|
|
57
|
+
disposition: opts.disposition ?? "delete",
|
|
58
|
+
status: "pending",
|
|
59
|
+
requestedAt,
|
|
60
|
+
...opts.legalBasis ? { legalBasis: opts.legalBasis } : {},
|
|
61
|
+
...opts.expiresInMs ? { expiresAt: new Date(Date.parse(requestedAt) + opts.expiresInMs).toISOString() } : {}
|
|
62
|
+
};
|
|
63
|
+
await writeRequest(vault, req, 0);
|
|
64
|
+
await vault._getLedgerOrNull()?.append({
|
|
65
|
+
op: "lifecycle",
|
|
66
|
+
collection: "",
|
|
67
|
+
id: "",
|
|
68
|
+
version: 0,
|
|
69
|
+
actor: keyring.userId,
|
|
70
|
+
payloadHash: "",
|
|
71
|
+
reason: `user-withdrawal-request:${requestId}:${keyring.userId}`
|
|
72
|
+
});
|
|
73
|
+
return { requestId, status: "pending", ...req.expiresAt ? { expiresAt: req.expiresAt } : {} };
|
|
74
|
+
}
|
|
75
|
+
async function listWithdrawalRequests(vault, opts = {}) {
|
|
76
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
77
|
+
const ids = await adapter.list(vaultName, WITHDRAWAL_REQUESTS_COLLECTION);
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const id of ids) {
|
|
80
|
+
const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, id);
|
|
81
|
+
if (!env) continue;
|
|
82
|
+
const req = JSON.parse(env._data);
|
|
83
|
+
if (!opts.status || req.status === opts.status) out.push(req);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function assertApprover(vault) {
|
|
88
|
+
const { keyring } = vault._introspectState();
|
|
89
|
+
if (keyring.role !== "owner" && keyring.role !== "admin") {
|
|
90
|
+
throw new WithdrawalRequestError("approveWithdrawal / rejectWithdrawal require an owner or admin");
|
|
91
|
+
}
|
|
92
|
+
return keyring.userId;
|
|
93
|
+
}
|
|
94
|
+
function assertPending(req) {
|
|
95
|
+
if (req.status !== "pending") {
|
|
96
|
+
throw new WithdrawalRequestError(`withdrawal request "${req.requestId}" is already ${req.status}`);
|
|
97
|
+
}
|
|
98
|
+
if (req.expiresAt && Date.now() > Date.parse(req.expiresAt)) {
|
|
99
|
+
throw new WithdrawalRequestError(`withdrawal request "${req.requestId}" expired at ${req.expiresAt}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function approveWithdrawal(vault, requestId, opts = {}) {
|
|
103
|
+
const approver = assertApprover(vault);
|
|
104
|
+
const { req, version } = await readRequest(vault, requestId);
|
|
105
|
+
assertPending(req);
|
|
106
|
+
const bundle = await buildAccessibleBundle(vault, [...req.collections], opts.reKey);
|
|
107
|
+
const snapshot = await freezeAndDeleteClosure(vault, req.collections, {
|
|
108
|
+
disposition: req.disposition,
|
|
109
|
+
actorUserId: approver,
|
|
110
|
+
withdrawalId: `wd-${requestId}`
|
|
111
|
+
});
|
|
112
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
113
|
+
await writeRequest(vault, {
|
|
114
|
+
...req,
|
|
115
|
+
status: "approved",
|
|
116
|
+
decidedAt,
|
|
117
|
+
decidedBy: approver,
|
|
118
|
+
...snapshot ? { snapshotSha256: snapshot.sha256 } : {}
|
|
119
|
+
}, version);
|
|
120
|
+
await vault._getLedgerOrNull()?.append({
|
|
121
|
+
op: "lifecycle",
|
|
122
|
+
collection: "",
|
|
123
|
+
id: "",
|
|
124
|
+
version: 0,
|
|
125
|
+
actor: approver,
|
|
126
|
+
payloadHash: "",
|
|
127
|
+
reason: `user-withdrawal-approved:${requestId}:${req.requester}:${req.disposition}`
|
|
128
|
+
});
|
|
129
|
+
return snapshot ? { bundle, snapshot } : { bundle };
|
|
130
|
+
}
|
|
131
|
+
async function rejectWithdrawal(vault, requestId, opts = {}) {
|
|
132
|
+
const approver = assertApprover(vault);
|
|
133
|
+
const { req, version } = await readRequest(vault, requestId);
|
|
134
|
+
assertPending(req);
|
|
135
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
136
|
+
const updated = {
|
|
137
|
+
...req,
|
|
138
|
+
status: "rejected",
|
|
139
|
+
decidedAt,
|
|
140
|
+
decidedBy: approver,
|
|
141
|
+
...opts.reason ? { rejectReason: opts.reason } : {}
|
|
142
|
+
};
|
|
143
|
+
await writeRequest(vault, updated, version);
|
|
144
|
+
await vault._getLedgerOrNull()?.append({
|
|
145
|
+
op: "lifecycle",
|
|
146
|
+
collection: "",
|
|
147
|
+
id: "",
|
|
148
|
+
version: 0,
|
|
149
|
+
actor: approver,
|
|
150
|
+
payloadHash: "",
|
|
151
|
+
reason: `user-withdrawal-rejected:${requestId}:${req.requester}`
|
|
152
|
+
});
|
|
153
|
+
return updated;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
WITHDRAWAL_REQUESTS_COLLECTION,
|
|
158
|
+
WithdrawalRequestError,
|
|
159
|
+
requestWithdrawal,
|
|
160
|
+
listWithdrawalRequests,
|
|
161
|
+
approveWithdrawal,
|
|
162
|
+
rejectWithdrawal
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=chunk-XXYIOFUB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-audit/portability/request-withdrawal.ts"],"sourcesContent":["/**\n * Two-party withdrawal ceremony.\n *\n * The conservative counterpart to `unilateralWithdrawal`: a non-owner —\n * including a read-only `client`/`viewer` who cannot self-serve a deletion —\n * files a durable REQUEST; an owner/admin reviews it and either APPROVES\n * (extract-and-dispose under firm authority) or REJECTS it. Every step is\n * audited in the tamper-evident ledger.\n *\n * requester: vault.user.requestWithdrawal({ scope, disposition?, legalBasis? })\n * owner: vault.user.listWithdrawalRequests()\n * vault.user.approveWithdrawal(requestId, { reKey }) → { bundle, snapshot? }\n * vault.user.rejectWithdrawal(requestId, { reason })\n *\n * Requests live in the reserved `_user_withdrawal_requests` namespace. The\n * record body is plaintext metadata (collection names + disposition + legal\n * basis — none secret in this trust model; the owner sees the data anyway) and\n * carries NO passphrase: the re-key passphrase is supplied by the approver at\n * approval time and conveyed to the requester out-of-band, so no secret is\n * stored at rest.\n */\nimport type { Vault } from '../../kernel/vault.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport { NoydbError } from '../../kernel/errors.js'\nimport { resolveAccessibleCollections, buildAccessibleBundle } from './export-accessible.js'\nimport { freezeAndDeleteClosure, randomId, type FrozenSnapshotRef, type WithdrawResult } from './withdraw-accessible.js'\n\nexport const WITHDRAWAL_REQUESTS_COLLECTION = '_user_withdrawal_requests'\n\n/** Raised when a request is missing, already decided, or expired. */\nexport class WithdrawalRequestError extends NoydbError {\n constructor(message: string) {\n super('WITHDRAWAL_REQUEST', message)\n this.name = 'WithdrawalRequestError'\n }\n}\n\nexport type WithdrawalRequestStatus = 'pending' | 'approved' | 'rejected'\n\nexport interface WithdrawalRequest {\n readonly requestId: string\n readonly requester: string\n readonly collections: readonly string[]\n readonly disposition: 'delete' | 'freeze'\n readonly legalBasis?: string\n readonly status: WithdrawalRequestStatus\n readonly requestedAt: string\n readonly expiresAt?: string\n readonly decidedAt?: string\n readonly decidedBy?: string\n readonly rejectReason?: string\n readonly snapshotSha256?: string\n}\n\nexport interface RequestWithdrawalOptions {\n readonly scope?: { readonly collections?: readonly string[] }\n readonly disposition?: 'delete' | 'freeze'\n readonly legalBasis?: string\n /** Time-to-live in ms; after this the request can no longer be approved. */\n readonly expiresInMs?: number\n}\n\nexport interface RequestWithdrawalResult {\n readonly requestId: string\n readonly status: WithdrawalRequestStatus\n readonly expiresAt?: string\n}\n\nfunction writeRequest(vault: Vault, req: WithdrawalRequest, expectedVersion: number): Promise<void> {\n const { name: vaultName, adapter } = vault._introspectState()\n const body = JSON.stringify(req)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION, _v: expectedVersion + 1, _ts: req.decidedAt ?? req.requestedAt,\n _iv: '', _data: body, _by: req.decidedBy ?? req.requester,\n }\n return adapter.put(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, req.requestId, env, expectedVersion)\n}\n\nasync function readRequest(vault: Vault, requestId: string): Promise<{ req: WithdrawalRequest; version: number }> {\n const { name: vaultName, adapter } = vault._introspectState()\n const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, requestId)\n if (!env) throw new WithdrawalRequestError(`withdrawal request \"${requestId}\" not found`)\n return { req: JSON.parse(env._data) as WithdrawalRequest, version: env._v }\n}\n\n/**\n * Requester side. Files a durable request for the caller's accessible scope.\n * Gated by `user-request-withdrawal` (checked in the UserApi wrapper).\n */\nexport async function requestWithdrawal(\n vault: Vault,\n opts: RequestWithdrawalOptions = {},\n): Promise<RequestWithdrawalResult> {\n const { keyring } = vault._introspectState()\n // Resolve the requester's accessible collections (read access defines \"theirs\";\n // the owner — who can delete anything — executes the disposition on approval).\n const collections = resolveAccessibleCollections(keyring, opts.scope, false)\n if (!collections || collections.length === 0) {\n throw new WithdrawalRequestError(\n 'requestWithdrawal needs a concrete scope.collections (the caller has all-collection access — name the collections to withdraw)',\n )\n }\n const requestId = `wr-${randomId()}`\n const requestedAt = new Date().toISOString()\n const req: WithdrawalRequest = {\n requestId, requester: keyring.userId, collections, disposition: opts.disposition ?? 'delete',\n status: 'pending', requestedAt,\n ...(opts.legalBasis ? { legalBasis: opts.legalBasis } : {}),\n ...(opts.expiresInMs ? { expiresAt: new Date(Date.parse(requestedAt) + opts.expiresInMs).toISOString() } : {}),\n }\n await writeRequest(vault, req, 0) // create-only\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle', collection: '', id: '', version: 0, actor: keyring.userId, payloadHash: '',\n reason: `user-withdrawal-request:${requestId}:${keyring.userId}`,\n })\n return { requestId, status: 'pending', ...(req.expiresAt ? { expiresAt: req.expiresAt } : {}) }\n}\n\n/** Owner side. List filed requests (optionally by status). */\nexport async function listWithdrawalRequests(\n vault: Vault,\n opts: { status?: WithdrawalRequestStatus } = {},\n): Promise<WithdrawalRequest[]> {\n const { name: vaultName, adapter } = vault._introspectState()\n const ids = await adapter.list(vaultName, WITHDRAWAL_REQUESTS_COLLECTION)\n const out: WithdrawalRequest[] = []\n for (const id of ids) {\n const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, id)\n if (!env) continue\n const req = JSON.parse(env._data) as WithdrawalRequest\n if (!opts.status || req.status === opts.status) out.push(req)\n }\n return out\n}\n\nfunction assertApprover(vault: Vault): string {\n const { keyring } = vault._introspectState()\n // FR-6: custodian is INTENTIONALLY excluded (SAFER default — review flag).\n // Approving a two-party withdrawal is a destructive extract-and-dispose\n // exercised under FIRM authority — a governance act adjacent to grant/revoke,\n // which a non-owning custodian cannot do. It stays owner/admin-only.\n if (keyring.role !== 'owner' && keyring.role !== 'admin') {\n throw new WithdrawalRequestError('approveWithdrawal / rejectWithdrawal require an owner or admin')\n }\n return keyring.userId\n}\n\nfunction assertPending(req: WithdrawalRequest): void {\n if (req.status !== 'pending') {\n throw new WithdrawalRequestError(`withdrawal request \"${req.requestId}\" is already ${req.status}`)\n }\n if (req.expiresAt && Date.now() > Date.parse(req.expiresAt)) {\n throw new WithdrawalRequestError(`withdrawal request \"${req.requestId}\" expired at ${req.expiresAt}`)\n }\n}\n\nexport interface ApproveWithdrawalOptions {\n /** Re-key the handed-back bundle to a passphrase the requester will use. */\n readonly reKey?: { readonly passphrase: string }\n}\n\n/**\n * Owner side. Extract the requester's recorded scope under firm authority,\n * dispose of the source per the request's disposition, mark the request\n * approved, and return the re-keyed bundle to hand back. Gated by\n * `approve-user-withdrawal` (checked in the UserApi wrapper) + owner/admin role.\n */\nexport async function approveWithdrawal(\n vault: Vault,\n requestId: string,\n opts: ApproveWithdrawalOptions = {},\n): Promise<WithdrawResult> {\n const approver = assertApprover(vault)\n const { req, version } = await readRequest(vault, requestId)\n assertPending(req)\n\n // 1. Produce the requester's re-keyed portable copy FIRST (owner authority\n // holds every DEK, so the recorded collections are all readable).\n const bundle = await buildAccessibleBundle(vault, [...req.collections], opts.reKey)\n\n // 2. + 3. dispose of the source (freeze snapshot, optional → delete-closure).\n const snapshot: FrozenSnapshotRef | undefined = await freezeAndDeleteClosure(vault, req.collections, {\n disposition: req.disposition, actorUserId: approver, withdrawalId: `wd-${requestId}`,\n })\n\n // 4. mark approved + audit (OCC against the version we read).\n const decidedAt = new Date().toISOString()\n await writeRequest(vault, {\n ...req, status: 'approved', decidedAt, decidedBy: approver,\n ...(snapshot ? { snapshotSha256: snapshot.sha256 } : {}),\n }, version)\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle', collection: '', id: '', version: 0, actor: approver, payloadHash: '',\n reason: `user-withdrawal-approved:${requestId}:${req.requester}:${req.disposition}`,\n })\n\n return snapshot ? { bundle, snapshot } : { bundle }\n}\n\nexport interface RejectWithdrawalOptions {\n readonly reason?: string\n}\n\n/** Owner side. Decline a pending request (no data is touched). */\nexport async function rejectWithdrawal(\n vault: Vault,\n requestId: string,\n opts: RejectWithdrawalOptions = {},\n): Promise<WithdrawalRequest> {\n const approver = assertApprover(vault)\n const { req, version } = await readRequest(vault, requestId)\n assertPending(req)\n const decidedAt = new Date().toISOString()\n const updated: WithdrawalRequest = {\n ...req, status: 'rejected', decidedAt, decidedBy: approver,\n ...(opts.reason ? { rejectReason: opts.reason } : {}),\n }\n await writeRequest(vault, updated, version)\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle', collection: '', id: '', version: 0, actor: approver, payloadHash: '',\n reason: `user-withdrawal-rejected:${requestId}:${req.requester}`,\n })\n return updated\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NOYDB_FORMAT_VERSION
|
|
3
|
-
} from "./chunk-WIRRPTFH.js";
|
|
4
1
|
import {
|
|
5
2
|
encrypt
|
|
6
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5O3AOPDT.js";
|
|
4
|
+
import {
|
|
5
|
+
NOYDB_FORMAT_VERSION
|
|
6
|
+
} from "./chunk-5TDJ56JE.js";
|
|
7
7
|
|
|
8
|
-
// src/blobs/export-blobs.ts
|
|
8
|
+
// src/with-shape/blobs/export-blobs.ts
|
|
9
9
|
var ExportBlobsAbortedError = class extends Error {
|
|
10
10
|
constructor(reason) {
|
|
11
11
|
super(`exportBlobs aborted: ${reason}`);
|
|
@@ -107,7 +107,7 @@ function generateBatchId() {
|
|
|
107
107
|
return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
// src/blobs/blob-compaction.ts
|
|
110
|
+
// src/with-shape/blobs/blob-compaction.ts
|
|
111
111
|
var BLOB_EVICTION_AUDIT_COLLECTION = "_blob_eviction_audit";
|
|
112
112
|
async function runCompaction(ctx, options = {}) {
|
|
113
113
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
@@ -118,6 +118,7 @@ async function runCompaction(ctx, options = {}) {
|
|
|
118
118
|
let evicted = 0;
|
|
119
119
|
let records = 0;
|
|
120
120
|
let auditEntries = 0;
|
|
121
|
+
let held = 0;
|
|
121
122
|
let collectionsWithPolicy = 0;
|
|
122
123
|
outer: for (const collectionName of allCollections) {
|
|
123
124
|
if (collectionName.startsWith("_")) continue;
|
|
@@ -141,6 +142,10 @@ async function runCompaction(ctx, options = {}) {
|
|
|
141
142
|
if (!policy) continue;
|
|
142
143
|
const reason = evaluatePolicy(policy, record, slot, now);
|
|
143
144
|
if (!reason) continue;
|
|
145
|
+
if (isHeld(policy, record, now)) {
|
|
146
|
+
held += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
144
149
|
if (!dryRun) {
|
|
145
150
|
await ctx.deleteSlot(collectionName, recordId, slot.name);
|
|
146
151
|
await writeAuditEntry(ctx, {
|
|
@@ -165,9 +170,32 @@ async function runCompaction(ctx, options = {}) {
|
|
|
165
170
|
records,
|
|
166
171
|
collections: collectionsWithPolicy,
|
|
167
172
|
auditEntries,
|
|
173
|
+
held,
|
|
168
174
|
byCollection
|
|
169
175
|
};
|
|
170
176
|
}
|
|
177
|
+
function isHeld(policy, record, now) {
|
|
178
|
+
if (policy.legalHold) {
|
|
179
|
+
try {
|
|
180
|
+
if (policy.legalHold(record)) return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (policy.retainUntil) {
|
|
186
|
+
try {
|
|
187
|
+
const until = policy.retainUntil(record);
|
|
188
|
+
if (until !== null && until !== void 0) {
|
|
189
|
+
const t = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Date.parse(String(until));
|
|
190
|
+
if (!Number.isFinite(t)) return true;
|
|
191
|
+
if (t > now.getTime()) return true;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
171
199
|
function evaluatePolicy(policy, record, slot, now) {
|
|
172
200
|
let ttlTriggered = false;
|
|
173
201
|
let predicateTriggered = false;
|
|
@@ -230,4 +258,4 @@ export {
|
|
|
230
258
|
BLOB_EVICTION_AUDIT_COLLECTION,
|
|
231
259
|
runCompaction
|
|
232
260
|
};
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
261
|
+
//# sourceMappingURL=chunk-Y22T3KQE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-shape/blobs/export-blobs.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../../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 { encrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n /**\n * **Legal hold.** When this predicate returns `true`, the slot is\n * never evicted — `retainDays`/`evictWhen` are overridden. Use for a\n * litigation / audit hold on a fiscal document: the blob stays until\n * the predicate returns `false` (the hold is released). Fail-closed:\n * if the predicate throws, the slot is treated as held.\n */\n readonly legalHold?: (record: T) => boolean\n /**\n * **Period-bound retention.** Returns the date (Date / ISO string /\n * epoch ms) until which the slot must be retained — typically derived\n * from the record's fiscal period (e.g. period end + 10 years). While\n * `now < retainUntil`, the slot is never evicted, regardless of\n * `retainDays`. Return `null`/`undefined` to impose no floor.\n * Fail-closed: a throwing function holds the slot.\n */\n readonly retainUntil?: (record: T) => Date | string | number | null | undefined\n /**\n * **External projection.** When `true`, this field's bytes are stored in the\n * vault's `ObjectProjection` (`createNoydb({ objectStore })`) as a single raw,\n * **unencrypted** object — servable directly from S3/CDN and processable by\n * native tooling — instead of the encrypted-chunk path. The encrypted slot\n * record remains the catalog (anchoring invariant). Requires an `objectStore`;\n * **outside the zero-knowledge guarantee** — use only for assets meant to\n * leave the vault. See the as-aws-s3 design spec.\n */\n readonly external?: boolean\n /**\n * For an `external` field: make the object world-readable (CDN origin) rather\n * than presigned-only. Default `false` (presigned). Ignored unless `external`.\n */\n readonly public?: boolean\n /**\n * For an `external` field: how to stamp a **backlink** (this record's\n * vault/collection/id/field) onto the object's metadata — the self-describing\n * \"secondary store\" that powers reconcile / DR / import re-pairing.\n * - `'opaque-token'` (default): a random id; preserves the opaque-bucket\n * property (no names leak); the token is also recorded on the slot.\n * - `'encrypted'`: the reference encrypted under the blob DEK (ZK-preserving;\n * falls back to `'opaque-token'` on a plaintext vault).\n * - `'plain'`: the reference in cleartext metadata — **leaks structure** to\n * bucket readers; only for non-sensitive deployments.\n * - `'none'`: no backlink.\n */\n readonly backlink?: 'opaque-token' | 'encrypted' | 'plain' | 'none'\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n readonly reason: 'ttl' | 'predicate' | 'both'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /** Number of audit entries written. Equal to `evicted`. */\n readonly auditEntries: number\n /**\n * Number of slots that would have evicted (TTL/predicate triggered)\n * but were retained by a `legalHold` or `retainUntil` floor.\n */\n readonly held: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<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\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let held = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n // Retention floor: a legal hold or period-bound retainUntil\n // blocks an otherwise-due eviction. Counted, never evicted.\n if (isHeld(policy, record, now)) {\n held += 1\n continue\n }\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n byCollection,\n }\n}\n\n/**\n * Whether a retention floor (legal hold or period-bound `retainUntil`)\n * currently blocks eviction of this record's slots. Fail-closed: a\n * throwing predicate holds the slot.\n */\nfunction isHeld<T>(policy: BlobFieldPolicy<T>, record: T, now: Date): boolean {\n if (policy.legalHold) {\n try {\n if (policy.legalHold(record)) return true\n } catch {\n return true\n }\n }\n if (policy.retainUntil) {\n try {\n const until = policy.retainUntil(record)\n if (until !== null && until !== undefined) {\n const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(String(until))\n if (!Number.isFinite(t)) return true // fail-closed: unparseable retainUntil holds the slot\n if (t > now.getTime()) return true\n }\n } catch {\n return true\n }\n }\n return false\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/guards/registry.ts
|
|
1
|
+
// src/with-audit/guards/registry.ts
|
|
2
2
|
var GuardRegistry = class {
|
|
3
3
|
_byCollection = /* @__PURE__ */ new Map();
|
|
4
4
|
_amendmentChanges = null;
|
|
@@ -122,4 +122,4 @@ var GuardRegistry = class {
|
|
|
122
122
|
export {
|
|
123
123
|
GuardRegistry
|
|
124
124
|
};
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
125
|
+
//# sourceMappingURL=chunk-YMUGBZN2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-audit/guards/registry.ts"],"sourcesContent":["import type { GuardStrategy, GuardContext, GuardChange } from './types.js'\n\n/**\n * Per-record metadata attached to every entry in an amendment's\n * change-set. Carried in a parallel map alongside `_amendmentChanges`\n * so the public {@link GuardChange} shape (`{ before, after }`) stays\n * clean for invariant authors — the audit ledger reads this side\n * structure to produce the `{ collection, id, vBefore, vAfter }`\n * tuples for the amendment entry.\n *\n * @internal\n */\nexport interface AmendmentChangeMeta {\n readonly id: string\n readonly vBefore: number\n readonly vAfter: number\n}\n\n/**\n * Vault-internal singleton that holds the guard graph and dispatches\n * per-collection guard execution. Owned by `Vault`; not exported.\n *\n * @internal\n */\n// Internal storage alias — guards are heterogeneous in their record type T,\n// so the registry stores them at the upper bound of GuardStrategy's T constraint.\ntype AnyGuard = GuardStrategy<Record<string, unknown>>\ntype AnyChange = GuardChange<Record<string, unknown>>\n\nexport class GuardRegistry {\n private readonly _byCollection = new Map<string, AnyGuard[]>()\n private _amendmentChanges: Map<string, AnyChange[]> | null = null\n private _amendmentMeta: Map<string, AmendmentChangeMeta[]> | null = null\n\n /** Register a guard. Multiple guards per collection are allowed. */\n register<T extends Record<string, unknown>>(spec: GuardStrategy<T>): void {\n const existing = this._byCollection.get(spec.collection)\n if (existing) existing.push(spec as unknown as AnyGuard)\n else this._byCollection.set(spec.collection, [spec as unknown as AnyGuard])\n }\n\n /** All guards registered against `collection` in registration order. */\n guardsFor(collection: string): ReadonlyArray<AnyGuard> {\n return this._byCollection.get(collection) ?? []\n }\n\n /** Per-collection guard counts, for introspection. */\n summary(): { collection: string; count: number }[] {\n return [...this._byCollection.entries()].map(([collection, guards]) => ({\n collection,\n count: guards.length,\n }))\n }\n\n /**\n * Run every guard's `check` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without a `check` skip.\n */\n async runChecks<T>(\n collection: string,\n incoming: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.check) {\n await g.check(\n incoming as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /**\n * Run every guard's `onDelete` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without an `onDelete` skip.\n * Mirrors {@link runChecks} but for the delete path.\n */\n async runOnDelete<T>(\n collection: string,\n existing: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.onDelete) {\n await g.onDelete(\n existing as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /** True if any guard for `collection` declares an `amendment` block. */\n hasAmendment(collection: string): boolean {\n const guards = this._byCollection.get(collection)\n if (!guards) return false\n return guards.some(g => g.amendment !== undefined)\n }\n\n /** Open a new amendment change-collection window. */\n beginAmendment(): void {\n this._amendmentChanges = new Map()\n this._amendmentMeta = new Map()\n }\n\n /** True iff we're currently inside an amendment transaction. */\n isAmendmentActive(): boolean {\n return this._amendmentChanges !== null\n }\n\n /**\n * Record a {before, after} pair for the active amendment. `vBefore`\n * and `vAfter` are stored in a parallel meta structure so the public\n * {@link GuardChange} shape handed to invariant callbacks stays\n * `{ before, after }` only — the audit ledger reads version metadata\n * via {@link consumeMeta}.\n */\n collectChange<T>(\n collection: string,\n id: string,\n before: T | null,\n after: T,\n vBefore = 0,\n vAfter = 0,\n ): void {\n if (this._amendmentChanges === null || this._amendmentMeta === null) {\n throw new Error('GuardRegistry.collectChange called outside an amendment')\n }\n const list = this._amendmentChanges.get(collection)\n const entry = { before, after } as unknown as AnyChange\n if (list) list.push(entry)\n else this._amendmentChanges.set(collection, [entry])\n\n const metaList = this._amendmentMeta.get(collection)\n const metaEntry: AmendmentChangeMeta = { id, vBefore, vAfter }\n if (metaList) metaList.push(metaEntry)\n else this._amendmentMeta.set(collection, [metaEntry])\n }\n\n /**\n * Drain the change-set and close the amendment window. The caller\n * (transaction commit) feeds these to each affected guard's invariant.\n */\n consumeChanges(): ReadonlyMap<string, ReadonlyArray<AnyChange>> {\n const out = this._amendmentChanges ?? new Map()\n this._amendmentChanges = null\n return out\n }\n\n /**\n * Drain the parallel id/version metadata captured during the\n * amendment. Returned as a flat list with `collection` denormalised\n * so the audit ledger can emit one `{ collection, id, vBefore,\n * vAfter }` tuple per record. Must be called AFTER\n * {@link consumeChanges} (or independently) — calling it closes the\n * meta window in the same way.\n */\n consumeMeta(): ReadonlyArray<{ collection: string; id: string; vBefore: number; vAfter: number }> {\n const out: { collection: string; id: string; vBefore: number; vAfter: number }[] = []\n if (this._amendmentMeta) {\n for (const [collection, list] of this._amendmentMeta) {\n for (const m of list) {\n out.push({ collection, id: m.id, vBefore: m.vBefore, vAfter: m.vAfter })\n }\n }\n }\n this._amendmentMeta = null\n return out\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,40 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_FORMAT_VERSION
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
decrypt,
|
|
6
|
-
encrypt
|
|
7
|
-
} from "./chunk-PP6W64Y3.js";
|
|
8
|
-
|
|
9
|
-
// src/persisted-schemas/storage.ts
|
|
10
|
-
var SCHEMAS_COLLECTION = "_schemas";
|
|
11
|
-
async function loadPersistedSchema(store, vault, collection, dek) {
|
|
12
|
-
const envelope = await store.get(vault, SCHEMAS_COLLECTION, collection);
|
|
13
|
-
if (!envelope) return void 0;
|
|
14
|
-
try {
|
|
15
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, dek);
|
|
16
|
-
const parsed = JSON.parse(plaintext);
|
|
17
|
-
if (parsed._noydb_schema !== 1) return void 0;
|
|
18
|
-
return parsed;
|
|
19
|
-
} catch {
|
|
20
|
-
return void 0;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function savePersistedSchema(store, vault, collection, dek, payload) {
|
|
24
|
-
const json = JSON.stringify(payload);
|
|
25
|
-
const { iv, data } = await encrypt(json, dek);
|
|
26
|
-
const prior = await store.get(vault, SCHEMAS_COLLECTION, collection);
|
|
27
|
-
const env = {
|
|
28
|
-
_noydb: NOYDB_FORMAT_VERSION,
|
|
29
|
-
_v: (prior?._v ?? 0) + 1,
|
|
30
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31
|
-
_iv: iv,
|
|
32
|
-
_data: data
|
|
33
|
-
};
|
|
34
|
-
await store.put(vault, SCHEMAS_COLLECTION, collection, env);
|
|
35
|
-
}
|
|
3
|
+
} from "./chunk-5TDJ56JE.js";
|
|
36
4
|
|
|
37
|
-
// src/team/managed-passphrase.ts
|
|
5
|
+
// src/with-party/team/managed-passphrase.ts
|
|
38
6
|
var MemorySealingKeyProvider = class {
|
|
39
7
|
id;
|
|
40
8
|
fingerprint;
|
|
@@ -84,6 +52,49 @@ var MemorySealingKeyProvider = class {
|
|
|
84
52
|
return out;
|
|
85
53
|
}
|
|
86
54
|
};
|
|
55
|
+
async function sealRsaOaepTlv(plaintext, publicKeyPem) {
|
|
56
|
+
const b64 = publicKeyPem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s+/g, "");
|
|
57
|
+
const spki = base64ToBytes(b64);
|
|
58
|
+
const recipientPub = await crypto.subtle.importKey(
|
|
59
|
+
"spki",
|
|
60
|
+
spki,
|
|
61
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
62
|
+
false,
|
|
63
|
+
["encrypt"]
|
|
64
|
+
);
|
|
65
|
+
const cekBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
66
|
+
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["encrypt"]);
|
|
67
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
68
|
+
const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cek, plaintext));
|
|
69
|
+
const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPub, cekBytes));
|
|
70
|
+
cekBytes.fill(0);
|
|
71
|
+
if (wrapped.length !== 256) {
|
|
72
|
+
throw new Error(`sealRsaOaepTlv: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`);
|
|
73
|
+
}
|
|
74
|
+
const out = new Uint8Array(1 + 256 + 12 + ct.length);
|
|
75
|
+
out[0] = 1;
|
|
76
|
+
out.set(wrapped, 1);
|
|
77
|
+
out.set(iv, 1 + 256);
|
|
78
|
+
out.set(ct, 1 + 256 + 12);
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function parseRsaOaepTlv(bytes) {
|
|
82
|
+
if (bytes.length < 1 + 256 + 12 + 16) {
|
|
83
|
+
throw new Error("parseRsaOaepTlv: sealed input too short");
|
|
84
|
+
}
|
|
85
|
+
if (bytes[0] !== 1) {
|
|
86
|
+
throw new Error(`parseRsaOaepTlv: unknown TLV version ${bytes[0]}`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
wrapped: bytes.subarray(1, 1 + 256),
|
|
90
|
+
iv: bytes.subarray(1 + 256, 1 + 256 + 12),
|
|
91
|
+
ct: bytes.subarray(1 + 256 + 12)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function aesGcmOpen(cekBytes, iv, ct) {
|
|
95
|
+
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["decrypt"]);
|
|
96
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cek, ct));
|
|
97
|
+
}
|
|
87
98
|
var MemoryRecipientSealer = class {
|
|
88
99
|
id;
|
|
89
100
|
keypair;
|
|
@@ -112,49 +123,17 @@ var MemoryRecipientSealer = class {
|
|
|
112
123
|
if (typeof pem !== "string") {
|
|
113
124
|
throw new Error("MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string");
|
|
114
125
|
}
|
|
115
|
-
|
|
116
|
-
const spki = base64ToBytes(b64);
|
|
117
|
-
const recipientPub = await crypto.subtle.importKey(
|
|
118
|
-
"spki",
|
|
119
|
-
spki,
|
|
120
|
-
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
121
|
-
false,
|
|
122
|
-
["encrypt"]
|
|
123
|
-
);
|
|
124
|
-
const cekBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
125
|
-
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["encrypt"]);
|
|
126
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
127
|
-
const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cek, plaintext));
|
|
128
|
-
const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPub, cekBytes));
|
|
129
|
-
cekBytes.fill(0);
|
|
130
|
-
if (wrapped.length !== 256) {
|
|
131
|
-
throw new Error(`MemoryRecipientSealer.sealForRecipient: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`);
|
|
132
|
-
}
|
|
133
|
-
const out = new Uint8Array(1 + 256 + 12 + ct.length);
|
|
134
|
-
out[0] = 1;
|
|
135
|
-
out.set(wrapped, 1);
|
|
136
|
-
out.set(iv, 1 + 256);
|
|
137
|
-
out.set(ct, 1 + 256 + 12);
|
|
138
|
-
return out;
|
|
126
|
+
return sealRsaOaepTlv(plaintext, pem);
|
|
139
127
|
}
|
|
140
128
|
async seal(plaintext) {
|
|
141
129
|
const hint = await this.publishRecipientHint();
|
|
142
130
|
return this.sealForRecipient(plaintext, hint);
|
|
143
131
|
}
|
|
144
132
|
async unseal(bytes) {
|
|
145
|
-
|
|
146
|
-
throw new Error("MemoryRecipientSealer.unseal: sealed input too short");
|
|
147
|
-
}
|
|
148
|
-
if (bytes[0] !== 1) {
|
|
149
|
-
throw new Error(`MemoryRecipientSealer.unseal: unknown TLV version ${bytes[0]}`);
|
|
150
|
-
}
|
|
151
|
-
const wrapped = bytes.subarray(1, 1 + 256);
|
|
152
|
-
const iv = bytes.subarray(1 + 256, 1 + 256 + 12);
|
|
153
|
-
const ct = bytes.subarray(1 + 256 + 12);
|
|
133
|
+
const { wrapped, iv, ct } = parseRsaOaepTlv(bytes);
|
|
154
134
|
const { privateKey } = await this.keypair;
|
|
155
135
|
const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, wrapped));
|
|
156
|
-
const
|
|
157
|
-
const pt = new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cek, ct));
|
|
136
|
+
const pt = await aesGcmOpen(cekBytes, iv, ct);
|
|
158
137
|
cekBytes.fill(0);
|
|
159
138
|
return pt;
|
|
160
139
|
}
|
|
@@ -237,10 +216,10 @@ async function resolveManagedSecret(store, vault, provider) {
|
|
|
237
216
|
}
|
|
238
217
|
|
|
239
218
|
export {
|
|
240
|
-
SCHEMAS_COLLECTION,
|
|
241
|
-
loadPersistedSchema,
|
|
242
|
-
savePersistedSchema,
|
|
243
219
|
MemorySealingKeyProvider,
|
|
220
|
+
sealRsaOaepTlv,
|
|
221
|
+
parseRsaOaepTlv,
|
|
222
|
+
aesGcmOpen,
|
|
244
223
|
MemoryRecipientSealer,
|
|
245
224
|
SEALED_PASSPHRASE_RECORD_ID,
|
|
246
225
|
parseSealedEnvelope,
|
|
@@ -248,4 +227,4 @@ export {
|
|
|
248
227
|
loadSealedPassphrase,
|
|
249
228
|
resolveManagedSecret
|
|
250
229
|
};
|
|
251
|
-
//# sourceMappingURL=chunk-
|
|
230
|
+
//# sourceMappingURL=chunk-ZCGAHGUS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-party/team/managed-passphrase.ts"],"sourcesContent":["/**\n * Managed-passphrase mode — rubber-hose-resistant vaults.\n *\n * A vault mode where the passphrase is machine-generated and never\n * exposed to the user, sealed under a developer-provided\n * {@link SealingKeyProvider} (macOS Keychain, Windows Credential\n * Manager, libsecret, AWS KMS, …). The user has no secret to give\n * up to coercion — they can't reveal what they don't know.\n *\n * ## Components in this file\n *\n * - {@link SealingKeyProvider} — the interface concrete providers\n * implement. Provider implementations live OUTSIDE hub (per-\n * platform packages).\n * - {@link MemorySealingKeyProvider} — in-memory test provider; uses\n * a deterministic per-instance \"key\" so two providers with\n * different ids cannot unseal each other's outputs.\n * - {@link RecipientHint} — public material a sender uses to seal\n * plaintext for a specific recipient; published by\n * {@link RecipientSealer.publishRecipientHint} and transported\n * out-of-band to the sender before bundle writes.\n * - {@link RecipientSealer} — interface for asymmetric/granted\n * providers that support recipient-target sealing (RSA-OAEP,\n * cloud-KMS asymmetric, etc.); distinct from self-only\n * {@link SealingKeyProvider} (macOS Keychain, WebAuthn-PRF).\n * - {@link MemoryRecipientSealer} — in-process reference\n * implementation of both `RecipientSealer` and\n * `SealingKeyProvider` using real WebCrypto RSA-OAEP + AES-GCM;\n * safe for tests and same-process sender/recipient scenarios.\n * - {@link loadSealedPassphrase} / {@link saveSealedPassphrase} —\n * plaintext envelope storage at `_meta/sealed-passphrase`.\n * Mirrors the `_meta/handle` and `_meta/public-envelope` AES-\n * GCM-bypassed patterns. The sealing layer (provider's job)\n * is the security boundary; hub doesn't have a key to encrypt\n * with at this layer — that's the whole point of the design.\n * - {@link resolveManagedSecret} — orchestrates the \"generate +\n * seal + persist on first open; unseal on reopen\" flow.\n * Returns the plaintext passphrase string that the rest of the\n * `createNoydb` keyring path consumes.\n *\n * Deferred to follow-ups:\n * - Block `rotate-passphrase` policy gate under managed mode.\n * - Mandatory strong-recovery enforcement.\n * - Recovery flow under managed mode (generates fresh sealed phrase).\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Managed-passphrase mode\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, RecipientSealer } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\n\n/**\n * The contract concrete providers (per-platform key stores) implement\n * to seal and unseal a hub-generated random passphrase. The plaintext\n * passphrase NEVER leaves hub-controlled memory in unsealed form —\n * the provider receives the bytes, returns opaque sealed bytes, and\n * later reverses the operation. Hub treats the sealed bytes as\n * fully opaque.\n *\n * Implementations live OUTSIDE `@noy-db/hub` (separate packages\n * per the issue's \"Concrete providers (live outside hub)\" note):\n *\n * | Platform | Package (TBD) | Backing |\n * |---|---|---|\n * | macOS | `@noy-db/seal-macos-keychain` | Security.framework |\n * | Windows | `@noy-db/seal-wincred` | Credential Manager |\n * | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |\n * | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |\n */\nexport interface SealingKeyProvider {\n /**\n * Non-sensitive identifier disclosed in the persisted envelope.\n * Surfaced to consumers via `loadSealedPassphrase().providerId` so\n * a vault opened with the wrong provider class can detect the\n * mismatch and surface a clear error. NOT secret — fine to log.\n *\n * Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,\n * `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never\n * parses this; it's purely audit metadata.\n */\n readonly id: string\n\n /** Seal raw passphrase bytes. Output bytes are opaque to hub. */\n seal(passphrase: Uint8Array): Promise<Uint8Array>\n\n /**\n * Reverse {@link seal}. MUST throw on tamper, wrong-provider, or\n * any other failure — hub treats a thrown error as \"this provider\n * cannot unlock this vault\" and surfaces it to the caller.\n */\n unseal(sealed: Uint8Array): Promise<Uint8Array>\n}\n\n/**\n * In-memory test provider. NOT secure — uses a deterministic\n * per-instance \"key\" (16-byte SHA-256 of `id`) XOR'd over the\n * passphrase plus a 4-byte provider-id fingerprint prefix. The XOR is\n * sufficient to make different `id` values produce mutually-unsealable\n * outputs (the contract tests for that), but offers ZERO real\n * confidentiality — never use outside tests.\n *\n * Replace with a real platform provider in production.\n */\nexport class MemorySealingKeyProvider implements SealingKeyProvider {\n readonly id: string\n private readonly fingerprint: Uint8Array\n private readonly keyBytes: Uint8Array\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n // Deterministic 4-byte fingerprint of the provider id, prepended\n // to every sealed output so we can detect \"wrong provider\" at\n // unseal time without leaking anything sensitive about either\n // provider's actual key material.\n const encoded = new TextEncoder().encode(opts.id)\n let h = 0\n for (let i = 0; i < encoded.length; i++) {\n h = (h * 31 + encoded[i]!) >>> 0\n }\n this.fingerprint = new Uint8Array([\n (h >>> 24) & 0xff, (h >>> 16) & 0xff, (h >>> 8) & 0xff, h & 0xff,\n ])\n // Deterministic 16-byte \"key\" derived from the id by repeating\n // the fingerprint with offsets. Good enough for the XOR-stream\n // test cipher; never confuse this with real key derivation.\n this.keyBytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n this.keyBytes[i] = this.fingerprint[i % 4]! ^ (i * 17)\n }\n }\n\n async seal(passphrase: Uint8Array): Promise<Uint8Array> {\n const out = new Uint8Array(4 + passphrase.length)\n out.set(this.fingerprint, 0)\n for (let i = 0; i < passphrase.length; i++) {\n out[4 + i] = passphrase[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n\n async unseal(sealed: Uint8Array): Promise<Uint8Array> {\n if (sealed.length < 4) {\n throw new Error('MemorySealingKeyProvider: sealed input too short')\n }\n for (let i = 0; i < 4; i++) {\n if (sealed[i] !== this.fingerprint[i]) {\n throw new Error(\n `MemorySealingKeyProvider(\"${this.id}\"): provider-id mismatch on unseal `\n + '(sealed bytes were produced by a different provider)',\n )\n }\n }\n const body = sealed.subarray(4)\n const out = new Uint8Array(body.length)\n for (let i = 0; i < body.length; i++) {\n out[i] = body[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n}\n\n/**\n * Public material a sender uses to seal-for-this-recipient. Published by\n * a recipient's RecipientSealer; transported to the sender out-of-band\n * (email, S3, in-app message). The sender obtains the hint, supplies it\n * to writePod's sealedCredentials.perUser[userId].hint, and the\n * hub seals each user's credential against it. Per foundation §11.4.\n */\nexport type RecipientHint = {\n readonly v: 1\n /** Recipient's provider id; matches the SealedAutoUnlockEntry.pid they'll unseal under. */\n readonly pid: string\n /** Algorithm the sender uses to produce the seal. Slice 1 ships RSA-OAEP-SHA256 only. */\n readonly alg: 'rsa-oaep-sha256'\n /** Public material — alg-specific. For 'rsa-oaep-sha256': { publicKeyPem: string }. */\n readonly material: Readonly<Record<string, unknown>>\n}\n\n// Hoisted to kernel/types.ts (C3 — enclave self-containment: sealing.ts\n// consumes this as a spine-owned contract type). Re-exported here so existing\n// importers of this module are unaffected.\nexport type { RecipientSealer } from '../../kernel/types.js'\n\n/**\n * Shared RSA-OAEP-SHA256 + AES-GCM seal in the canonical recipient-target\n * TLV wire format. Mints a fresh 32-byte CEK, AES-GCM-encrypts `plaintext`\n * under it, RSA-OAEP-SHA256-wraps the CEK to `publicKeyPem`, and packs:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * This is the single source of truth for the wire format — both\n * {@link MemoryRecipientSealer} and external sealers (e.g. `@noy-db/at-aws-kms`'s\n * asymmetric-KMS recipient sealer) call it so a blob sealed by one unseals\n * by the other. WebCrypto RSA-OAEP/SHA-256 here is wire-compatible with\n * AWS KMS `RSAES_OAEP_SHA_256` (both RSAES-OAEP, SHA-256 hash, MGF1-SHA256,\n * empty label).\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function sealRsaOaepTlv(plaintext: Uint8Array, publicKeyPem: string): Promise<Uint8Array> {\n // Parse PEM → SPKI bytes.\n const b64 = publicKeyPem.replace(/-----BEGIN PUBLIC KEY-----/, '').replace(/-----END PUBLIC KEY-----/, '').replace(/\\s+/g, '')\n const spki = base64ToBytes(b64)\n const recipientPub = await crypto.subtle.importKey(\n 'spki', spki as BufferSource,\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false, ['encrypt'],\n )\n // Mint fresh CEK + IV, AES-GCM encrypt plaintext.\n const cekBytes = crypto.getRandomValues(new Uint8Array(32))\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, plaintext as BufferSource))\n // RSA-OAEP-wrap the CEK bytes.\n const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, recipientPub, cekBytes as BufferSource))\n cekBytes.fill(0)\n if (wrapped.length !== 256) {\n throw new Error(`sealRsaOaepTlv: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`)\n }\n // TLV layout.\n const out = new Uint8Array(1 + 256 + 12 + ct.length)\n out[0] = 0x01\n out.set(wrapped, 1)\n out.set(iv, 1 + 256)\n out.set(ct, 1 + 256 + 12)\n return out\n}\n\n/**\n * Parse a {@link sealRsaOaepTlv} blob into its three segments without\n * decrypting. The `wrapped` CEK is RSA-OAEP-SHA256 ciphertext over a\n * 32-byte CEK — the unwrap step is pluggable: {@link MemoryRecipientSealer}\n * decrypts it with a local RSA private key; `@noy-db/at-aws-kms` hands it to\n * KMS `Decrypt`. After unwrapping, pass the CEK + `iv` + `ct` to\n * {@link aesGcmOpen}.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport function parseRsaOaepTlv(bytes: Uint8Array): { wrapped: Uint8Array; iv: Uint8Array; ct: Uint8Array } {\n if (bytes.length < 1 + 256 + 12 + 16) {\n throw new Error('parseRsaOaepTlv: sealed input too short')\n }\n if (bytes[0] !== 0x01) {\n throw new Error(`parseRsaOaepTlv: unknown TLV version ${bytes[0]}`)\n }\n return {\n wrapped: bytes.subarray(1, 1 + 256),\n iv: bytes.subarray(1 + 256, 1 + 256 + 12),\n ct: bytes.subarray(1 + 256 + 12),\n }\n}\n\n/**\n * AES-GCM-decrypt the `ct` segment of a {@link parseRsaOaepTlv} result under\n * the unwrapped 32-byte CEK and its `iv`. Throws on a bad tag (tamper) — the\n * same authenticated-decryption guarantee the TLV relies on.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function aesGcmOpen(cekBytes: Uint8Array, iv: Uint8Array, ct: Uint8Array): Promise<Uint8Array> {\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['decrypt'])\n return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, ct as BufferSource))\n}\n\n/**\n * Reference implementation of `RecipientSealer` + `SealingKeyProvider`.\n * Uses WebCrypto RSA-OAEP-SHA256 (2048-bit) to wrap a fresh 32-byte\n * AES-GCM CEK, AES-GCM-encrypts plaintext under it, and packs the\n * result into a self-describing TLV:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * Implements BOTH interfaces. `seal(plaintext)` (self-target) is just\n * `sealForRecipient(plaintext, this own hint)` — same TLV. Convenient\n * for tests where one provider plays both ends. Real cloud providers\n * (`at-aws-kms`, etc.) will pick their own internal layouts; the only\n * contract is round-trip identity.\n *\n * SAFE for production within its scope — the cryptography is real\n * (RSA-OAEP + AES-GCM via WebCrypto), but the keypair lives in-process\n * and is regenerated on every construction. Not suitable as a managed\n * keychain; use it for tests and for shipping bundles where the\n * recipient instance lives in the same process as the sender (rare).\n */\nexport class MemoryRecipientSealer implements SealingKeyProvider, RecipientSealer {\n readonly id: string\n private readonly keypair: Promise<CryptoKeyPair>\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n this.keypair = crypto.subtle.generateKey(\n { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },\n true,\n ['encrypt', 'decrypt'],\n )\n }\n\n async publishRecipientHint(): Promise<RecipientHint> {\n const { publicKey } = await this.keypair\n const spki = await crypto.subtle.exportKey('spki', publicKey)\n const pem = '-----BEGIN PUBLIC KEY-----\\n'\n + bytesToBase64(new Uint8Array(spki)).match(/.{1,64}/g)!.join('\\n')\n + '\\n-----END PUBLIC KEY-----\\n'\n return { v: 1, pid: this.id, alg: 'rsa-oaep-sha256', material: { publicKeyPem: pem } }\n }\n\n async sealForRecipient(plaintext: Uint8Array, hint: RecipientHint): Promise<Uint8Array> {\n if (hint.v !== 1) {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.v ${String(hint.v)} (expected 1)`)\n }\n if (hint.alg !== 'rsa-oaep-sha256') {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.alg '${String(hint.alg)}' (expected 'rsa-oaep-sha256')`)\n }\n const pem = hint.material['publicKeyPem']\n if (typeof pem !== 'string') {\n throw new Error('MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string')\n }\n return sealRsaOaepTlv(plaintext, pem)\n }\n\n async seal(plaintext: Uint8Array): Promise<Uint8Array> {\n const hint = await this.publishRecipientHint()\n return this.sealForRecipient(plaintext, hint)\n }\n\n async unseal(bytes: Uint8Array): Promise<Uint8Array> {\n const { wrapped, iv, ct } = parseRsaOaepTlv(bytes)\n const { privateKey } = await this.keypair\n // Local RSA-OAEP-SHA256 unwrap of the CEK — the pluggable step external\n // sealers replace with KMS Decrypt.\n const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, wrapped as BufferSource))\n const pt = await aesGcmOpen(cekBytes, iv, ct)\n cekBytes.fill(0)\n return pt\n }\n}\n\n// ─── Persisted envelope ────────────────────────────────────────────────\n\n/** Reserved id for the managed-passphrase envelope under `_meta`. */\nexport const SEALED_PASSPHRASE_RECORD_ID = 'sealed-passphrase' as const\n\n/** Plaintext payload stored inside the `_meta/sealed-passphrase` envelope. */\nexport interface SealedPassphrase {\n readonly _noydb_sealed: 1\n readonly providerId: string\n /** Sealed bytes. Base64-encoded on the wire; decoded on load. */\n readonly sealed: Uint8Array\n}\n\n/**\n * Wire-format envelope persisted at `_meta/sealed-passphrase` for\n * managed-mode vaults. The provider produces raw sealed bytes via\n * {@link SealingKeyProvider.seal}; this wrapper carries the dispatch\n * metadata hub needs to pick the right provider on the unseal path.\n *\n * Stability boundary: once shipped, the wire format only grows by\n * adding optional fields. See the at-* sealing dimension foundation\n * doc, §11.9.1.\n *\n * v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.\n *\n * Legacy shape (earlier releases): `{ _noydb_sealed: 1, providerId, sealed }`\n * — accepted on read for backwards compatibility; never produced on\n * write going forward.\n */\nexport interface SealedEnvelope {\n /** Envelope schema version. v1 is the current shape. */\n readonly v: 1\n /** Magic marker for forensics + legacy-shape detection. */\n readonly _noydb_sealed: 1\n /** Matches the producing provider's `.id`. Dispatch key on unseal. */\n readonly pid: string\n /** Sealed bytes from the provider, base64-encoded on the wire. */\n readonly payload: string\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Parse a `_meta/sealed-passphrase` `_data` JSON string into the\n * in-memory {@link SealedPassphrase} representation. Accepts both:\n *\n * 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —\n * the current shape.\n * 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —\n * read-only; never written\n * going forward.\n *\n * Returns `undefined` for any input that doesn't match either shape,\n * so callers can fall back to \"no managed-mode envelope present.\"\n *\n * @internal — exported only for the migration safety-net test suite.\n */\nexport function parseSealedEnvelope(raw: unknown): SealedPassphrase | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined\n const r = raw as Record<string, unknown>\n if (r._noydb_sealed !== 1) return undefined\n\n // v1 shape — preferred.\n if (\n r.v === 1\n && typeof r.pid === 'string'\n && typeof r.payload === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.pid,\n sealed: base64ToBytes(r.payload),\n }\n }\n\n // Legacy shape — earlier releases. Accept on read for compat.\n if (\n typeof r.providerId === 'string'\n && typeof r.sealed === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.providerId,\n sealed: base64ToBytes(r.sealed),\n }\n }\n\n return undefined\n}\n\nexport async function saveSealedPassphrase(\n store: NoydbStore,\n vault: string,\n payload: { readonly providerId: string; readonly sealed: Uint8Array },\n): Promise<void> {\n const persisted: SealedEnvelope = {\n v: 1,\n _noydb_sealed: 1,\n pid: payload.providerId,\n payload: bytesToBase64(payload.sealed),\n }\n const prior = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — the sealing layer is the security boundary.\n _iv: '',\n _data: JSON.stringify(persisted),\n }\n await store.put(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID, env)\n}\n\nexport async function loadSealedPassphrase(\n store: NoydbStore,\n vault: string,\n): Promise<SealedPassphrase | undefined> {\n const envelope = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n if (!envelope) return undefined\n try {\n return parseSealedEnvelope(JSON.parse(envelope._data))\n } catch {\n return undefined\n }\n}\n\n// ─── createNoydb orchestration ─────────────────────────────────────────\n\n/**\n * Resolve the effective plaintext passphrase string for a managed-mode\n * vault. Two paths:\n *\n * 1. **First open (no envelope persisted):** generate a 256-bit random\n * via `crypto.getRandomValues`, base64-encode for use as a\n * passphrase string, seal the underlying bytes under the\n * provider, persist `_meta/sealed-passphrase`, return the\n * base64 string.\n *\n * 2. **Reopen (envelope exists):** read + unseal + decode → return.\n * A different provider whose `seal` output disagrees on the\n * stored bytes throws here, surfaced as a clear error.\n *\n * The returned string is the same shape that `secret:` would take in\n * standard mode — the rest of the keyring path consumes it\n * unchanged.\n *\n * @internal — called from `createNoydb` / `getKeyringInternal`.\n */\nexport async function resolveManagedSecret(\n store: NoydbStore,\n vault: string,\n provider: SealingKeyProvider,\n): Promise<string> {\n const existing = await loadSealedPassphrase(store, vault)\n if (existing) {\n if (existing.providerId !== provider.id) {\n throw new Error(\n `Managed-mode vault \"${vault}\" was sealed under provider id `\n + `\"${existing.providerId}\" but the current SealingKeyProvider is `\n + `\"${provider.id}\". Pass the same provider that originally enrolled `\n + 'the vault, or treat this as a fresh enrollment and clear '\n + '`_meta/sealed-passphrase` first.',\n )\n }\n const plaintext = await provider.unseal(existing.sealed)\n return bytesToBase64(plaintext)\n }\n\n // First open: mint a 256-bit random, seal, persist.\n const random = new Uint8Array(32)\n globalThis.crypto.getRandomValues(random)\n const sealed = await provider.seal(random)\n await saveSealedPassphrase(store, vault, { providerId: provider.id, sealed })\n return bytesToBase64(random)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/with-pod/ulid.ts
|
|
2
2
|
var CROCKFORD_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
3
|
function encodeBase32(value, length) {
|
|
4
4
|
let out = "";
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
generateULID,
|
|
30
30
|
isULID
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-ZJADGNYF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-pod/ulid.ts"],"sourcesContent":["/**\n * Minimal ULID generator — zero dependencies, Web Crypto API only.\n *\n *. Used by the bundle writer to generate stable opaque\n * handles for `.noydb` containers.\n *\n * **What's a ULID?** A 128-bit identifier encoded as 26 Crockford\n * base32 characters. Layout:\n *\n * ```\n * 01HYABCDEFGHJKMNPQRSTVWXYZ\n * |--------||---------------|\n * 48-bit 80-bit\n * timestamp randomness\n * ```\n *\n * The first 10 chars encode a millisecond Unix timestamp (so ULIDs\n * sort lexicographically by creation time), and the remaining 16\n * chars are random. Crockford base32 omits I/L/O/U to avoid\n * ambiguity in handwriting and URLs.\n *\n * **Why hand-roll instead of pulling in `ulid`?** The package adds\n * a dep, the implementation is ~30 lines, and the bundle module\n * is the only consumer. Adding `ulid` would also drag in its own\n * crypto polyfill that we don't need on Node 18+ or modern\n * browsers.\n *\n * **Privacy consideration:** the timestamp prefix is observable in\n * the bundle header. This is a deliberate trade-off:\n * - Pro: lexicographic sortability lets bundle adapters list\n * newest-first without an extra index.\n * - Con: a casual observer can read the bundle's creation time\n * from the handle. They cannot read it from any OTHER field\n * (the header explicitly forbids `_exported_at`), and a\n * creation timestamp is the same kind of metadata that\n * filesystem mtime would already expose for a downloaded\n * bundle. The leak is therefore equivalent to what's already\n * visible from the file's mtime — not a new exposure.\n *\n * If a future use case needs timestamp-free handles, a v2 of the\n * format could specify \"use the random portion only\" without a\n * format break — `validateBundleHeader` only checks the regex\n * shape, not the encoded timestamp.\n */\n\n/**\n * Crockford base32 alphabet — omits I, L, O, U to avoid handwriting\n * and URL-encoding ambiguity. 32 characters covering 5 bits each.\n */\nconst CROCKFORD_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/**\n * Encode a non-negative integer as a fixed-width Crockford base32\n * string. The width is fixed (not the natural log32 length) so\n * leading zeros are preserved — that's required for the timestamp\n * prefix to remain lexicographically sortable.\n *\n * Used twice: once for the 48-bit timestamp portion (10 chars) and\n * once for each 40-bit half of the randomness (8 chars × 2).\n */\nfunction encodeBase32(value: number, length: number): string {\n let out = ''\n let v = value\n for (let i = 0; i < length; i++) {\n out = CROCKFORD_ALPHABET[v % 32]! + out\n v = Math.floor(v / 32)\n }\n return out\n}\n\n/**\n * Generate a fresh ULID. Uses `crypto.getRandomValues` for the\n * randomness portion — same Web Crypto API the rest of the\n * codebase uses for IVs and salt.\n *\n * Returns a 26-character string. Calling twice in the same\n * millisecond produces two distinct ULIDs (the random portion\n * differs); ULIDs from the same millisecond are NOT guaranteed\n * to be monotonically ordered relative to each other, only\n * relative to ULIDs from a different millisecond. The bundle\n * format never relies on intra-millisecond ordering.\n */\nexport function generateULID(): string {\n const now = Date.now()\n\n // 48-bit timestamp → 10 Crockford base32 characters.\n // JavaScript's max safe integer is 2^53 - 1; Date.now() is well\n // within that range until the year ~285,000 AD. Splitting into\n // high and low 24-bit halves keeps every intermediate value\n // inside the safe-integer range and avoids any ambiguity in the\n // base32 encoder above.\n const timestampHigh = Math.floor(now / 0x1000000) // top 24 bits\n const timestampLow = now & 0xffffff // bottom 24 bits\n const tsPart =\n encodeBase32(timestampHigh, 5) + encodeBase32(timestampLow, 5)\n\n // 80-bit randomness → 16 Crockford base32 characters. Split into\n // two 40-bit halves so each fits in JavaScript's safe-integer\n // range (53 bits) and the base32 encoder doesn't have to deal\n // with bigints.\n const randBytes = new Uint8Array(10)\n crypto.getRandomValues(randBytes)\n\n // First 5 bytes (40 bits) → 8 Crockford base32 characters.\n // Reconstruct the 40-bit integer from bytes in big-endian order.\n // Multiplication by 2^32 (instead of bit-shift) avoids JavaScript's\n // 32-bit integer cast on the high byte.\n const rand1 =\n randBytes[0]! * 2 ** 32 +\n (randBytes[1]! << 24 >>> 0) +\n (randBytes[2]! << 16) +\n (randBytes[3]! << 8) +\n randBytes[4]!\n // Same for the second 5 bytes.\n const rand2 =\n randBytes[5]! * 2 ** 32 +\n (randBytes[6]! << 24 >>> 0) +\n (randBytes[7]! << 16) +\n (randBytes[8]! << 8) +\n randBytes[9]!\n const randPart = encodeBase32(rand1, 8) + encodeBase32(rand2, 8)\n\n return tsPart + randPart\n}\n\n/**\n * Validate that a string is a syntactically well-formed ULID. Used\n * by the bundle header validator. Does NOT verify that the\n * timestamp portion decodes to a sensible date — the format only\n * cares about the encoding shape.\n */\nexport function isULID(value: string): boolean {\n return /^[0-9A-HJKMNP-TV-Z]{26}$/.test(value)\n}\n"],"mappings":";AAiDA,IAAM,qBAAqB;AAW3B,SAAS,aAAa,OAAe,QAAwB;AAC3D,MAAI,MAAM;AACV,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,mBAAmB,IAAI,EAAE,IAAK;AACpC,QAAI,KAAK,MAAM,IAAI,EAAE;AAAA,EACvB;AACA,SAAO;AACT;AAcO,SAAS,eAAuB;AACrC,QAAM,MAAM,KAAK,IAAI;AAQrB,QAAM,gBAAgB,KAAK,MAAM,MAAM,QAAS;AAChD,QAAM,eAAe,MAAM;AAC3B,QAAM,SACJ,aAAa,eAAe,CAAC,IAAI,aAAa,cAAc,CAAC;AAM/D,QAAM,YAAY,IAAI,WAAW,EAAE;AACnC,SAAO,gBAAgB,SAAS;AAMhC,QAAM,QACJ,UAAU,CAAC,IAAK,KAAK,MACpB,UAAU,CAAC,KAAM,OAAO,MACxB,UAAU,CAAC,KAAM,OACjB,UAAU,CAAC,KAAM,KAClB,UAAU,CAAC;AAEb,QAAM,QACJ,UAAU,CAAC,IAAK,KAAK,MACpB,UAAU,CAAC,KAAM,OAAO,MACxB,UAAU,CAAC,KAAM,OACjB,UAAU,CAAC,KAAM,KAClB,UAAU,CAAC;AACb,QAAM,WAAW,aAAa,OAAO,CAAC,IAAI,aAAa,OAAO,CAAC;AAE/D,SAAO,SAAS;AAClB;AAQO,SAAS,OAAO,OAAwB;AAC7C,SAAO,2BAA2B,KAAK,KAAK;AAC9C;","names":[]}
|