@noy-db/hub 0.6.0-pre.2 → 0.6.0-pre.21
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/CHANGELOG.md +5234 -0
- package/codemods/0.4.0-pre.json +699 -0
- package/codemods/0.6.0-pre.json +211 -0
- package/dist/api-EYNT6KGS.js +20 -0
- package/dist/attestation/index.js +19 -19
- package/dist/{backup-YYBE5RPC.js → backup-FSC36VWC.js} +36 -18
- package/dist/backup-FSC36VWC.js.map +1 -0
- package/dist/blobs/index.js +13 -13
- package/dist/broker/index.js +12 -12
- package/dist/cargo/index.js +33 -33
- package/dist/{chunk-35EBKWQU.js → chunk-2LFOMUEA.js} +4 -4
- package/dist/{chunk-OF3L2SD6.js → chunk-32B7VCKU.js} +2 -2
- package/dist/{chunk-MSWYQLMQ.js → chunk-3B6S6RSI.js} +2 -2
- package/dist/{chunk-PJNTR2NV.js → chunk-3ENNPCIW.js} +7 -10
- package/dist/chunk-3ENNPCIW.js.map +1 -0
- package/dist/{chunk-7F4DBNSP.js → chunk-3O72ZD2E.js} +3 -3
- package/dist/{chunk-FNSMZQS4.js → chunk-3PMFMUFX.js} +2 -2
- package/dist/{chunk-WQDTCJ6W.js → chunk-45YHJ3GV.js} +11 -3
- package/dist/{chunk-WQDTCJ6W.js.map → chunk-45YHJ3GV.js.map} +1 -1
- package/dist/{chunk-NIVEDYJZ.js → chunk-4A34KWOU.js} +31 -49
- package/dist/chunk-4A34KWOU.js.map +1 -0
- package/dist/{chunk-ZJADGNYF.js → chunk-4AOERLTV.js} +1 -1
- package/dist/chunk-4AOERLTV.js.map +1 -0
- package/dist/{chunk-EDXAQ6JI.js → chunk-4N5Q23KV.js} +87 -34
- package/dist/chunk-4N5Q23KV.js.map +1 -0
- package/dist/{chunk-VV4NSB7P.js → chunk-4OQZNXQG.js} +15 -24
- package/dist/chunk-4OQZNXQG.js.map +1 -0
- package/dist/{chunk-FAXZQ5UF.js → chunk-4RAS6HN6.js} +2 -2
- package/dist/{chunk-C2OOEM57.js → chunk-4YL2LMXK.js} +92 -35
- package/dist/chunk-4YL2LMXK.js.map +1 -0
- package/dist/{chunk-EP7VPR7Y.js → chunk-5Q7AJ7DN.js} +11 -8
- package/dist/chunk-5Q7AJ7DN.js.map +1 -0
- package/dist/{chunk-VMCCGUQB.js → chunk-6JPGVL7X.js} +2 -2
- package/dist/{chunk-PRDRGL73.js → chunk-6KTVJ5ZZ.js} +17 -14
- package/dist/chunk-6KTVJ5ZZ.js.map +1 -0
- package/dist/{chunk-4MC5JRRX.js → chunk-6WO62AJW.js} +14 -15
- package/dist/chunk-6WO62AJW.js.map +1 -0
- package/dist/{chunk-WLYIROCJ.js → chunk-6YD5SYQS.js} +2 -2
- package/dist/{chunk-YAWK6AWF.js → chunk-7KAE7CQ2.js} +2 -2
- package/dist/{chunk-MWKDOHEF.js → chunk-7LNE2N2V.js} +17 -10
- package/dist/chunk-7LNE2N2V.js.map +1 -0
- package/dist/{chunk-5PR33AC4.js → chunk-7PJ3BKHS.js} +20 -17
- package/dist/chunk-7PJ3BKHS.js.map +1 -0
- package/dist/{chunk-JXIDGFKS.js → chunk-A4TOGAUW.js} +4 -2
- package/dist/chunk-A4TOGAUW.js.map +1 -0
- package/dist/{chunk-VCETL77O.js → chunk-AU3AUJIZ.js} +27 -7
- package/dist/chunk-AU3AUJIZ.js.map +1 -0
- package/dist/{chunk-NKH7GSIH.js → chunk-AUPSEJ7O.js} +4 -4
- package/dist/{chunk-CCZJJ4I2.js → chunk-B6IYATME.js} +4 -4
- package/dist/{chunk-WYZ5MYUA.js → chunk-BAHOV5I5.js} +44 -42
- package/dist/chunk-BAHOV5I5.js.map +1 -0
- package/dist/{chunk-G5APARUV.js → chunk-BEZDLIDG.js} +13 -15
- package/dist/chunk-BEZDLIDG.js.map +1 -0
- package/dist/{chunk-ERB6NDDW.js → chunk-CAGI4LLJ.js} +427 -72
- package/dist/chunk-CAGI4LLJ.js.map +1 -0
- package/dist/{chunk-4UVDI4XE.js → chunk-CLAHBPOI.js} +4 -4
- package/dist/{chunk-HNGSV2YC.js → chunk-CWM4GQD2.js} +39 -10
- package/dist/chunk-CWM4GQD2.js.map +1 -0
- package/dist/{chunk-HVQGCKY4.js → chunk-D3R7Q4QK.js} +2 -2
- package/dist/{chunk-I4F6PKUK.js → chunk-DIZ6HM7Q.js} +16 -17
- package/dist/chunk-DIZ6HM7Q.js.map +1 -0
- package/dist/{chunk-ZNACADFG.js → chunk-DVRZYAGT.js} +4 -4
- package/dist/chunk-DVRZYAGT.js.map +1 -0
- package/dist/{chunk-P7OG2ETF.js → chunk-DXKLLKME.js} +3 -3
- package/dist/{chunk-TDJPS3T2.js → chunk-E5SC6ENM.js} +24 -19
- package/dist/chunk-E5SC6ENM.js.map +1 -0
- package/dist/{chunk-26JMGSSC.js → chunk-EES37S2V.js} +4 -4
- package/dist/chunk-EES37S2V.js.map +1 -0
- package/dist/{chunk-JIUVJ4M7.js → chunk-EKEIEJL6.js} +2 -2
- package/dist/{chunk-GASWQ3XG.js → chunk-ESKIAXO2.js} +15 -16
- package/dist/chunk-ESKIAXO2.js.map +1 -0
- package/dist/{chunk-TOAIYQJR.js → chunk-FFSQFH6D.js} +7 -10
- package/dist/chunk-FFSQFH6D.js.map +1 -0
- package/dist/{chunk-DG27IUVI.js → chunk-FS7NTGCJ.js} +2 -2
- package/dist/{chunk-V5KYPBUR.js → chunk-FW7NGYLT.js} +11 -14
- package/dist/chunk-FW7NGYLT.js.map +1 -0
- package/dist/{chunk-52QYGZHH.js → chunk-GBBDFE6C.js} +2 -2
- package/dist/chunk-GBBDFE6C.js.map +1 -0
- package/dist/{chunk-HXGUIPQB.js → chunk-GQXFOAYZ.js} +3 -3
- package/dist/{chunk-BTVYJWSN.js → chunk-GTHVPVAR.js} +10 -10
- package/dist/{chunk-DMQCLZ4Z.js → chunk-GWO2IDAB.js} +2 -2
- package/dist/{chunk-6HFM7EIT.js → chunk-H3G4I3PN.js} +2 -2
- package/dist/chunk-HK67PX4J.js +27 -0
- package/dist/chunk-HK67PX4J.js.map +1 -0
- package/dist/{chunk-E7OBCBXS.js → chunk-HQSPF5IY.js} +22 -18
- package/dist/chunk-HQSPF5IY.js.map +1 -0
- package/dist/{chunk-VJFMXYKP.js → chunk-HR5R5JOM.js} +2 -2
- package/dist/{chunk-FDX7OWWI.js → chunk-HYYVT2KZ.js} +2 -2
- package/dist/{chunk-JRCYZEHU.js → chunk-I6QKB7MT.js} +2 -2
- package/dist/{chunk-MZ5ZFO2D.js → chunk-ICDHUUMQ.js} +84 -112
- package/dist/chunk-ICDHUUMQ.js.map +1 -0
- package/dist/{chunk-W5W6KHN2.js → chunk-IGNJOJEP.js} +1 -1
- package/dist/chunk-IGNJOJEP.js.map +1 -0
- package/dist/{chunk-KUDZVJKQ.js → chunk-IJ46VCWD.js} +2 -2
- package/dist/{chunk-4HDYQL3J.js → chunk-INMHEMXX.js} +2 -2
- package/dist/{chunk-66W5OOCX.js → chunk-JLYQQNY4.js} +52 -65
- package/dist/chunk-JLYQQNY4.js.map +1 -0
- package/dist/{chunk-UHXCLPGC.js → chunk-JNMTN5D5.js} +2 -2
- package/dist/chunk-JNMTN5D5.js.map +1 -0
- package/dist/{chunk-I4ZAO7F7.js → chunk-JWMBG5S3.js} +7 -11
- package/dist/chunk-JWMBG5S3.js.map +1 -0
- package/dist/chunk-KRVS7YWY.js +123 -0
- package/dist/chunk-KRVS7YWY.js.map +1 -0
- package/dist/{chunk-AR2JDFAE.js → chunk-KV6JM2IA.js} +2 -2
- package/dist/{chunk-PVU7LW5L.js → chunk-KX57HODD.js} +6 -6
- package/dist/{chunk-PY7CI6L5.js → chunk-LGFTOU27.js} +553 -275
- package/dist/chunk-LGFTOU27.js.map +1 -0
- package/dist/chunk-LN7P6MZB.js +190 -0
- package/dist/chunk-LN7P6MZB.js.map +1 -0
- package/dist/{chunk-UIBGXRDD.js → chunk-LX46B56Q.js} +25 -31
- package/dist/chunk-LX46B56Q.js.map +1 -0
- package/dist/{chunk-CEJWMBXO.js → chunk-LXECF2ZR.js} +2 -2
- package/dist/{chunk-7VHY5E3X.js → chunk-MSLPDFK2.js} +7 -7
- package/dist/{chunk-I6SDX7WX.js → chunk-MZTUTEHP.js} +16 -22
- package/dist/chunk-MZTUTEHP.js.map +1 -0
- package/dist/{chunk-VRA4Q7DA.js → chunk-NCCEDL2K.js} +55 -48
- package/dist/chunk-NCCEDL2K.js.map +1 -0
- package/dist/{chunk-HNHCQ556.js → chunk-NR2SUJ3H.js} +2 -2
- package/dist/{chunk-AHLJSVRL.js → chunk-NW4TY6VC.js} +5 -7
- package/dist/chunk-NW4TY6VC.js.map +1 -0
- package/dist/{chunk-RKKORO4M.js → chunk-O742QBZZ.js} +10 -10
- package/dist/chunk-O742QBZZ.js.map +1 -0
- package/dist/{chunk-VN45YLWM.js → chunk-OHOKBMTM.js} +343 -132
- package/dist/chunk-OHOKBMTM.js.map +1 -0
- package/dist/{chunk-W3TVWF6N.js → chunk-OIBEJN5H.js} +2 -2
- package/dist/{chunk-QD724WDJ.js → chunk-OMYCSTXL.js} +4 -4
- package/dist/{chunk-Z45YRGFO.js → chunk-ONVD6GBY.js} +2 -2
- package/dist/{chunk-TAGFGKHP.js → chunk-OTHHO3KI.js} +7 -10
- package/dist/chunk-OTHHO3KI.js.map +1 -0
- package/dist/{chunk-3737A6EA.js → chunk-P2MR5UNE.js} +75 -18
- package/dist/chunk-P2MR5UNE.js.map +1 -0
- package/dist/chunk-PENPHA45.js +66 -0
- package/dist/chunk-PENPHA45.js.map +1 -0
- package/dist/{chunk-3FSX6LDG.js → chunk-PPCOKVFA.js} +8 -3
- package/dist/{chunk-3FSX6LDG.js.map → chunk-PPCOKVFA.js.map} +1 -1
- package/dist/{chunk-VOQW5LAM.js → chunk-QSIEL6TL.js} +102 -71
- package/dist/chunk-QSIEL6TL.js.map +1 -0
- package/dist/chunk-RFO4UXKQ.js +201 -0
- package/dist/chunk-RFO4UXKQ.js.map +1 -0
- package/dist/{chunk-KCQX65FU.js → chunk-RKCRPPNB.js} +3 -3
- package/dist/{chunk-7TSYBD3E.js → chunk-RSXQNFGO.js} +2 -2
- package/dist/{chunk-SZ2K3MHL.js → chunk-S56VYR4Q.js} +8 -11
- package/dist/{chunk-SZ2K3MHL.js.map → chunk-S56VYR4Q.js.map} +1 -1
- package/dist/{chunk-7NXHMW6Y.js → chunk-SYS46BL7.js} +2 -2
- package/dist/{chunk-55LKVALY.js → chunk-T7IR4N2S.js} +1 -3
- package/dist/chunk-T7IR4N2S.js.map +1 -0
- package/dist/{chunk-A67356IR.js → chunk-T7JGWTES.js} +33 -8
- package/dist/chunk-T7JGWTES.js.map +1 -0
- package/dist/{chunk-WHJKIZS7.js → chunk-TDSN2O2V.js} +27 -149
- package/dist/chunk-TDSN2O2V.js.map +1 -0
- package/dist/{chunk-747LRSOO.js → chunk-TMBSPLUG.js} +2 -2
- package/dist/{chunk-LWSJG3LI.js → chunk-TQPASLY4.js} +7 -3
- package/dist/chunk-TQPASLY4.js.map +1 -0
- package/dist/{chunk-WKPJXWKR.js → chunk-TX5MKAHA.js} +3 -3
- package/dist/{chunk-HFQ6LGIO.js → chunk-U27Y4DJZ.js} +12 -10
- package/dist/{chunk-HFQ6LGIO.js.map → chunk-U27Y4DJZ.js.map} +1 -1
- package/dist/{chunk-E6427JU2.js → chunk-VVSOPRUE.js} +18 -2
- package/dist/chunk-VVSOPRUE.js.map +1 -0
- package/dist/{chunk-7G6VDWXB.js → chunk-WVE6YKMX.js} +3 -3
- package/dist/chunk-WVE6YKMX.js.map +1 -0
- package/dist/{chunk-GLKDWWNO.js → chunk-YALD3SST.js} +16 -14
- package/dist/chunk-YALD3SST.js.map +1 -0
- package/dist/{chunk-NJDQ4YSV.js → chunk-YKXZHUOW.js} +4 -4
- package/dist/{chunk-VW6ZZGCG.js → chunk-ZLHHE5XZ.js} +3 -3
- package/dist/{chunk-FUHKW252.js → chunk-ZZXLB6AS.js} +2 -2
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-FTOX7Q5L.js → classified-marker-NA6HUV33.js} +3 -3
- package/dist/collection-facade-VBM35WAH.js +45 -0
- package/dist/{computed-6G5M3UPH.js → computed-GSTLWSY5.js} +3 -3
- package/dist/consent/index.js +10 -10
- package/dist/cover/index.js +10 -3
- package/dist/custody/index.js +11 -11
- package/dist/{dead-filter-ENRIOQIE.js → dead-filter-UALP5DJX.js} +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/{delegation-SSEPLXXO.js → delegation-JLNXM7KH.js} +24 -23
- package/dist/delegation-JLNXM7KH.js.map +1 -0
- package/dist/derivations/index.js +12 -12
- package/dist/derive-YUO3TQYN.js +22 -0
- package/dist/directory/index.js +10 -10
- package/dist/{dispatch-RE74SH6L.js → dispatch-4ZHLSFZQ.js} +5 -5
- package/dist/{dispatch-BDVGYC6X.js → dispatch-77CB75KB.js} +18 -18
- package/dist/{enclave-52VUXNRL.js → enclave-O4NOROCJ.js} +27 -9
- package/dist/executor-CZDXBXKE.js +29 -0
- package/dist/executor-DVJAWN3B.js +9 -0
- package/dist/executor-VRLG4TQJ.js +9 -0
- package/dist/export-accessible-UXSMG7AB.js +23 -0
- package/dist/extract-partition-TVY7AMKH.js +38 -0
- package/dist/{fanout-sidecar-DTBIDOWF.js → fanout-sidecar-NLQ7QAYW.js} +18 -27
- package/dist/fanout-sidecar-NLQ7QAYW.js.map +1 -0
- package/dist/find-WSARMJBW.js +11 -0
- package/dist/forget/index.js +9 -9
- package/dist/guards/index.js +7 -7
- package/dist/history/index.js +23 -20
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +14 -14
- package/dist/index.d.ts +33 -15
- package/dist/index.js +187 -191
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +2 -2
- package/dist/introspection/index.js +11 -11
- package/dist/issue-3JNJNPT3.js +19 -0
- package/dist/kernel/constants.d.ts +18 -0
- package/dist/kernel/enclave/crypto.d.ts +24 -4
- package/dist/kernel/enclave/index.d.ts +9 -2
- package/dist/kernel/enclave/record-aad.d.ts +131 -0
- package/dist/kernel/enclave/record-envelope.d.ts +76 -0
- package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
- package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
- package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
- package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +88 -0
- package/dist/kernel/enclave/record-keys/rekey.d.ts +68 -0
- package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -2
- package/dist/kernel/errors.d.ts +71 -12
- package/dist/kernel/merge-authority.d.ts +25 -0
- package/dist/kernel/noydb.d.ts +27 -4
- package/dist/kernel/query/builder.d.ts +15 -2
- package/dist/kernel/query/join.d.ts +27 -0
- package/dist/kernel/sync-policy.d.ts +0 -2
- package/dist/kernel/tier-visibility.d.ts +15 -0
- package/dist/kernel/types.d.ts +145 -12
- package/dist/kernel/vault.d.ts +22 -18
- package/dist/kernel/via/index.d.ts +11 -2
- package/dist/{ledger-4WJL66BW.js → ledger-MVPNG5OS.js} +11 -11
- package/dist/liberate-4LOJOLDT.js +24 -0
- package/dist/{link-set-N4ZPV6E6.js → link-set-3POMQR3I.js} +11 -11
- package/dist/{managed-secret-OTHS64IF.js → managed-secret-IARIJJ7Q.js} +11 -3
- package/dist/materialized-views/index.js +22 -21
- package/dist/migrate-cek-XM2ETTZW.js +19 -0
- package/dist/migrate-cek-XM2ETTZW.js.map +1 -0
- package/dist/money/index.js +12 -8
- package/dist/noydb-IRILUDED.js +84 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +22 -12
- package/dist/pod/index.js +75 -86
- package/dist/pod-handle-MELERCKU.js +41 -0
- package/dist/pod-handle-MELERCKU.js.map +1 -0
- package/dist/policy/index.js +11 -4
- package/dist/port/to/index.d.ts +4 -4
- package/dist/port/to/locator.d.ts +54 -8
- package/dist/port/with/i18n-strategy.d.ts +4 -1
- package/dist/port/with/lookup-strategy.d.ts +4 -1
- package/dist/port/with/merge-authority.d.ts +52 -0
- package/dist/port/with/service-bus.d.ts +0 -4
- package/dist/port/with/strategies.d.ts +2 -0
- package/dist/port/with/team-strategy.d.ts +5 -1
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-ZAG2CAIY.js → post-register-Z5T7FY45.js} +6 -6
- package/dist/query/index.js +3 -3
- package/dist/reduce/index.js +4 -4
- package/dist/register-UZ37QPDB.js +32 -0
- package/dist/registry-ETJNA7PO.js +9 -0
- package/dist/registry-F7U6KSLX.js +9 -0
- package/dist/registry-L4ELKX6C.js +18 -0
- package/dist/registry-SDT5LIGO.js +19 -0
- package/dist/request-withdrawal-PVFEYX57.js +31 -0
- package/dist/{reveal-CMOJY4VO.js → reveal-DQ2E3QAZ.js} +6 -6
- package/dist/revoke-YXY6SHUH.js +24 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +20 -13
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +4 -4
- package/dist/{seed-UK4QRIUN.js → seed-DZ4ORU5N.js} +19 -22
- package/dist/seed-DZ4ORU5N.js.map +1 -0
- package/dist/sequence/index.js +9 -9
- package/dist/session/index.js +10 -10
- package/dist/shadow/index.js +2 -2
- package/dist/share-link/index.js +1 -1
- package/dist/signer-Q7XQBMPA.js +25 -0
- package/dist/snapshots/index.js +10 -10
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-ZWXSOWJI.js → stale-NI7SAXAZ.js} +10 -10
- package/dist/storage-3VNGSHXS.js +23 -0
- package/dist/{store-coordination-provider-PB6QZ57W.js → store-coordination-provider-TYKNR3OU.js} +18 -12
- package/dist/store-coordination-provider-TYKNR3OU.js.map +1 -0
- package/dist/sync/index.js +11 -11
- package/dist/team/index.js +15 -14
- package/dist/tiers/index.js +11 -10
- package/dist/to/index.js +17 -9
- package/dist/to/index.js.map +1 -1
- package/dist/transactions/index.js +4 -4
- package/dist/{ulid-PTAIMDG4.js → ulid-VQW6ERQN.js} +2 -2
- package/dist/util/index.js +1 -1
- package/dist/vault-head/index.js +115 -0
- package/dist/vault-head/index.js.map +1 -0
- package/dist/{verify-RO5RTZOJ.js → verify-6J2YLKSO.js} +5 -5
- package/dist/via/lookup/handle.d.ts +4 -1
- package/dist/via/money/exact.d.ts +44 -0
- package/dist/via/money/index.d.ts +2 -0
- package/dist/via/money/normalize.d.ts +20 -0
- package/dist/walk-WRA7RAPY.js +21 -0
- package/dist/with-audit/guards/immutable-guard.d.ts +14 -0
- package/dist/with-audit/periods/active.d.ts +28 -1
- package/dist/with-audit/periods/index.d.ts +4 -2
- package/dist/with-audit/periods/periods.d.ts +161 -6
- package/dist/with-audit/periods/strategy.d.ts +14 -5
- package/dist/with-audit/periods/vault-facade.d.ts +89 -10
- package/dist/with-audit/periods/window.d.ts +10 -0
- package/dist/with-audit/sealed-record/index.d.ts +13 -0
- package/dist/with-audit/tiers/index.d.ts +17 -2
- package/dist/with-cargo/adopt-partition.d.ts +1 -1
- package/dist/with-cargo/extract-partition.d.ts +1 -1
- package/dist/with-cargo/index.d.ts +2 -2
- package/dist/with-commit/history/history.d.ts +26 -0
- package/dist/with-commit/history/index.d.ts +2 -1
- package/dist/with-commit/history/ledger/store.d.ts +7 -1
- package/dist/with-commit/history/strategy.d.ts +11 -1
- package/dist/with-commit/numbering/index.d.ts +0 -5
- package/dist/with-commit/vault-head/active.d.ts +2 -0
- package/dist/with-commit/vault-head/head.d.ts +28 -0
- package/dist/with-commit/vault-head/index.d.ts +10 -0
- package/dist/with-commit/vault-head/strategy.d.ts +127 -0
- package/dist/with-commit/vault-head/verify.d.ts +90 -0
- package/dist/with-formula/materialized-views/types.d.ts +52 -0
- package/dist/with-party/directory/cover/storage.d.ts +0 -8
- package/dist/with-party/directory/storage.d.ts +0 -11
- package/dist/with-party/directory/visibility.d.ts +0 -21
- package/dist/with-party/policy/storage.d.ts +0 -10
- package/dist/with-party/team/index.d.ts +1 -1
- package/dist/with-party/team/keyring.d.ts +149 -3
- package/dist/with-party/team/noydb-facade.d.ts +5 -1
- package/dist/with-party/team/roster-tag.d.ts +101 -0
- package/dist/with-party/team/tiers.d.ts +2 -1
- package/dist/with-pod/format.d.ts +10 -12
- package/dist/with-pod/index.d.ts +10 -10
- package/dist/with-pod/open.d.ts +2 -2
- package/dist/with-pod/pod-handle.d.ts +19 -0
- package/dist/with-pod/pod-store.d.ts +0 -8
- package/dist/with-pod/{bundle.d.ts → pod.d.ts} +26 -19
- package/dist/with-pod/ulid.d.ts +1 -1
- package/dist/with-shape/blobs/blob-set.d.ts +2 -2
- package/dist/with-shape/introspection/index.d.ts +1 -0
- package/dist/with-sync/engine.d.ts +54 -5
- package/dist/with-sync/strategy.d.ts +3 -0
- package/dist/withdraw-accessible-4JTDFRFZ.js +28 -0
- package/package.json +13 -5
- package/dist/api-WVY3H3YH.js +0 -20
- package/dist/backup-YYBE5RPC.js.map +0 -1
- package/dist/bundle-handle-JEUWU2SJ.js +0 -36
- package/dist/bundle-handle-JEUWU2SJ.js.map +0 -1
- package/dist/chunk-26JMGSSC.js.map +0 -1
- package/dist/chunk-3737A6EA.js.map +0 -1
- package/dist/chunk-4MC5JRRX.js.map +0 -1
- package/dist/chunk-52QYGZHH.js.map +0 -1
- package/dist/chunk-55LKVALY.js.map +0 -1
- package/dist/chunk-5PR33AC4.js.map +0 -1
- package/dist/chunk-66W5OOCX.js.map +0 -1
- package/dist/chunk-7G6VDWXB.js.map +0 -1
- package/dist/chunk-A67356IR.js.map +0 -1
- package/dist/chunk-AFHQATY4.js +0 -47
- package/dist/chunk-AFHQATY4.js.map +0 -1
- package/dist/chunk-AHLJSVRL.js.map +0 -1
- package/dist/chunk-C2OOEM57.js.map +0 -1
- package/dist/chunk-DIMKRG2E.js +0 -131
- package/dist/chunk-DIMKRG2E.js.map +0 -1
- package/dist/chunk-E6427JU2.js.map +0 -1
- package/dist/chunk-E7OBCBXS.js.map +0 -1
- package/dist/chunk-EDXAQ6JI.js.map +0 -1
- package/dist/chunk-EP7VPR7Y.js.map +0 -1
- package/dist/chunk-ERB6NDDW.js.map +0 -1
- package/dist/chunk-G5APARUV.js.map +0 -1
- package/dist/chunk-GASWQ3XG.js.map +0 -1
- package/dist/chunk-GLKDWWNO.js.map +0 -1
- package/dist/chunk-HNGSV2YC.js.map +0 -1
- package/dist/chunk-I4F6PKUK.js.map +0 -1
- package/dist/chunk-I4ZAO7F7.js.map +0 -1
- package/dist/chunk-I6SDX7WX.js.map +0 -1
- package/dist/chunk-JXIDGFKS.js.map +0 -1
- package/dist/chunk-LWSJG3LI.js.map +0 -1
- package/dist/chunk-MWKDOHEF.js.map +0 -1
- package/dist/chunk-MZ5ZFO2D.js.map +0 -1
- package/dist/chunk-NIVEDYJZ.js.map +0 -1
- package/dist/chunk-NWCGDAV6.js +0 -45
- package/dist/chunk-NWCGDAV6.js.map +0 -1
- package/dist/chunk-PJNTR2NV.js.map +0 -1
- package/dist/chunk-PRDRGL73.js.map +0 -1
- package/dist/chunk-PY7CI6L5.js.map +0 -1
- package/dist/chunk-RKKORO4M.js.map +0 -1
- package/dist/chunk-TAGFGKHP.js.map +0 -1
- package/dist/chunk-TDJPS3T2.js.map +0 -1
- package/dist/chunk-TOAIYQJR.js.map +0 -1
- package/dist/chunk-UHXCLPGC.js.map +0 -1
- package/dist/chunk-UIBGXRDD.js.map +0 -1
- package/dist/chunk-V5KYPBUR.js.map +0 -1
- package/dist/chunk-VCETL77O.js.map +0 -1
- package/dist/chunk-VN45YLWM.js.map +0 -1
- package/dist/chunk-VOQW5LAM.js.map +0 -1
- package/dist/chunk-VRA4Q7DA.js.map +0 -1
- package/dist/chunk-VV4NSB7P.js.map +0 -1
- package/dist/chunk-W5W6KHN2.js.map +0 -1
- package/dist/chunk-WHJKIZS7.js.map +0 -1
- package/dist/chunk-WYZ5MYUA.js.map +0 -1
- package/dist/chunk-ZJADGNYF.js.map +0 -1
- package/dist/chunk-ZNACADFG.js.map +0 -1
- package/dist/collection-facade-QN5ULWYC.js +0 -45
- package/dist/delegation-SSEPLXXO.js.map +0 -1
- package/dist/derive-6V3PIZF7.js +0 -22
- package/dist/executor-CYJQLKKI.js +0 -9
- package/dist/executor-FHHLXO4Y.js +0 -28
- package/dist/executor-S4QIKEHW.js +0 -9
- package/dist/export-accessible-PE5AYKZR.js +0 -23
- package/dist/extract-partition-X2L26O6A.js +0 -38
- package/dist/fanout-sidecar-DTBIDOWF.js.map +0 -1
- package/dist/find-RBBYKXI7.js +0 -11
- package/dist/issue-RNU55YOF.js +0 -19
- package/dist/liberate-PX4CAT5V.js +0 -24
- package/dist/noydb-YPSEGK32.js +0 -83
- package/dist/register-TMD6YRCX.js +0 -32
- package/dist/registry-CZVINVQH.js +0 -9
- package/dist/registry-FVLDWQKT.js +0 -18
- package/dist/registry-IKUR2MXD.js +0 -9
- package/dist/registry-JET4HBLY.js +0 -19
- package/dist/request-withdrawal-RH55LNZL.js +0 -31
- package/dist/revoke-R5TBYVA6.js +0 -24
- package/dist/seed-UK4QRIUN.js.map +0 -1
- package/dist/signer-AGALOXOO.js +0 -25
- package/dist/storage-N7LB275C.js +0 -23
- package/dist/store-coordination-provider-PB6QZ57W.js.map +0 -1
- package/dist/walk-OCUHCYJ6.js +0 -21
- package/dist/with-pod/bundle-handle.d.ts +0 -9
- package/dist/withdraw-accessible-QQNWM5BO.js +0 -28
- /package/dist/{api-WVY3H3YH.js.map → api-EYNT6KGS.js.map} +0 -0
- /package/dist/{chunk-35EBKWQU.js.map → chunk-2LFOMUEA.js.map} +0 -0
- /package/dist/{chunk-OF3L2SD6.js.map → chunk-32B7VCKU.js.map} +0 -0
- /package/dist/{chunk-MSWYQLMQ.js.map → chunk-3B6S6RSI.js.map} +0 -0
- /package/dist/{chunk-7F4DBNSP.js.map → chunk-3O72ZD2E.js.map} +0 -0
- /package/dist/{chunk-FNSMZQS4.js.map → chunk-3PMFMUFX.js.map} +0 -0
- /package/dist/{chunk-FAXZQ5UF.js.map → chunk-4RAS6HN6.js.map} +0 -0
- /package/dist/{chunk-VMCCGUQB.js.map → chunk-6JPGVL7X.js.map} +0 -0
- /package/dist/{chunk-WLYIROCJ.js.map → chunk-6YD5SYQS.js.map} +0 -0
- /package/dist/{chunk-YAWK6AWF.js.map → chunk-7KAE7CQ2.js.map} +0 -0
- /package/dist/{chunk-NKH7GSIH.js.map → chunk-AUPSEJ7O.js.map} +0 -0
- /package/dist/{chunk-CCZJJ4I2.js.map → chunk-B6IYATME.js.map} +0 -0
- /package/dist/{chunk-4UVDI4XE.js.map → chunk-CLAHBPOI.js.map} +0 -0
- /package/dist/{chunk-HVQGCKY4.js.map → chunk-D3R7Q4QK.js.map} +0 -0
- /package/dist/{chunk-P7OG2ETF.js.map → chunk-DXKLLKME.js.map} +0 -0
- /package/dist/{chunk-JIUVJ4M7.js.map → chunk-EKEIEJL6.js.map} +0 -0
- /package/dist/{chunk-DG27IUVI.js.map → chunk-FS7NTGCJ.js.map} +0 -0
- /package/dist/{chunk-HXGUIPQB.js.map → chunk-GQXFOAYZ.js.map} +0 -0
- /package/dist/{chunk-BTVYJWSN.js.map → chunk-GTHVPVAR.js.map} +0 -0
- /package/dist/{chunk-DMQCLZ4Z.js.map → chunk-GWO2IDAB.js.map} +0 -0
- /package/dist/{chunk-6HFM7EIT.js.map → chunk-H3G4I3PN.js.map} +0 -0
- /package/dist/{chunk-VJFMXYKP.js.map → chunk-HR5R5JOM.js.map} +0 -0
- /package/dist/{chunk-FDX7OWWI.js.map → chunk-HYYVT2KZ.js.map} +0 -0
- /package/dist/{chunk-JRCYZEHU.js.map → chunk-I6QKB7MT.js.map} +0 -0
- /package/dist/{chunk-KUDZVJKQ.js.map → chunk-IJ46VCWD.js.map} +0 -0
- /package/dist/{chunk-4HDYQL3J.js.map → chunk-INMHEMXX.js.map} +0 -0
- /package/dist/{chunk-AR2JDFAE.js.map → chunk-KV6JM2IA.js.map} +0 -0
- /package/dist/{chunk-PVU7LW5L.js.map → chunk-KX57HODD.js.map} +0 -0
- /package/dist/{chunk-CEJWMBXO.js.map → chunk-LXECF2ZR.js.map} +0 -0
- /package/dist/{chunk-7VHY5E3X.js.map → chunk-MSLPDFK2.js.map} +0 -0
- /package/dist/{chunk-HNHCQ556.js.map → chunk-NR2SUJ3H.js.map} +0 -0
- /package/dist/{chunk-W3TVWF6N.js.map → chunk-OIBEJN5H.js.map} +0 -0
- /package/dist/{chunk-QD724WDJ.js.map → chunk-OMYCSTXL.js.map} +0 -0
- /package/dist/{chunk-Z45YRGFO.js.map → chunk-ONVD6GBY.js.map} +0 -0
- /package/dist/{chunk-KCQX65FU.js.map → chunk-RKCRPPNB.js.map} +0 -0
- /package/dist/{chunk-7TSYBD3E.js.map → chunk-RSXQNFGO.js.map} +0 -0
- /package/dist/{chunk-7NXHMW6Y.js.map → chunk-SYS46BL7.js.map} +0 -0
- /package/dist/{chunk-747LRSOO.js.map → chunk-TMBSPLUG.js.map} +0 -0
- /package/dist/{chunk-WKPJXWKR.js.map → chunk-TX5MKAHA.js.map} +0 -0
- /package/dist/{chunk-NJDQ4YSV.js.map → chunk-YKXZHUOW.js.map} +0 -0
- /package/dist/{chunk-VW6ZZGCG.js.map → chunk-ZLHHE5XZ.js.map} +0 -0
- /package/dist/{chunk-FUHKW252.js.map → chunk-ZZXLB6AS.js.map} +0 -0
- /package/dist/{classified-marker-FTOX7Q5L.js.map → classified-marker-NA6HUV33.js.map} +0 -0
- /package/dist/{collection-facade-QN5ULWYC.js.map → collection-facade-VBM35WAH.js.map} +0 -0
- /package/dist/{computed-6G5M3UPH.js.map → computed-GSTLWSY5.js.map} +0 -0
- /package/dist/{dead-filter-ENRIOQIE.js.map → dead-filter-UALP5DJX.js.map} +0 -0
- /package/dist/{derive-6V3PIZF7.js.map → derive-YUO3TQYN.js.map} +0 -0
- /package/dist/{dispatch-RE74SH6L.js.map → dispatch-4ZHLSFZQ.js.map} +0 -0
- /package/dist/{dispatch-BDVGYC6X.js.map → dispatch-77CB75KB.js.map} +0 -0
- /package/dist/{enclave-52VUXNRL.js.map → enclave-O4NOROCJ.js.map} +0 -0
- /package/dist/{executor-CYJQLKKI.js.map → executor-CZDXBXKE.js.map} +0 -0
- /package/dist/{executor-FHHLXO4Y.js.map → executor-DVJAWN3B.js.map} +0 -0
- /package/dist/{executor-S4QIKEHW.js.map → executor-VRLG4TQJ.js.map} +0 -0
- /package/dist/{export-accessible-PE5AYKZR.js.map → export-accessible-UXSMG7AB.js.map} +0 -0
- /package/dist/{extract-partition-X2L26O6A.js.map → extract-partition-TVY7AMKH.js.map} +0 -0
- /package/dist/{find-RBBYKXI7.js.map → find-WSARMJBW.js.map} +0 -0
- /package/dist/{issue-RNU55YOF.js.map → issue-3JNJNPT3.js.map} +0 -0
- /package/dist/{ledger-4WJL66BW.js.map → ledger-MVPNG5OS.js.map} +0 -0
- /package/dist/{liberate-PX4CAT5V.js.map → liberate-4LOJOLDT.js.map} +0 -0
- /package/dist/{link-set-N4ZPV6E6.js.map → link-set-3POMQR3I.js.map} +0 -0
- /package/dist/{managed-secret-OTHS64IF.js.map → managed-secret-IARIJJ7Q.js.map} +0 -0
- /package/dist/{noydb-YPSEGK32.js.map → noydb-IRILUDED.js.map} +0 -0
- /package/dist/{post-register-ZAG2CAIY.js.map → post-register-Z5T7FY45.js.map} +0 -0
- /package/dist/{register-TMD6YRCX.js.map → register-UZ37QPDB.js.map} +0 -0
- /package/dist/{registry-CZVINVQH.js.map → registry-ETJNA7PO.js.map} +0 -0
- /package/dist/{registry-FVLDWQKT.js.map → registry-F7U6KSLX.js.map} +0 -0
- /package/dist/{registry-IKUR2MXD.js.map → registry-L4ELKX6C.js.map} +0 -0
- /package/dist/{registry-JET4HBLY.js.map → registry-SDT5LIGO.js.map} +0 -0
- /package/dist/{request-withdrawal-RH55LNZL.js.map → request-withdrawal-PVFEYX57.js.map} +0 -0
- /package/dist/{reveal-CMOJY4VO.js.map → reveal-DQ2E3QAZ.js.map} +0 -0
- /package/dist/{revoke-R5TBYVA6.js.map → revoke-YXY6SHUH.js.map} +0 -0
- /package/dist/{signer-AGALOXOO.js.map → signer-Q7XQBMPA.js.map} +0 -0
- /package/dist/{stale-ZWXSOWJI.js.map → stale-NI7SAXAZ.js.map} +0 -0
- /package/dist/{storage-N7LB275C.js.map → storage-3VNGSHXS.js.map} +0 -0
- /package/dist/{ulid-PTAIMDG4.js.map → ulid-VQW6ERQN.js.map} +0 -0
- /package/dist/{verify-RO5RTZOJ.js.map → verify-6J2YLKSO.js.map} +0 -0
- /package/dist/{walk-OCUHCYJ6.js.map → walk-WRA7RAPY.js.map} +0 -0
- /package/dist/{withdraw-accessible-QQNWM5BO.js.map → withdraw-accessible-4JTDFRFZ.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/enclave/crypto.ts"],"sourcesContent":["/**\n * Cryptographic primitives — thin wrappers around the Web Crypto API.\n *\n * ## Design principle\n *\n * **Zero npm crypto dependencies.** Every operation uses `globalThis.crypto.subtle`,\n * which is available natively in Node.js ≥ 18, all modern browsers, and\n * Deno/Bun. This avoids supply-chain risk from third-party crypto packages and\n * ensures the library stays auditable.\n *\n * ## Algorithms\n *\n * | Use case | Algorithm | Parameters |\n * |----------|-----------|------------|\n * | Key derivation | PBKDF2-SHA256 | 600,000 iterations, 32-byte salt |\n * | Record encryption | AES-256-GCM | 12-byte random IV per operation |\n * | DEK wrapping | AES-KW (RFC 3394) | 256-bit KEK |\n * | Binary encrypt | AES-256-GCM | same as record encryption |\n * | Integrity | HMAC-SHA256 | for presence channels |\n * | Content hash | SHA-256 | for ledger and bundle integrity |\n *\n * ## Key lifecycle\n *\n * ```\n * secret + salt\n * └─► deriveKey() → KEK (CryptoKey, extractable: false)\n * └─► wrapKey() → wrapped DEK bytes [stored in keyring]\n * └─► unwrapKey() → DEK (CryptoKey) [memory only during session]\n * └─► encrypt() / decrypt() → ciphertext / plaintext\n * ```\n *\n * IVs are generated fresh by {@link generateIV} on every encrypt call.\n * Reusing an IV with the same key would break GCM's authentication guarantee —\n * this function should be the only place IVs are produced.\n *\n * @module\n */\n\nimport { DecryptionError, InvalidKeyError, TamperedError, ValidationError } from '../errors.js'\n\n/**\n * **EnclaveKey** — the opaque key type at the enclave seam.\n *\n * In noy-db's reference enclave this is `CryptoKey` (the Web Crypto API's\n * key handle). A fork's enclave redefines this alias to its own key\n * representation (e.g. `type EnclaveKey = null` for a keyless HSM-backed\n * fork) — outside `kernel/enclave/**`, every consumer must treat it as\n * opaque: never construct, inspect, or serialize it directly, only pass it\n * between barrel functions (`encrypt`/`decrypt`/`wrapKey`/`unwrapKey`/…).\n */\nexport type EnclaveKey = CryptoKey\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst KEY_BITS = 256\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a secret and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n secret: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n return deriveKekFromMaterial(new TextEncoder().encode(secret), salt)\n}\n\n/**\n * Shared PBKDF2 → AES-KW derivation core. `material` is the raw\n * pre-KDF input bytes (a UTF-8 phrase for standard mode, an\n * AG-1-encoded 3-part structure for echo mode).\n */\nasync function deriveKekFromMaterial(\n material: Uint8Array,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey('raw', material as BufferSource, 'PBKDF2', false, ['deriveKey'])\n return subtle.deriveKey(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n/** One three-part echo secret: prompt (typed) → echo (revealed) → key (typed). */\nexport interface EchoSecretParts {\n readonly prompt: string\n readonly echo: string\n readonly key: string\n}\n\n/**\n * Domain context for the AG-1 encoding. The leading `0xFF` is the load-bearing\n * byte: it is not a legal UTF-8 lead byte, so `TextEncoder` can never emit it —\n * no string's UTF-8 encoding can equal an AG-1 encoding, whatever the string.\n */\nconst ECHO_KDF_CONTEXT = new Uint8Array([0xff, ...new TextEncoder().encode('noydb-echo-secret-v1')])\n\n/**\n * AG-1 encoding: domain context + 4-byte big-endian length prefix per\n * part. Two independent guarantees make a single typed string unable to\n * derive an echo vault's KEK (spec AG-1):\n *\n * 1. The `0xFF` context prefix — UTF-8 never produces that byte, so the\n * encoding is provably not the encoding of ANY string (not merely of\n * no separator-joined form).\n * 2. Structural length prefixes — part boundaries are key material, so\n * re-splitting the same characters changes the derived KEK.\n *\n * @throws ValidationError when any part is not a string — the single\n * chokepoint every echo KEK derivation and block mint passes through, so a\n * malformed parts object cannot be `TextEncoder`-coerced into key material.\n */\nexport function encodeEchoParts(parts: EchoSecretParts): Uint8Array {\n for (const name of ['prompt', 'echo', 'key'] as const) {\n if (typeof parts?.[name] !== 'string') {\n throw new ValidationError('echo secret parts must be three strings: prompt, echo, key')\n }\n }\n const enc = new TextEncoder()\n const segments = [parts.prompt, parts.echo, parts.key].map((p) => enc.encode(p))\n const total = ECHO_KDF_CONTEXT.length + segments.reduce((n, s) => n + 4 + s.length, 0)\n const out = new Uint8Array(total)\n out.set(ECHO_KDF_CONTEXT, 0)\n let offset = ECHO_KDF_CONTEXT.length\n for (const s of segments) {\n new DataView(out.buffer).setUint32(offset, s.length, false)\n out.set(s, offset + 4)\n offset += 4 + s.length\n }\n return out\n}\n\n/** Derive the tier-1 KEK from a 3-part echo secret (spec: KEK from ALL parts). */\nexport async function deriveEchoKey(\n parts: EchoSecretParts,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n return deriveKekFromMaterial(encodeEchoParts(parts), salt)\n}\n\n/**\n * Which AES algorithm/usage a {@link deriveSecretKey} result is for:\n * `'aes-kw'` mints a key-wrapping key (`wrapKey`/`unwrapKey`, e.g. for\n * KEK derivation); `'aes-gcm'` mints a direct encrypt/decrypt key (e.g.\n * for the wrap-DEKs primitive in `with-party/team/wrapped-deks.ts`).\n */\nexport type SecretKeyUsage = 'aes-kw' | 'aes-gcm'\n\n/**\n * Derive a non-extractable AES key from a secret/credential and salt\n * via PBKDF2-SHA256, generalizing {@link deriveKey}'s AES-KW derivation to\n * also cover AES-GCM-usage keys. Callers pin their own `iterations` and\n * `keyUsage` so an existing call site's exact parameters (and thus its\n * derived-key bytes) are preserved verbatim when migrated onto this\n * shared primitive — this is call-site consolidation, not a KDF change.\n */\nexport async function deriveSecretKey(\n secret: string,\n salt: Uint8Array,\n params: { iterations: number; keyUsage: SecretKeyUsage },\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return params.keyUsage === 'aes-gcm'\n ? subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n : subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Per-record CEK wrapping ───────────────────────────────────────────\n\n/**\n * Derive a **dedicated** AES-KW wrapping key from a collection/tier DEK via\n * HKDF-SHA256, used to wrap/unwrap a per-record CEK.\n *\n * The collection/tier DEKs are AES-256-**GCM** keys (usages\n * `encrypt`/`decrypt`) and cannot themselves act as an AES-KW KEK. We do NOT\n * re-import the raw DEK bytes directly as an AES-KW key — that would reuse one\n * key across two primitives (AES-GCM for `_det`/presence, AES-KW for CEK\n * wrapping), violating key separation. Instead we HKDF-derive a domain-\n * separated KW key (salt `noydb-cek-wrap`), exactly as `derivePresenceKey`\n * derives a separate presence key. The derivation is deterministic, so\n * wrapping the same CEK under the same DEK always yields identical ciphertext\n * — which is what lets an update reuse a record's stable CEK and produce a\n * byte-identical `_cek`. The KW key is independent of the DEK's GCM key.\n *\n * The DEK must be extractable (it is — `generateDEK`/`unwrapKey` mint\n * extractable keys).\n */\nasync function asKwKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-cek-wrap')\n const info = new TextEncoder().encode('v1')\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey('raw', bits, 'AES-KW', false, ['wrapKey', 'unwrapKey'])\n}\n\n/**\n * AES-KW-wrap a per-record CEK under a collection/tier DEK. Returns base64.\n * Deterministic over `(cek, dek)`.\n */\nexport async function wrapCek(cek: CryptoKey, dek: CryptoKey): Promise<string> {\n const kw = await asKwKey(dek)\n const wrapped = await subtle.wrapKey('raw', cek, kw, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/**\n * Unwrap a per-record CEK from base64 under a collection/tier DEK. Throws\n * `InvalidKeyError` if the wrapped bytes do not authenticate under the DEK.\n */\nexport async function unwrapCek(wrappedBase64: string, dek: CryptoKey): Promise<CryptoKey> {\n const kw = await asKwKey(dek)\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kw,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n/**\n * Import a raw 32-byte AES-256-GCM record CEK (e.g. from a sealed-CEK\n * delivery binding). Non-extractable, decrypt-only — the imported key is\n * used solely to open the record's `_iv`/`_data` body under `decrypt()`.\n */\nexport async function importCek(rawKey: Uint8Array): Promise<CryptoKey> {\n return subtle.importKey('raw', rawKey as BufferSource, { name: 'AES-GCM', length: KEY_BITS }, false, ['decrypt'])\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/**\n * Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call.\n *\n * `aad` binds the record's identity into the auth tag (#1041) — see\n * `record-aad.ts` for what is bound and why `_v` is not. A body sealed with\n * AAD can only be opened by a reader supplying the identical bytes, which is\n * what stops an untrusted store from relocating or re-tiering an envelope.\n *\n * Optional **only while the call-site sweep is in progress**. It becomes\n * mandatory once every `encrypt`/`decrypt` pair supplies it, at which point\n * the format version bumps and the reader rejects anything unbound. Until\n * then an omitted `aad` reproduces the previous, unauthenticated behaviour.\n */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n aad?: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n ...(aad !== undefined && { additionalData: aad as BufferSource }),\n },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data.\n *\n * `aad` must be byte-identical to what {@link encrypt} was given, or the auth\n * tag fails and this throws `TamperedError` (#1041). Note the asymmetry that\n * makes this useful: a mismatched identity is indistinguishable from a\n * corrupted body, so relocation fails **closed** rather than returning a\n * plausible record in the wrong place.\n */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad?: Uint8Array,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n ...(aad !== undefined && { additionalData: aad as BufferSource }),\n },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError(...(await classifyTagFailure(iv, ciphertext, dek, aad)))\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n\n/**\n * Distinguish a FORMAT TRANSITION from an attack, when the enclave can (#1103).\n *\n * ## The problem\n *\n * Since #1041 switched identity AAD on, one `TamperedError` covers at least\n * four situations: legitimate pre-binding data, a store that reconstructs an\n * envelope and drops a bound field, a CEK/rotation mismatch, and actual\n * tampering. `SECURITY.md` and the subsystem docs teach the LAST reading — so a\n * user who upgrades, hits this on their own honest data, and consults the docs\n * is told their store has been compromised.\n *\n * That is a false positive on the product's central security claim, and the\n * cost is not merely a confusing message: **a tamper alarm that cries wolf is\n * one users learn to ignore.**\n *\n * ## Why the probe is sound, and why it is NOT a downgrade\n *\n * A body sealed before #1041 carries no AAD, so it opens under an empty one.\n * Retrying that way is **positive evidence** of the legacy format rather than a\n * guess: forging a body that decrypts under this DEK requires the DEK, and an\n * untrusted store does not hold one.\n *\n * ⚠️ **Classification ONLY — the plaintext is discarded and the caller still\n * throws.** That is the entire safety argument: the outcome is identical either\n * way, so this cannot become the lenient path #1041 exists to remove. A variant\n * that RETURNED the retry's plaintext would be exactly the accept-without-AAD\n * downgrade the binding was built to prevent. Do not \"optimise\" it into one.\n *\n * Note what the honest message concedes: pre-#1041 data had no authenticated\n * metadata, so a legacy record that was ALSO tampered with reads as legacy.\n * That is not a weakening — it is an accurate statement about data that\n * genuinely never carried a binding.\n */\nasync function classifyTagFailure(\n iv: Uint8Array,\n ciphertext: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array | undefined,\n): Promise<[] | [string, 'unbound-legacy-format']> {\n // Only meaningful when AAD was supplied: with none there is no second\n // interpretation to test, and the failure is a plain tag mismatch.\n if (aad === undefined) return []\n try {\n await subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, dek, ciphertext as BufferSource)\n } catch {\n return [] // no benign explanation — the security alert stands\n }\n return [\n 'This record was sealed BEFORE record-identity binding (#1041) and cannot be read by this ' +\n 'version. Its body decrypts correctly under the record key, so this is a data-format ' +\n 'transition, not tampering — but it also means the record predates authenticated metadata. ' +\n 'There is no migration path; see noy-db #1100.',\n 'unbound-legacy-format',\n ]\n}\n\n// ─── Binary Encrypt / Decrypt ────────\n\n/**\n * Encrypt raw bytes with AES-256-GCM using a fresh random IV.\n * Used by the attachment store so binary blobs avoid double base64 encoding\n * (the existing `encrypt()` function calls `TextEncoder` on a string — here\n * we pass the `Uint8Array` directly to `subtle.encrypt`).\n */\nexport async function encryptBytes(\n data: Uint8Array,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext back to raw bytes.\n * Counterpart to `encryptBytes`. Throws `TamperedError` on auth-tag failure.\n */\nexport async function decryptBytes(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n/**\n * SHA-256 hex digest of raw bytes. Used to derive content-addressed\n * eTags for blob deduplication. Computed on plaintext bytes\n * before compression and encryption so the eTag identifies content, not\n * ciphertext, and survives re-encryption (key rotation, re-upload).\n */\nexport async function sha256Hex(data: Uint8Array): Promise<string> {\n const hash = await subtle.digest('SHA-256', data as unknown as BufferSource)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── HMAC-SHA-256 ─────────────────────────────\n\n/**\n * Compute HMAC-SHA-256(key, data) and return hex string.\n *\n * Used to derive content-addressed eTags that are opaque to the store:\n * ```\n * eTag = hmacSha256Hex(blobDEK, plaintext)\n * ```\n *\n * Unlike a plain SHA-256, the HMAC is keyed by the vault-shared `_blob` DEK,\n * so an attacker with store access cannot pre-compute eTags for known files.\n * Deduplication still works within a vault (same key + same content = same eTag).\n */\nexport async function hmacSha256Hex(key: CryptoKey, data: Uint8Array): Promise<string> {\n // Export AES-GCM DEK raw bytes → import as HMAC key\n const rawKey = await subtle.exportKey('raw', key)\n const hmacKey = await subtle.importKey(\n 'raw',\n rawKey,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const sig = await subtle.sign('HMAC', hmacKey, data as unknown as BufferSource)\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── AAD-aware Binary Encrypt / Decrypt ──\n\n/**\n * Encrypt raw bytes with AES-256-GCM using Additional Authenticated Data.\n *\n * The AAD binds each chunk to its parent blob and position, preventing\n * chunk reorder, substitution, and truncation attacks:\n * ```\n * AAD = UTF-8(\"{eTag}:{chunkIndex}:{chunkCount}\")\n * ```\n *\n * The AAD is NOT stored — the reader reconstructs it from `BlobObject`\n * metadata and passes it to `decryptBytesWithAAD`.\n */\nexport async function encryptBytesWithAAD(\n data: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext with AAD verification.\n *\n * If the AAD does not match the one used at encryption time (e.g. because\n * a chunk was reordered or substituted from another blob), the GCM auth\n * tag fails and this throws `TamperedError`.\n */\nexport async function decryptBytesWithAAD(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Presence Key Derivation ──────────────────────────────\n\n/**\n * Derive an AES-256-GCM presence key from a collection DEK using HKDF-SHA256.\n *\n * The presence key is domain-separated from the data DEK by the fixed salt\n * `'noydb-presence'` and the `info` = collection name. This means:\n * - The adapter never sees the presence key.\n * - Presence payloads rotate automatically when the collection DEK is rotated.\n * - Revoked users cannot derive the new presence key after a DEK rotation.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Used as the HKDF `info` parameter for domain separation.\n * @returns A non-extractable AES-256-GCM key suitable for presence payload encryption.\n */\nexport async function derivePresenceKey(dek: CryptoKey, collectionName: string): Promise<CryptoKey> {\n // Step 1: export DEK raw bytes\n const rawDek = await subtle.exportKey('raw', dek)\n\n // Step 2: import as HKDF key material\n const hkdfKey = await subtle.importKey(\n 'raw',\n rawDek,\n 'HKDF',\n false,\n ['deriveBits'],\n )\n\n // Step 3: derive 256 bits with salt='noydb-presence' and info=collectionName\n const salt = new TextEncoder().encode('noydb-presence')\n const info = new TextEncoder().encode(collectionName)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n\n // Step 4: import derived bits as AES-GCM key\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Sealed Field Key Derivation ──────────────────────────\n\n/**\n * Derive a **per-field** AES-256-GCM key from a collection DEK using\n * HKDF-SHA256, used to encrypt a single sensitive field into its own\n * `_sealed[field]` envelope slot (structural group-encryption).\n *\n * Domain-separated from the data DEK (and from the presence/deterministic\n * channels) by the fixed salt `'noydb-sealed'` and an `info` of\n * `JSON.stringify(['noydb-sealed', collectionName, field])`. The JSON-array\n * encoding is injective — each element is separately quoted/escaped — so\n * collection `a/sealed/b` + field `c` cannot collide with collection `a` +\n * field `b/sealed/c`. Each sensitive field gets a **distinct** key, so a\n * `_sealed[a]` ciphertext does not authenticate under field `b`'s key —\n * sealed fields are cryptographically isolated from one another. The\n * collection name keeps the same field name in two collections from sharing a\n * key, mirroring how `derivePresenceKey` / `encryptDeterministic` domain-separate.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Part of the HKDF `info` for domain separation.\n * @param field Sensitive field name — the rest of the `info`.\n * @returns A non-extractable AES-256-GCM key for that field's sealed slot.\n */\nexport async function deriveSealedFieldKey(\n dek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed')\n // JSON-array encoding is injective: each element is separately quoted/\n // escaped, so collection `a/sealed/b` + field `c` cannot collide with\n // collection `a` + field `b/sealed/c`.\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a per-record sealed-field key from the record's **per-record CEK**\n * rather than the collection DEK. Same HKDF construction as\n * {@link deriveSealedFieldKey} but with salt `'noydb-sealed-cek'` (distinct\n * from `'noydb-sealed'`), so a CEK-derived key can never collide with a\n * DEK-derived one for the same `{collection, field}`.\n *\n * The point: the sealed ciphertext's only key now flows from the record's CEK.\n * Dropping the record's wrapped `_cek` (crypto-shred / `forget`) makes the key\n * — and thus `_sealed[field]` — unrecoverable, giving sealed fields the same\n * erasure guarantee `_data` already has. The CEK must be extractable (it is —\n * `unwrapCek`/`generateDEK` mint extractable keys).\n *\n * @param cek The record's AES-256-GCM CEK (extractable).\n * @param collectionName Part of the HKDF `info` for domain separation.\n * @param field Sensitive field name — the rest of the `info`.\n * @returns A non-extractable AES-256-GCM key for that field's sealed slot.\n */\nexport async function deriveSealedFieldKeyFromCek(\n cek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawCek = await subtle.exportKey('raw', cek)\n const hkdfKey = await subtle.importKey('raw', rawCek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed-cek')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed-cek', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Deterministic Encryption ────────────────────────────\n\n/**\n * Derive the collection's **dedicated deterministic-index key** from its DEK\n * via HKDF-SHA256 (L-1, #554).\n *\n * `_det` slots previously encrypted under the raw collection DEK — the same\n * key `_data` uses with a *randomized*-IV regime while `_det` uses a\n * *deterministic*-IV regime. Two IV regimes on one key is avoidable hygiene\n * debt (~2^-96 theoretical collision), so the deterministic index now gets its\n * own HKDF-separated key: salt `'noydb-det'`, info\n * `JSON.stringify(['noydb-det'])` (the injective JSON-array domain tag, same\n * convention as {@link deriveSealedFieldKey}). Per-field separation still\n * comes from the `'<collection>/<field>'` context inside\n * {@link encryptDeterministic}, exactly as before.\n *\n * The derived key stays **DEK-derived (collection-level)** on purpose: `_det`\n * is carried verbatim through per-record CEK rotation (see\n * `record-keys/sealing.ts`), so its key must not depend on the CEK. It rotates\n * with the collection DEK, like the presence key.\n *\n * The key is minted **extractable** — {@link encryptDeterministic} derives its\n * per-value IV via HKDF over the raw key bytes (an `exportKey` under the\n * hood), which a non-extractable key would forbid. Same in-memory posture as\n * the DEK itself (`generateDEK` mints extractable keys).\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @returns A dedicated AES-256-GCM key for the `_det` deterministic index.\n */\nexport async function deriveDeterministicKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-det')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-det']))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a deterministic 12-byte IV from `{ DEK, context, plaintext }`\n * via HKDF-SHA256. Given the same three inputs, the IV is identical, so\n * `encryptDeterministic` produces the same ciphertext on every call —\n * which is precisely what enables blind equality search on encrypted\n * fields.\n *\n * **The side channel this opens.** Two records whose field value is the\n * same produce the same ciphertext. An observer with store access can\n * therefore tell which records share a value — not *what* the value is,\n * but the equivalence class. This is the well-known trade-off of\n * deterministic encryption and is why the feature is strictly opt-in\n * per field, guarded by `acknowledgeDeterministicRisk: true` at\n * collection creation.\n *\n * The context string MUST include the collection name and field name,\n * so:\n * - The same plaintext in two different fields encrypts differently\n * (no cross-field equality leak).\n * - The same plaintext in two different collections (different DEKs)\n * encrypts differently by virtue of the key, even before HKDF\n * domain separation kicks in.\n */\nasync function deriveDeterministicIV(\n dek: CryptoKey,\n context: string,\n plaintext: string,\n): Promise<Uint8Array> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-deterministic-v1')\n const info = new TextEncoder().encode(`${context}\\x00${plaintext}`)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n IV_BYTES * 8,\n )\n return new Uint8Array(bits)\n}\n\n/**\n * Encrypt a plaintext string with AES-256-GCM and a deterministic,\n * HKDF-derived IV.\n *\n * The same `{ dek, context, plaintext }` triple always produces the\n * same `{ iv, data }` — call this twice and you can string-compare the\n * ciphertexts to check equality of the inputs without decrypting them.\n *\n * @param context Domain-separation string — by convention\n * `'<collection>/<field>'`. Different contexts encrypt\n * the same plaintext to different ciphertexts, so\n * `email` in collection `users` does not collide with\n * `email` in collection `customers`.\n */\nexport async function encryptDeterministic(\n plaintext: string,\n dek: CryptoKey,\n context: string,\n): Promise<EncryptResult> {\n const iv = await deriveDeterministicIV(dek, context, plaintext)\n const encoded = new TextEncoder().encode(plaintext)\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Counterpart to {@link encryptDeterministic}. The IV is stored\n * alongside the ciphertext (exactly like the randomized path), so\n * decrypt uses the stored IV and verifies the GCM auth tag — a tampered\n * ciphertext throws `TamperedError` just like randomized AES-GCM.\n */\nexport async function decryptDeterministic(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n return decrypt(ivBase64, dataBase64, dek)\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
grant,
|
|
3
3
|
revoke
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CAGI4LLJ.js";
|
|
5
5
|
import {
|
|
6
6
|
CustodyNotEnabledError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CWM4GQD2.js";
|
|
8
8
|
|
|
9
9
|
// src/with-party/custody/active.ts
|
|
10
10
|
function withCustody() {
|
|
@@ -16,7 +16,7 @@ function withCustody() {
|
|
|
16
16
|
return host._revokeCustodianImpl(revoke, vault, options, factors);
|
|
17
17
|
},
|
|
18
18
|
async liberate(vault, opts) {
|
|
19
|
-
const { liberateVault } = await import("./liberate-
|
|
19
|
+
const { liberateVault } = await import("./liberate-4LOJOLDT.js");
|
|
20
20
|
return liberateVault(vault, opts);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
@@ -74,4 +74,4 @@ export {
|
|
|
74
74
|
withCustody,
|
|
75
75
|
CustodyApi
|
|
76
76
|
};
|
|
77
|
-
//# sourceMappingURL=chunk-
|
|
77
|
+
//# sourceMappingURL=chunk-AUPSEJ7O.js.map
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
} from "./chunk-IGJCYUT5.js";
|
|
10
10
|
import {
|
|
11
11
|
applyI18nLocale
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WVE6YKMX.js";
|
|
13
13
|
import {
|
|
14
14
|
viaBinder
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-JNMTN5D5.js";
|
|
16
16
|
import {
|
|
17
17
|
GroupCardinalityError
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CWM4GQD2.js";
|
|
19
19
|
|
|
20
20
|
// src/with-lookup/reduce/reduction.ts
|
|
21
21
|
function reduceRecords(records, spec) {
|
|
@@ -361,4 +361,4 @@ export {
|
|
|
361
361
|
groupAndReduce,
|
|
362
362
|
GroupedReduction
|
|
363
363
|
};
|
|
364
|
-
//# sourceMappingURL=chunk-
|
|
364
|
+
//# sourceMappingURL=chunk-B6IYATME.js.map
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SCHEMAS_COLLECTION
|
|
3
|
+
} from "./chunk-6WO62AJW.js";
|
|
4
|
+
import {
|
|
5
|
+
assemblePodContainer,
|
|
6
|
+
buildExtractedPartitionWrapper
|
|
7
|
+
} from "./chunk-QSIEL6TL.js";
|
|
1
8
|
import {
|
|
2
9
|
BLOB_CHUNKS_COLLECTION,
|
|
3
10
|
BLOB_COLLECTION,
|
|
@@ -5,17 +12,10 @@ import {
|
|
|
5
12
|
BLOB_INTENT_COLLECTION,
|
|
6
13
|
BLOB_SLOTS_PREFIX,
|
|
7
14
|
BLOB_VERSIONS_PREFIX
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
SCHEMAS_COLLECTION
|
|
11
|
-
} from "./chunk-4MC5JRRX.js";
|
|
12
|
-
import {
|
|
13
|
-
assembleBundleContainer,
|
|
14
|
-
buildExtractedPartitionWrapper
|
|
15
|
-
} from "./chunk-VOQW5LAM.js";
|
|
15
|
+
} from "./chunk-JLYQQNY4.js";
|
|
16
16
|
import {
|
|
17
17
|
generateULID
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4AOERLTV.js";
|
|
19
19
|
import {
|
|
20
20
|
LEDGER_COLLECTION
|
|
21
21
|
} from "./chunk-I7FD46FF.js";
|
|
@@ -23,11 +23,16 @@ import {
|
|
|
23
23
|
canonicalJson,
|
|
24
24
|
envelopePayloadHash,
|
|
25
25
|
hashEntry
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ZLHHE5XZ.js";
|
|
27
27
|
import {
|
|
28
|
+
buildRecordAad,
|
|
28
29
|
openEnvelopeJson,
|
|
30
|
+
recordAadFor,
|
|
29
31
|
writeEnvelopeBody
|
|
30
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-OHOKBMTM.js";
|
|
33
|
+
import {
|
|
34
|
+
buildRecordEnvelope
|
|
35
|
+
} from "./chunk-PENPHA45.js";
|
|
31
36
|
import {
|
|
32
37
|
bufferToBase64,
|
|
33
38
|
decrypt,
|
|
@@ -37,14 +42,13 @@ import {
|
|
|
37
42
|
generateDEK,
|
|
38
43
|
unwrapCek,
|
|
39
44
|
wrapCek
|
|
40
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-AU3AUJIZ.js";
|
|
41
46
|
import {
|
|
42
|
-
NOYDB_BACKUP_VERSION
|
|
43
|
-
|
|
44
|
-
} from "./chunk-W5W6KHN2.js";
|
|
47
|
+
NOYDB_BACKUP_VERSION
|
|
48
|
+
} from "./chunk-IGNJOJEP.js";
|
|
45
49
|
import {
|
|
46
50
|
PartitionExtractionError
|
|
47
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-CWM4GQD2.js";
|
|
48
52
|
|
|
49
53
|
// src/with-cargo/walk-closure.ts
|
|
50
54
|
async function walkClosure(vault, opts) {
|
|
@@ -185,15 +189,16 @@ async function reKeyClosure(vault, closure, fieldProjection) {
|
|
|
185
189
|
);
|
|
186
190
|
}
|
|
187
191
|
if (env._cek !== void 0) {
|
|
192
|
+
const aad = recordAadFor({ collection: collectionName, id }, env);
|
|
188
193
|
const cek = await unwrapCek(env._cek, srcDek);
|
|
189
|
-
const plaintext2 = await decrypt(env._iv, env._data, cek);
|
|
190
|
-
const { iv: iv2, data: data2 } = await encrypt(project(plaintext2), cek);
|
|
194
|
+
const plaintext2 = await decrypt(env._iv, env._data, cek, aad);
|
|
195
|
+
const { iv: iv2, data: data2 } = await encrypt(project(plaintext2), cek, aad);
|
|
191
196
|
const wrapped = await wrapCek(cek, destDek);
|
|
192
197
|
out[id] = { ...env, _iv: iv2, _data: data2, _cek: wrapped };
|
|
193
198
|
continue;
|
|
194
199
|
}
|
|
195
|
-
const plaintext = await openEnvelopeJson(env, srcDek);
|
|
196
|
-
const { iv, data } = await encrypt(project(plaintext), destDek);
|
|
200
|
+
const plaintext = await openEnvelopeJson({ collection: collectionName, id }, env, srcDek);
|
|
201
|
+
const { iv, data } = await encrypt(project(plaintext), destDek, recordAadFor({ collection: collectionName, id }, env));
|
|
197
202
|
out[id] = { ...env, _iv: iv, _data: data };
|
|
198
203
|
}
|
|
199
204
|
collections[collectionName] = out;
|
|
@@ -210,8 +215,8 @@ async function reKeySchemas(vault, closure, destDeks, fieldProjection) {
|
|
|
210
215
|
const destDek = destDeks.get(collectionName);
|
|
211
216
|
if (!destDek) continue;
|
|
212
217
|
const srcDek = await getDEK(collectionName);
|
|
213
|
-
const plaintext = await openEnvelopeJson(env, srcDek);
|
|
214
|
-
const { iv, data } = await encrypt(plaintext, destDek);
|
|
218
|
+
const plaintext = await openEnvelopeJson({ collection: SCHEMAS_COLLECTION, id: collectionName }, env, srcDek);
|
|
219
|
+
const { iv, data } = await encrypt(plaintext, destDek, recordAadFor({ collection: SCHEMAS_COLLECTION, id: collectionName }, env));
|
|
215
220
|
out[collectionName] = { ...env, _iv: iv, _data: data };
|
|
216
221
|
}
|
|
217
222
|
return out;
|
|
@@ -225,7 +230,7 @@ async function reKeyLedger(vault, closure, reKeyedCollections, ledgerDek) {
|
|
|
225
230
|
for (const id of ids) {
|
|
226
231
|
const env = await adapter.get(vaultName, LEDGER_COLLECTION, id);
|
|
227
232
|
if (!env) continue;
|
|
228
|
-
srcEntries.push(JSON.parse(await openEnvelopeJson(env, srcLedgerDek)));
|
|
233
|
+
srcEntries.push(JSON.parse(await openEnvelopeJson({ collection: LEDGER_COLLECTION, id }, env, srcLedgerDek)));
|
|
229
234
|
}
|
|
230
235
|
const kept = srcEntries.filter(
|
|
231
236
|
(e) => (e.op === "put" || e.op === "delete") && (closure.get(e.collection)?.has(e.id) ?? false)
|
|
@@ -258,15 +263,12 @@ async function reKeyLedger(vault, closure, reKeyedCollections, ledgerDek) {
|
|
|
258
263
|
payloadHash,
|
|
259
264
|
...src.reason !== void 0 ? { reason: src.reason } : {}
|
|
260
265
|
};
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
_data: data,
|
|
268
|
-
_by: entry.actor
|
|
269
|
-
};
|
|
266
|
+
const ledgerIdentity = { collection: LEDGER_COLLECTION, id: paddedIndex(i), version: i + 1 };
|
|
267
|
+
const { iv, data } = await encrypt(canonicalJson(entry), ledgerDek, buildRecordAad(ledgerIdentity));
|
|
268
|
+
entries[paddedIndex(i)] = buildRecordEnvelope(
|
|
269
|
+
ledgerIdentity,
|
|
270
|
+
{ ts: entry.ts, iv, data }
|
|
271
|
+
);
|
|
270
272
|
prevHash = await hashEntry(entry);
|
|
271
273
|
last = entry;
|
|
272
274
|
}
|
|
@@ -305,8 +307,8 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
305
307
|
const intentId = `${collectionName}::${id}`;
|
|
306
308
|
const intentEnv = await adapter.get(vaultName, BLOB_INTENT_COLLECTION, intentId);
|
|
307
309
|
if (intentEnv) {
|
|
308
|
-
const intentPlain = await openEnvelopeJson(intentEnv, srcDek);
|
|
309
|
-
const intentBody = await writeEnvelopeBody(intentPlain, destDek);
|
|
310
|
+
const intentPlain = await openEnvelopeJson({ collection: BLOB_INTENT_COLLECTION, id: intentId }, intentEnv, srcDek);
|
|
311
|
+
const intentBody = await writeEnvelopeBody({ collection: BLOB_INTENT_COLLECTION, id: intentId, version: intentEnv._v }, intentPlain, destDek);
|
|
310
312
|
place(BLOB_INTENT_COLLECTION, intentId, { ...intentEnv, ...intentBody });
|
|
311
313
|
}
|
|
312
314
|
const env = await adapter.get(vaultName, slotsCollection, id);
|
|
@@ -316,7 +318,7 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
316
318
|
`#748: reKeyBlobs fetched an elevated slot-map envelope (collection="${slotsCollection}" id="${id}" _tier=${env._tier}) \u2014 this is unreachable by construction. Refusing to carry it into a partition.`
|
|
317
319
|
);
|
|
318
320
|
}
|
|
319
|
-
const slots = JSON.parse(await openEnvelopeJson(env, srcDek));
|
|
321
|
+
const slots = JSON.parse(await openEnvelopeJson({ collection: slotsCollection, id }, env, srcDek));
|
|
320
322
|
const kept = {};
|
|
321
323
|
for (const [slotName, slot] of Object.entries(slots)) {
|
|
322
324
|
if (proj && !proj.has(slotName)) continue;
|
|
@@ -326,7 +328,7 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
326
328
|
addRef(slot.eTag);
|
|
327
329
|
}
|
|
328
330
|
if (Object.keys(kept).length === 0) continue;
|
|
329
|
-
const { iv, data } = await encrypt(JSON.stringify(kept), destDek);
|
|
331
|
+
const { iv, data } = await encrypt(JSON.stringify(kept), destDek, recordAadFor({ collection: slotsCollection, id }, env));
|
|
330
332
|
place(slotsCollection, id, { ...env, _iv: iv, _data: data });
|
|
331
333
|
}
|
|
332
334
|
const versionsCollection = `${BLOB_VERSIONS_PREFIX}${collectionName}`;
|
|
@@ -342,9 +344,9 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
342
344
|
`#748: reKeyBlobs fetched an elevated version-record envelope (collection="${versionsCollection}" key="${key}" _tier=${env._tier}) \u2014 this is unreachable by construction. Refusing to carry it into a partition.`
|
|
343
345
|
);
|
|
344
346
|
}
|
|
345
|
-
const record = JSON.parse(await openEnvelopeJson(env, srcDek));
|
|
347
|
+
const record = JSON.parse(await openEnvelopeJson({ collection: versionsCollection, id: key }, env, srcDek));
|
|
346
348
|
addRef(record.eTag);
|
|
347
|
-
const { iv, data } = await encrypt(JSON.stringify(record), destDek);
|
|
349
|
+
const { iv, data } = await encrypt(JSON.stringify(record), destDek, recordAadFor({ collection: versionsCollection, id: key }, env));
|
|
348
350
|
place(versionsCollection, key, { ...env, _iv: iv, _data: data });
|
|
349
351
|
}
|
|
350
352
|
}
|
|
@@ -354,7 +356,7 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
354
356
|
for (const [eTag, refCount] of carriedRefs) {
|
|
355
357
|
const idxEnv = await adapter.get(vaultName, BLOB_INDEX_COLLECTION, eTag);
|
|
356
358
|
if (!idxEnv) continue;
|
|
357
|
-
const blob = JSON.parse(await openEnvelopeJson(idxEnv, srcBlobDek));
|
|
359
|
+
const blob = JSON.parse(await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, idxEnv, srcBlobDek));
|
|
358
360
|
let contentCek;
|
|
359
361
|
let chunksPassthrough;
|
|
360
362
|
if (blob._cek !== void 0) {
|
|
@@ -384,7 +386,7 @@ async function reKeyBlobs(vault, closure, destDeks, fieldProjection) {
|
|
|
384
386
|
const { _cekPending, ...rest } = blob;
|
|
385
387
|
void _cekPending;
|
|
386
388
|
const carried = { ...rest, refCount, _cek: await wrapCek(contentCek, transferBlobDek) };
|
|
387
|
-
const { iv, data } = await encrypt(JSON.stringify(carried), transferBlobDek);
|
|
389
|
+
const { iv, data } = await encrypt(JSON.stringify(carried), transferBlobDek, recordAadFor({ collection: BLOB_INDEX_COLLECTION, id: eTag }, idxEnv));
|
|
388
390
|
place(BLOB_INDEX_COLLECTION, eTag, { ...idxEnv, _iv: iv, _data: data });
|
|
389
391
|
}
|
|
390
392
|
return { internal, blobDek: transferBlobDek };
|
|
@@ -469,7 +471,7 @@ async function extractPartitionCore(vault, opts) {
|
|
|
469
471
|
};
|
|
470
472
|
const bodyJsonStr = JSON.stringify(buildExtractedPartitionWrapper(JSON.stringify(backup), seal));
|
|
471
473
|
const handle = generateULID();
|
|
472
|
-
const bundleBytes = await
|
|
474
|
+
const bundleBytes = await assemblePodContainer({
|
|
473
475
|
handle,
|
|
474
476
|
bodyJsonStr,
|
|
475
477
|
compression: opts.compression,
|
|
@@ -492,4 +494,4 @@ export {
|
|
|
492
494
|
extractPartition,
|
|
493
495
|
extractPartitionCore
|
|
494
496
|
};
|
|
495
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-BAHOV5I5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-cargo/walk-closure.ts","../src/with-cargo/extract-partition.ts"],"sourcesContent":["/**\n * Transitive-closure FK walker. Computes the set of\n * (collection, id) tuples reachable from seed predicates, so a\n * partition extraction ships a referentially-complete subset.\n *\n * Two-phase, plaintext, read-only (runs inside the unlocked vault\n * session — see foundation §13.4 / spec invariant 7):\n * 1. INBOUND expansion: from selected records, pull every record\n * that references them (children travel with parents), to a\n * fixed point.\n * 2. OUTBOUND completion: pull every VISIBLE parent the selected set\n * references, transitively, WITHOUT re-expanding inbound from those\n * parents (bounds the closure). A tier-elevated (or missing) parent is\n * excluded rather than admitted — see `danglingRefs` on the result.\n *\n * The FK graph is auto-derived from the vault's existing RefRegistry\n * (the `ref('target')` declarations on collections) — no hand-written\n * edge list.\n *\n * @module\n */\nimport type { Vault } from '../kernel/vault.js'\nimport { PartitionExtractionError } from '../kernel/errors.js'\n\n/** Seed predicate per collection. Records that return true become roots. */\nexport interface WalkClosureOptions {\n readonly seeds: Record<\n string,\n (record: Record<string, unknown>) => boolean | Promise<boolean>\n >\n /** Max fixed-point iterations before throwing. Default 16. */\n readonly maxDepth?: number\n}\n\n/**\n * #759: an outbound FK edge whose referenced parent was excluded from the\n * closure — either because it doesn't exist, or because it is tier-elevated\n * and therefore invisible (same \"elevated ≡ missing\" semantics as root\n * selection / inbound expansion). The child keeps its FK value; the parent\n * does not travel. Callers (extract-partition) surface this as a residue\n * notice rather than silently dropping it.\n */\nexport interface DanglingRefNotice {\n /** Collection of the child record that holds the dangling FK. */\n readonly collection: string\n /** Id of the child record. */\n readonly id: string\n /** FK field on the child that references the missing/elevated parent. */\n readonly field: string\n /** Target collection the FK points at. */\n readonly target: string\n /** Id of the missing/elevated parent. */\n readonly targetId: string\n /**\n * #772: distinguishes an intentional tier boundary (`'elevated'` — the\n * parent exists but is above the caller's readable tier) from a genuine\n * data-integrity gap (`'missing'` — no envelope for `targetId` at all).\n */\n readonly reason: 'missing' | 'elevated'\n}\n\nexport interface ClosureResult {\n /** collection → set of record ids that travel together. */\n readonly closure: Map<string, Set<string>>\n readonly graph: {\n /** Fixed-point iterations the walk needed to converge. */\n readonly depth: number\n /** True if an edge pointed back to an already-selected node. */\n readonly cyclesDetected: boolean\n }\n /** #759: outbound FK edges whose parent was excluded (missing or elevated). */\n readonly danglingRefs: ReadonlyArray<DanglingRefNotice>\n}\n\nexport async function walkClosure(\n vault: Vault,\n opts: WalkClosureOptions,\n): Promise<ClosureResult> {\n const closure = new Map<string, Set<string>>()\n\n // Records carry a string `id` by construction (Collection.put(id: string)).\n // A non-string id during the walk means a malformed record — fail loud\n // rather than silently dropping it from the closure (which would leave a\n // dangling FK or a missing child in the extracted bundle).\n const requireStringId = (collection: string, record: Record<string, unknown>): string => {\n const id = record['id']\n if (typeof id !== 'string') {\n throw new PartitionExtractionError(\n `walkClosure: record in collection \"${collection}\" has a non-string ` +\n `id (${typeof id}); cannot include it in the partition closure.`,\n )\n }\n return id\n }\n\n const add = (collection: string, id: string): boolean => {\n let set = closure.get(collection)\n if (!set) {\n set = new Set<string>()\n closure.set(collection, set)\n }\n if (set.has(id)) return false\n set.add(id)\n return true\n }\n\n // Phase 0: evaluate seed predicates.\n for (const [collectionName, predicate] of Object.entries(opts.seeds)) {\n const coll = vault.collection<Record<string, unknown>>(collectionName)\n const records = await coll.list()\n for (const record of records) {\n if (await predicate(record)) {\n add(collectionName, requireStringId(collectionName, record))\n }\n }\n }\n\n const { refRegistry, adapter, name: vaultName } = vault._introspectState()\n const maxDepth = opts.maxDepth ?? 16\n let cyclesDetected = false\n\n // `depth` counts PRODUCTIVE expansion generations (rounds that added at\n // least one new record), taken as the max over the two phases — i.e. the\n // FK hop-distance the closure needed, not the raw loop-iteration count.\n // The terminal draining pass that adds nothing does not count.\n let inboundDepth = 0\n let outboundDepth = 0\n\n // Phase 1 — INBOUND expansion. Worklist of newly-added (collection,id)\n // whose children we still need to pull.\n let frontier: Array<[string, string]> = []\n for (const [c, ids] of closure) for (const id of ids) frontier.push([c, id])\n\n while (frontier.length > 0) {\n const next: Array<[string, string]> = []\n for (const [collectionName, id] of frontier) {\n // Which collections reference THIS collection, and via which field?\n for (const inbound of refRegistry.getInbound(collectionName)) {\n const childColl = vault.collection<Record<string, unknown>>(inbound.collection)\n // TODO(perf): re-scans the full inbound collection on every frontier\n // element. O(frontier · inboundCollections · records) per depth. Fine\n // at consumer-firm scale (foundation §13.4); revisit with an index or\n // pagination if extraction over very large vaults gets slow.\n const childRecords = await childColl.list()\n for (const child of childRecords) {\n const fk = child[inbound.field]\n // Only scalar FK values can match an id; skip null/objects\n // (mirrors checkIntegrity's scalar guard, vault.ts).\n if (typeof fk !== 'string' && typeof fk !== 'number') continue\n if (String(fk) !== id) continue\n const childId = requireStringId(inbound.collection, child)\n if (add(inbound.collection, childId)) {\n next.push([inbound.collection, childId])\n } else {\n cyclesDetected = true\n }\n }\n }\n }\n if (next.length > 0 && ++inboundDepth > maxDepth) {\n throw new PartitionExtractionError(\n `walkClosure exceeded maxDepth=${maxDepth}; the FK graph may be ` +\n `unexpectedly deep or cyclic. Raise maxDepth or narrow the seeds.`,\n )\n }\n frontier = next\n }\n\n // Phase 2 — OUTBOUND completion. Pull referenced parents so no FK\n // dangles. Transitive over outbound edges only; parents are NOT\n // inbound-expanded (that would drag in unrelated siblings).\n let outboundFrontier: Array<[string, string]> = []\n for (const [c, ids] of closure) for (const id of ids) outboundFrontier.push([c, id])\n\n const danglingRefs: DanglingRefNotice[] = []\n\n while (outboundFrontier.length > 0) {\n const next: Array<[string, string]> = []\n for (const [collectionName, id] of outboundFrontier) {\n const outbound = refRegistry.getOutbound(collectionName)\n if (Object.keys(outbound).length === 0) continue\n const coll = vault.collection<Record<string, unknown>>(collectionName)\n const record = await coll.get(id)\n if (!record) continue\n for (const [field, descriptor] of Object.entries(outbound)) {\n const rawId = record[field]\n // Only scalar FK values reference a parent id; skip null/objects.\n if (typeof rawId !== 'string' && typeof rawId !== 'number') continue\n const parentId = String(rawId)\n // #759: verify the referenced parent is visible through the SAME\n // tier-gated primitive root selection / inbound expansion use\n // (`Collection.get()` → `#getRaw`, which returns null for both a\n // missing record and a tier-elevated one). An elevated parent must\n // be excluded exactly like an elevated root or inbound target —\n // \"elevated ≡ invisible\" — rather than admitted into the closure,\n // where `reKeyClosure`'s raw adapter read would later hit it and\n // fail loud (the #748 canary). Record the resulting dangling FK\n // instead of silently dropping it.\n const parentColl = vault.collection<Record<string, unknown>>(descriptor.target)\n const parentRecord = await parentColl.get(parentId)\n if (!parentRecord) {\n // #772: a raw (tier-unaware) read distinguishes the two cases the\n // tier-gated `Collection.get()` above collapses to the same null —\n // an envelope present with `_tier > 0` is an intentional tier\n // boundary; no envelope at all is a genuine data-integrity gap.\n const rawParentEnv = await adapter.get(vaultName, descriptor.target, parentId)\n const reason: 'missing' | 'elevated' =\n rawParentEnv && (rawParentEnv._tier ?? 0) > 0 ? 'elevated' : 'missing'\n danglingRefs.push({\n collection: collectionName, id, field, target: descriptor.target, targetId: parentId, reason,\n })\n continue\n }\n // Reaching an already-selected parent here is normal DAG\n // convergence (a child referencing its in-scope parent), not a\n // cycle — so do NOT flag cyclesDetected in the outbound phase.\n if (add(descriptor.target, parentId)) {\n next.push([descriptor.target, parentId])\n }\n }\n }\n if (next.length > 0 && ++outboundDepth > maxDepth) {\n throw new PartitionExtractionError(\n `walkClosure exceeded maxDepth=${maxDepth} during outbound completion.`,\n )\n }\n outboundFrontier = next\n }\n\n const depth = Math.max(inboundDepth, outboundDepth)\n\n return { closure, graph: { depth, cyclesDetected }, danglingRefs }\n}\n","/**\n * Partition extraction. Walks the FK closure, re-encrypts\n * the selected records under fresh per-collection DEKs, seals those DEKs\n * under a one-time transfer key, and serializes an unowned\n * `extracted-partition` bundle.\n *\n * @module\n */\nimport type { Vault } from '../kernel/vault.js'\nimport type { EncryptedEnvelope, BlobObject, SlotRecord, VersionRecord } from '../kernel/types.js'\nimport { NOYDB_BACKUP_VERSION } from '../kernel/types.js'\nimport { buildRecordAad, recordAadFor,\n decrypt,\n encrypt,\n openEnvelopeJson,\n writeEnvelopeBody,\n generateDEK,\n bufferToBase64,\n encryptBytesWithAAD,\n decryptBytesWithAAD,\n unwrapCek,\n wrapCek,\n type EnclaveKey,\n} from '../kernel/enclave/index.js'\nimport {\n BLOB_COLLECTION,\n BLOB_INDEX_COLLECTION,\n BLOB_CHUNKS_COLLECTION,\n BLOB_SLOTS_PREFIX,\n BLOB_VERSIONS_PREFIX,\n} from '../with-shape/blobs/blob-set.js'\nimport { BLOB_INTENT_COLLECTION } from '../with-shape/blobs/blob-intent.js'\nimport { PartitionExtractionError } from '../kernel/errors.js'\nimport { walkClosure, type WalkClosureOptions, type DanglingRefNotice } from './walk-closure.js'\nimport { generateULID } from '../with-pod/ulid.js'\nimport { SCHEMAS_COLLECTION } from '../with-shape/persisted-schemas/storage.js'\nimport { LEDGER_COLLECTION } from '../with-commit/history/ledger/constants.js'\nimport { canonicalJson, hashEntry } from '../with-commit/history/ledger/entry.js'\nimport type { LedgerEntry } from '../with-commit/history/ledger/entry.js'\nimport { envelopePayloadHash } from '../with-commit/history/ledger/hash.js'\nimport { buildRecordEnvelope } from '../kernel/enclave/index.js'\nimport {\n assemblePodContainer,\n buildExtractedPartitionWrapper,\n type TransferSealPayload,\n} from '../with-pod/pod.js'\n\n/** Re-keyed collections snapshot + the fresh DEKs used. */\nexport interface ReKeyResult {\n readonly collections: Record<string, Record<string, EncryptedEnvelope>>\n readonly deks: Map<string, EnclaveKey>\n}\n\n/**\n * Re-encrypt every record in `closure` under a fresh per-collection DEK.\n * Reads raw source envelopes, decrypts under the source DEK, re-encrypts\n * under the new DEK. Plaintext-pipeline: requires an unlocked vault.\n */\nexport async function reKeyClosure(\n vault: Vault,\n closure: Map<string, Set<string>>,\n fieldProjection?: Record<string, readonly string[]>,\n): Promise<ReKeyResult> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const collections: Record<string, Record<string, EncryptedEnvelope>> = {}\n const deks = new Map<string, EnclaveKey>()\n\n for (const [collectionName, ids] of closure) {\n const srcDek = await getDEK(collectionName)\n const destDek = await generateDEK()\n deks.set(collectionName, destDek)\n const out: Record<string, EncryptedEnvelope> = {}\n // FR-7 structural field projection: when this collection has a projection,\n // narrow the plaintext body to `id` (always) + the listed fields BEFORE\n // re-encryption, so excluded fields never travel in the bundle. Applied\n // identically in both re-key branches; only the body changes — the `_cek`\n // re-wrap order is untouched. Absent/empty projection → byte-identical to\n // the un-projected path (`proj` is undefined and `project` is a no-op).\n const projList = fieldProjection?.[collectionName]\n const proj = projList ? new Set(projList) : undefined\n const project = (plaintext: string): string => {\n if (!proj) return plaintext\n const rec = JSON.parse(plaintext) as Record<string, unknown>\n const kept: Record<string, unknown> = {}\n if ('id' in rec) kept['id'] = rec['id'] // id ALWAYS preserved\n for (const f of proj) if (f in rec) kept[f] = rec[f]\n return JSON.stringify(kept)\n }\n\n for (const id of ids) {\n const env = await adapter.get(vaultName, collectionName, id)\n if (!env) continue\n // #748 defense-in-depth canary: `closure` is built exclusively through\n // tier-gated `Collection.list()`/`get()` (collection.ts `ensureHydrated`\n // / `#getRaw` both skip `_tier > 0` envelopes), so an elevated record\n // can never reach this raw `adapter.get` fetch — this should be\n // unreachable by construction. Not a user-facing guard; a cheap tripwire\n // in case that invariant ever regresses.\n if ((env._tier ?? 0) > 0) {\n throw new PartitionExtractionError(\n `#748: reKeyClosure fetched an elevated envelope (collection=\"${collectionName}\" id=\"${id}\" `\n + `_tier=${env._tier}) — this is unreachable by construction (walkClosure only ever adds ids `\n + `reachable through tier-gated Collection reads). Refusing to carry it into a partition.`,\n )\n }\n if (env._cek !== undefined) {\n // Per-record CEK: a naive `{ ...env }` spread would carry a\n // SOURCE-DEK-wrapped CEK into a bundle re-keyed under a different\n // destination DEK — silently undecryptable for the recipient.\n // Re-wrap: unwrap the CEK under the source DEK, re-encrypt the body\n // under the SAME CEK (decision 2 — CEK reused on re-key, preserving\n // the history-chain identity), then wrap the CEK under the fresh\n // destination DEK. The recipient gains access transitively once they\n // re-wrap the collection DEK under their KEK on adopt.\n // Identity is UNCHANGED by extraction — collection/id/_tier/_by all\n // travel with the record, and `vault` is deliberately unbound (the ADR's\n // constraint (c) exists precisely so adoption can re-home a partition).\n // So the same AAD opens the source and seals the destination (#1041).\n const aad = recordAadFor({ collection: collectionName, id }, env)\n const cek = await unwrapCek(env._cek, srcDek)\n const plaintext = await decrypt(env._iv, env._data, cek, aad)\n const { iv, data } = await encrypt(project(plaintext), cek, aad)\n const wrapped = await wrapCek(cek, destDek)\n out[id] = { ...env, _iv: iv, _data: data, _cek: wrapped }\n continue\n }\n const plaintext = await openEnvelopeJson({ collection: collectionName, id }, env, srcDek)\n const { iv, data } = await encrypt(project(plaintext), destDek, recordAadFor({ collection: collectionName, id }, env))\n out[id] = { ...env, _iv: iv, _data: data }\n }\n collections[collectionName] = out\n }\n\n return { collections, deks }\n}\n\n/**\n * Re-key the persisted JSON Schemas (`_schemas/<collection>`) for the\n * closure collections under the destination DEKs. Returns a\n * `{ collection: envelope }` map for the carried collections that actually\n * have a schema; collections without one are omitted.\n */\nexport async function reKeySchemas(\n vault: Vault,\n closure: Map<string, Set<string>>,\n destDeks: Map<string, EnclaveKey>,\n fieldProjection?: Record<string, readonly string[]>,\n): Promise<Record<string, EncryptedEnvelope>> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const out: Record<string, EncryptedEnvelope> = {}\n\n for (const collectionName of closure.keys()) {\n // FR-7: skip a projected collection's schema — the narrowed shape no\n // longer matches the stored JSON Schema, so carrying it would assert a\n // contract the projected records violate (missing required fields).\n if (fieldProjection?.[collectionName]) continue\n const env = await adapter.get(vaultName, SCHEMAS_COLLECTION, collectionName)\n if (!env) continue // collection has no persisted schema — skip\n const destDek = destDeks.get(collectionName)\n if (!destDek) continue\n const srcDek = await getDEK(collectionName)\n const plaintext = await openEnvelopeJson({ collection: SCHEMAS_COLLECTION, id: collectionName }, env, srcDek)\n const { iv, data } = await encrypt(plaintext, destDek, recordAadFor({ collection: SCHEMAS_COLLECTION, id: collectionName }, env))\n out[collectionName] = { ...env, _iv: iv, _data: data }\n }\n return out\n}\n\nconst paddedIndex = (n: number): string => String(n).padStart(10, '0')\n\nexport interface ReKeyLedgerResult {\n /** { paddedIndex: re-encrypted entry envelope } for backup._internal._ledger. */\n readonly entries: Record<string, EncryptedEnvelope>\n /** Recomputed ledgerHead for the carried chain (index -1 when empty). */\n readonly head: { hash: string; index: number; ts: string }\n}\n\n/**\n * Build the carried `_ledger` chain for an extracted partition.\n * Filters source entries to the closure, RE-CHAINS them (fresh index + prevHash),\n * and re-encrypts under `ledgerDek`. The `payloadHash` is recomputed against the\n * re-keyed envelope ONLY for the latest `put` per (collection,id) — the entry\n * `verifyBackupIntegrity` cross-checks; earlier puts + deletes keep their source\n * `payloadHash` verbatim (recomputing an intermediate put would assert a false\n * hash for an older version). Amendments + out-of-closure entries are dropped;\n * `_ledger_deltas`/`_history` are deferred to slice 2.\n */\nexport async function reKeyLedger(\n vault: Vault,\n closure: Map<string, Set<string>>,\n reKeyedCollections: Record<string, Record<string, EncryptedEnvelope>>,\n ledgerDek: EnclaveKey,\n): Promise<ReKeyLedgerResult> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const srcLedgerDek = await getDEK(LEDGER_COLLECTION)\n\n // 1. Load + decrypt source entries in index order.\n const ids = (await adapter.list(vaultName, LEDGER_COLLECTION)).sort()\n const srcEntries: LedgerEntry[] = []\n for (const id of ids) {\n const env = await adapter.get(vaultName, LEDGER_COLLECTION, id)\n if (!env) continue\n srcEntries.push(JSON.parse(await openEnvelopeJson({ collection: LEDGER_COLLECTION, id }, env, srcLedgerDek)) as LedgerEntry)\n }\n\n // 2. Keep closure put/delete entries (drop amendments + out-of-closure).\n const kept = srcEntries.filter(\n (e) => (e.op === 'put' || e.op === 'delete') && (closure.get(e.collection)?.has(e.id) ?? false),\n )\n\n // 3a. Reverse pass: index of the LATEST put per (collection,id).\n const latestPutIndex = new Map<string, number>()\n for (let i = kept.length - 1; i >= 0; i--) {\n const e = kept[i]!\n if (e.op !== 'put') continue\n const key = `${e.collection}/${e.id}`\n if (!latestPutIndex.has(key)) latestPutIndex.set(key, i)\n }\n\n // 3b. Forward re-chain + re-encrypt.\n const entries: Record<string, EncryptedEnvelope> = {}\n let prevHash = ''\n let last: LedgerEntry | undefined\n for (let i = 0; i < kept.length; i++) {\n const src = kept[i]!\n const key = `${src.collection}/${src.id}`\n const isLatestPut = src.op === 'put' && latestPutIndex.get(key) === i\n const reKeyedEnv = reKeyedCollections[src.collection]?.[src.id]\n const payloadHash = isLatestPut && reKeyedEnv\n ? await envelopePayloadHash(reKeyedEnv)\n : src.payloadHash\n const entry: LedgerEntry = {\n index: i,\n prevHash,\n op: src.op,\n collection: src.collection,\n id: src.id,\n version: src.version,\n ts: src.ts,\n actor: src.actor,\n payloadHash,\n ...(src.reason !== undefined ? { reason: src.reason } : {}),\n }\n const ledgerIdentity = { collection: LEDGER_COLLECTION, id: paddedIndex(i), version: i + 1 }\n const { iv, data } = await encrypt(canonicalJson(entry), ledgerDek, buildRecordAad(ledgerIdentity))\n entries[paddedIndex(i)] = buildRecordEnvelope(\n ledgerIdentity,\n { ts: entry.ts, iv, data},\n )\n prevHash = await hashEntry(entry)\n last = entry\n }\n\n return {\n entries,\n head: last ? { hash: prevHash, index: last.index, ts: last.ts } : { hash: '', index: -1, ts: '' },\n }\n}\n\n/** Build the AAD binding for chunk integrity: \"{eTag}:{chunkIndex}:{chunkCount}\".\n * Mirrors `chunkAAD` in blob-set.ts — the eTag is preserved verbatim across\n * the transfer (see `reKeyBlobs`), so the same AAD that bound a chunk in the\n * source keeps binding it in the bundle. */\nfunction chunkAAD(eTag: string, chunkIndex: number, chunkCount: number): Uint8Array {\n return new TextEncoder().encode(`${eTag}:${chunkIndex}:${chunkCount}`)\n}\n\n/** Carried blob internals + the fresh transfer `_blob` DEK (present only when\n * the closure references at least one chunk-based blob). */\nexport interface ReKeyBlobsResult {\n /** `_blob_slots_<C>` / `_blob_versions_<C>` / `_blob_index` / `_blob_chunks`\n * envelopes for the bundle's `_internal` map. */\n readonly internal: Record<string, Record<string, EncryptedEnvelope>>\n /** Fresh transfer `_blob` DEK — seal it so owner-creation wraps it under the\n * recipient KEK. Undefined when no blob travels (source keyring untouched). */\n readonly blobDek?: EnclaveKey\n}\n\n/**\n * Carry the FK-closed slice's blobs — HARDENED key handling (no master-key leak).\n *\n * The source vault's shared `_blob` DEK decrypts (or unwraps the content CEK of)\n * EVERY blob in the source. Sealing it into the transfer would hand the recipient\n * a key to blobs far outside their slice. Instead we mint a **fresh transfer\n * `_blob` DEK** and arrange every carried blob into per-blob-CEK mode under it:\n *\n * - **erasable blob** (`_cek` present): unwrap the per-blob content CEK under the\n * SOURCE `_blob` DEK, re-wrap it under the FRESH transfer DEK. Chunks travel\n * **verbatim** (still ciphertext under that same content CEK — passthrough).\n * - **legacy blob** (no `_cek`, chunks under the shared `_blob` DEK): promote it\n * IN-BUNDLE — mint a fresh content CEK, decrypt each chunk under the source\n * `_blob` DEK and re-encrypt under the content CEK (same AAD, so the eTag-bound\n * integrity holds), then wrap the content CEK under the transfer DEK. The\n * SOURCE is never mutated (non-destructive), and the bundle never holds\n * plaintext blob bytes (zero-knowledge preserved).\n *\n * **eTag identity is preserved** (not re-HMAC'd). eTags are HMAC-keyed off the\n * `_blob` DEK, but they are stored as OPAQUE keys (`_blob_index/<eTag>`,\n * `_blob_chunks/<eTag>_<i>`) and never recomputed on read — only on a `put()`\n * for dedup. Keeping them verbatim keeps every slot/version/index/chunk key and\n * the chunk AAD coherent with zero chunk-key churn. The only consequence: a\n * future `put()` of identical bytes in the adopted vault computes a different\n * eTag (HMAC under the fresh DEK) and will NOT dedup against the carried blob —\n * an accepted, documented trade for hardened key isolation.\n *\n * Slots/versions are re-keyed under their parent collection's destination DEK\n * (honoring `fieldProjection` — projected-out blob fields' slots never travel);\n * `BlobObject.refCount` is recomputed from carried references only.\n *\n * `external` slots reference an unencrypted shared-bucket object that is not in\n * the bundle — their slot metadata travels (so the catalog entry survives) but\n * the bytes do not (a documented v1 limitation; their eTag is `''`).\n */\nexport async function reKeyBlobs(\n vault: Vault,\n closure: Map<string, Set<string>>,\n destDeks: Map<string, EnclaveKey>,\n fieldProjection?: Record<string, readonly string[]>,\n): Promise<ReKeyBlobsResult> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const internal: Record<string, Record<string, EncryptedEnvelope>> = {}\n\n // travel set: eTag → number of carried references (slots + versions) within\n // the closure. Recomputed refCount, NOT the source's (which may count\n // out-of-slice referrers, leaving the adopted blob un-GC-able).\n const carriedRefs = new Map<string, number>()\n const addRef = (eTag: string): void => {\n if (!eTag) return // external slot — no chunk-based blob to carry\n carriedRefs.set(eTag, (carriedRefs.get(eTag) ?? 0) + 1)\n }\n\n const place = (collection: string, id: string, env: EncryptedEnvelope): void => {\n let bucket = internal[collection]\n if (!bucket) { bucket = {}; internal[collection] = bucket }\n bucket[id] = env\n }\n\n // ── Slots + versions (parent-collection-DEK keyed) ─────────────────\n for (const [collectionName, ids] of closure) {\n const destDek = destDeks.get(collectionName)\n if (!destDek) continue\n const srcDek = await getDEK(collectionName)\n const projList = fieldProjection?.[collectionName]\n const proj = projList ? new Set(projList) : undefined\n\n // Slots: one envelope per record, a `{ slotName: SlotRecord }` map.\n const slotsCollection = `${BLOB_SLOTS_PREFIX}${collectionName}`\n for (const id of ids) {\n // #767: carry any in-flight `_blob_intent` marker for this record,\n // re-keyed under the SAME destination DEK as its owning collection\n // (mirrors `dumpVault`'s backup allowlist, which carries `_blob_intent`\n // for the identical crash-recovery reason — a partition extracted\n // mid-shred/mid-rehome must carry the marker so resume-on-touch heals\n // it post-restore). Checked independently of the slot-map fetch below:\n // a marker can be present even when the slot map has already been\n // touched or removed by the in-flight operation.\n const intentId = `${collectionName}::${id}`\n const intentEnv = await adapter.get(vaultName, BLOB_INTENT_COLLECTION, intentId)\n if (intentEnv) {\n const intentPlain = await openEnvelopeJson({ collection: BLOB_INTENT_COLLECTION, id: intentId }, intentEnv, srcDek)\n const intentBody = await writeEnvelopeBody({ collection: BLOB_INTENT_COLLECTION, id: intentId, version: intentEnv._v }, intentPlain, destDek)\n place(BLOB_INTENT_COLLECTION, intentId, { ...intentEnv, ...intentBody })\n }\n\n const env = await adapter.get(vaultName, slotsCollection, id)\n if (!env) continue\n // #748 defense-in-depth canary — see the matching one in `reKeyClosure`:\n // `ids` comes from the same tier-gated `closure`, so an elevated\n // record's slot map should never be reachable here. Unreachable by\n // construction; a cheap tripwire, not a user-facing guard.\n if ((env._tier ?? 0) > 0) {\n throw new PartitionExtractionError(\n `#748: reKeyBlobs fetched an elevated slot-map envelope (collection=\"${slotsCollection}\" `\n + `id=\"${id}\" _tier=${env._tier}) — this is unreachable by construction. Refusing to carry it into a partition.`,\n )\n }\n const slots = JSON.parse(await openEnvelopeJson({ collection: slotsCollection, id }, env, srcDek)) as Record<string, SlotRecord>\n // FR-7: drop projected-out blob fields' slots (slot names are blob field\n // names) — their eTags then never enter the travel set.\n const kept: Record<string, SlotRecord> = {}\n for (const [slotName, slot] of Object.entries(slots)) {\n if (proj && !proj.has(slotName)) continue\n // #769: strip the internal `pendingRelease` resume breadcrumb before\n // it travels. It is a SOURCE-VAULT-LOCAL rehome marker (see\n // `SlotRecord.pendingRelease`'s doc comment) pointing at an old eTag\n // awaiting release — meaningless, and potentially misleading, once\n // detached from the source vault (the eTag it names may be absent\n // from the destination). Unlike `_blob_intent` above (carried, so\n // resume-on-touch heals it post-restore), this breadcrumb must NOT\n // travel into a cross-vault partition.\n const { pendingRelease, ...strippedSlot } = slot\n void pendingRelease\n kept[slotName] = strippedSlot\n addRef(slot.eTag)\n }\n if (Object.keys(kept).length === 0) continue\n const { iv, data } = await encrypt(JSON.stringify(kept), destDek, recordAadFor({ collection: slotsCollection, id }, env))\n place(slotsCollection, id, { ...env, _iv: iv, _data: data })\n }\n\n // Versions: key = `${recordId}::${slotName}::${label}`; each an independent\n // refCount hold on its eTag.\n const versionsCollection = `${BLOB_VERSIONS_PREFIX}${collectionName}`\n const versionKeys = await adapter.list(vaultName, versionsCollection)\n for (const key of versionKeys) {\n const [recordId, slotName] = key.split('::')\n if (recordId === undefined || !ids.has(recordId)) continue\n if (proj && slotName !== undefined && !proj.has(slotName)) continue\n const env = await adapter.get(vaultName, versionsCollection, key)\n if (!env) continue\n // #748 defense-in-depth canary — see `reKeyClosure`'s. Unreachable by\n // construction; a cheap tripwire, not a user-facing guard.\n if ((env._tier ?? 0) > 0) {\n throw new PartitionExtractionError(\n `#748: reKeyBlobs fetched an elevated version-record envelope (collection=\"${versionsCollection}\" `\n + `key=\"${key}\" _tier=${env._tier}) — this is unreachable by construction. Refusing to carry it into a partition.`,\n )\n }\n const record = JSON.parse(await openEnvelopeJson({ collection: versionsCollection, id: key }, env, srcDek)) as VersionRecord\n addRef(record.eTag)\n const { iv, data } = await encrypt(JSON.stringify(record), destDek, recordAadFor({ collection: versionsCollection, id: key }, env))\n place(versionsCollection, key, { ...env, _iv: iv, _data: data })\n }\n }\n\n // No chunk-based blob in the closure → carry nothing, mint nothing. Guarded\n // so `getDEK('_blob')` does not auto-mint + persist a phantom DEK on the\n // source keyring (mirrors the carryLedger non-destructive guard).\n if (carriedRefs.size === 0) return { internal }\n\n // ── Index + chunks (re-keyed under a FRESH transfer `_blob` DEK) ────\n const srcBlobDek = await getDEK(BLOB_COLLECTION)\n const transferBlobDek = await generateDEK()\n\n for (const [eTag, refCount] of carriedRefs) {\n const idxEnv = await adapter.get(vaultName, BLOB_INDEX_COLLECTION, eTag)\n if (!idxEnv) continue // dangling slot reference — nothing to carry\n const blob = JSON.parse(await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, idxEnv, srcBlobDek)) as BlobObject\n\n // Resolve the per-blob content CEK; passthrough vs. in-bundle promotion.\n let contentCek: EnclaveKey\n let chunksPassthrough: boolean\n if (blob._cek !== undefined) {\n contentCek = await unwrapCek(blob._cek, srcBlobDek)\n chunksPassthrough = true\n } else {\n // Legacy: promote to per-blob-CEK for the bundle (source untouched).\n contentCek = await generateDEK()\n chunksPassthrough = false\n }\n\n // Chunks: verbatim for an already-erasable blob; decrypt-then-re-encrypt\n // under the fresh content CEK for a legacy blob (same eTag-bound AAD).\n for (let i = 0; i < blob.chunkCount; i++) {\n const chunkId = `${eTag}_${i}`\n const chunkEnv = await adapter.get(vaultName, BLOB_CHUNKS_COLLECTION, chunkId)\n if (!chunkEnv) {\n throw new PartitionExtractionError(\n `reKeyBlobs: blob chunk ${i}/${blob.chunkCount} missing for eTag \"${eTag}\"; `\n + `cannot carry an incomplete blob into the partition.`,\n )\n }\n if (chunksPassthrough) {\n place(BLOB_CHUNKS_COLLECTION, chunkId, chunkEnv)\n } else {\n const aad = chunkAAD(eTag, i, blob.chunkCount)\n const plain = await decryptBytesWithAAD(chunkEnv._iv, chunkEnv._data, srcBlobDek, aad)\n const { iv, data } = await encryptBytesWithAAD(plain, contentCek, aad)\n place(BLOB_CHUNKS_COLLECTION, chunkId, { ...chunkEnv, _iv: iv, _data: data })\n }\n }\n\n // Index: per-blob-CEK mode under the transfer DEK, refCount corrected.\n // Drop any transient `_cekPending` (never set on a settled blob — possible\n // only if the source is mid-migration; we set a fresh settled `_cek`).\n const { _cekPending, ...rest } = blob\n void _cekPending\n const carried: BlobObject = { ...rest, refCount, _cek: await wrapCek(contentCek, transferBlobDek) }\n const { iv, data } = await encrypt(JSON.stringify(carried), transferBlobDek, recordAadFor({ collection: BLOB_INDEX_COLLECTION, id: eTag }, idxEnv))\n place(BLOB_INDEX_COLLECTION, eTag, { ...idxEnv, _iv: iv, _data: data })\n }\n\n return { internal, blobDek: transferBlobDek }\n}\n\n/** A minted transfer key (raw 32 bytes) + the seal carrying the DEK set. */\nexport interface SealResult {\n readonly seal: TransferSealPayload\n readonly transferKey: Uint8Array\n}\n\n/**\n * Mint a random 32-byte transfer key, export each DEK to raw bytes, and\n * AES-256-GCM-seal the `{ collection: base64(rawDEK) }` map under the\n * transfer key. The transfer key is returned to the caller out-of-band;\n * only the sealed bytes travel in the bundle. Layout: iv(12) ‖ ct ‖ tag.\n */\nexport async function sealDeks(deks: Map<string, EnclaveKey>): Promise<SealResult> {\n const dekMap: Record<string, string> = {}\n for (const [collection, dek] of deks) {\n const raw = await crypto.subtle.exportKey('raw', dek)\n dekMap[collection] = bufferToBase64(raw)\n }\n\n const transferKey = crypto.getRandomValues(new Uint8Array(32))\n const key = await crypto.subtle.importKey('raw', transferKey, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = new TextEncoder().encode(JSON.stringify(dekMap))\n const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext)\n\n const combined = new Uint8Array(iv.byteLength + ct.byteLength)\n combined.set(iv, 0)\n combined.set(new Uint8Array(ct), iv.byteLength)\n\n const sealId = bufferToBase64(crypto.getRandomValues(new Uint8Array(12)))\n return {\n seal: { v: 1, alg: 'aes-256-gcm-pre-shared', sealId, payload: bufferToBase64(combined) },\n transferKey,\n }\n}\n\nexport interface ExtractPartitionResult {\n readonly bundleBytes: Uint8Array\n /** Raw 32-byte transfer key — deliver out-of-band; required to adopt. */\n readonly transferKey: Uint8Array\n readonly sealId: string\n /**\n * #759: outbound FK edges whose referenced parent was excluded from the\n * partition (missing, or tier-elevated and therefore invisible). The\n * child record still carries its FK value; the parent did not travel —\n * callers should surface this to the operator rather than assume\n * referential completeness.\n */\n readonly danglingRefs: ReadonlyArray<DanglingRefNotice>\n}\n\n/**\n * Extract a re-keyed, transfer-sealed partition. Owner-only\n * (invariant 5): producing a standalone re-keyed vault is an\n * ownership operation. Non-destructive on the source.\n */\nexport type ExtractPartitionOptions = WalkClosureOptions & {\n readonly compression?: 'auto' | 'brotli' | 'gzip' | 'none'\n readonly carrySchemas?: boolean\n readonly carryLedger?: boolean\n /**\n * FR-7 structural field projection: per-collection allow-list of fields\n * to keep. Non-listed fields are dropped from each record BEFORE\n * re-encryption (so they never travel in the bundle); `id` is always\n * preserved. A projected collection's persisted schema is NOT carried.\n * Absent/empty → un-projected behavior (byte-identical to today).\n */\n readonly fieldProjection?: Record<string, readonly string[]>\n}\n\n/**\n * Public extract-partition entry — gated behind `cargoStrategy: withCargo()`\n * (S4). Routes through the source vault's cargo strategy so an un-opted-in\n * caller hits `NO_CARGO`'s throw; `withCargo()` dynamically imports and runs\n * {@link extractPartitionCore} (the crypto engine, kept out of the floor).\n */\nexport async function extractPartition(\n vault: Vault,\n opts: ExtractPartitionOptions,\n): Promise<ExtractPartitionResult> {\n return vault.cargoStrategy.extractPartition(vault, opts)\n}\n\n/**\n * The extract-partition crypto engine — reached only when `withCargo()` is\n * opted in (via the lazy `with-cargo/active.ts` chunk). Body unchanged from the\n * hardened implementation; only the public gate wraps it.\n */\nexport async function extractPartitionCore(\n vault: Vault,\n opts: ExtractPartitionOptions,\n): Promise<ExtractPartitionResult> {\n // FR-6: extract-and-sever is the inalienability-floor half — owner-only. A\n // custodian operates fully but must NEVER produce a standalone re-keyed\n // partition (that would let it sever a copy out from under the sealed owner).\n // Explicit assertion so the security boundary is auditable at this site.\n if (vault.role === 'custodian') {\n throw new PartitionExtractionError(\n 'extractPartition is owner-only; a custodian cannot extract-and-sever '\n + '(FR-6: producing a re-keyed standalone partition is an ownership operation; use the Deed owner).',\n )\n }\n if (vault.role !== 'owner') {\n throw new PartitionExtractionError(\n `extractPartition requires the 'owner' role on the source vault; caller is '${vault.role}'. `\n + `Producing a re-keyed standalone partition is an ownership operation.`,\n )\n }\n\n // Persisted-schema writes (collection({ persistJsonSchema: true })) are fire-\n // and-forget queued onto vault._pendingSchemaWrites — a caller that does\n // `collection() → put() → extractPartition({ carrySchemas: true })` in quick\n // succession can hit a window where _schemas/<col> is not yet on disk and\n // reKeySchemas silently drops the row. Drain BEFORE reKeySchemas reads.\n if (opts.carrySchemas) await vault._drainPendingSchemaWrites()\n\n const { closure, danglingRefs } = await walkClosure(vault, opts)\n const { collections, deks } = await reKeyClosure(vault, closure, opts.fieldProjection)\n\n // carryLedger: mint a fresh _ledger DEK, build the carried chain, and\n // SEAL the ledger DEK alongside the data DEKs so owner-creation wraps it into the\n // recipient keyring (lets them decrypt + verify the chain). Must run BEFORE\n // sealDeks.\n let ledgerHead: { hash: string; index: number; ts: string } | undefined\n let ledgerEntries: Record<string, EncryptedEnvelope> | undefined\n if (opts.carryLedger && vault._getLedgerOrNull() !== null) {\n // Skip when the source vault has no history strategy: reKeyLedger's first\n // `getDEK(LEDGER_COLLECTION)` would auto-mint and persist a phantom\n // _ledger DEK on the source keyring (contradicting \"non-destructive on\n // the source\"), and there's nothing to carry anyway. Mirrors the same\n // null-guard the source audit-append uses below.\n const ledgerDek = await generateDEK()\n const built = await reKeyLedger(vault, closure, collections, ledgerDek)\n if (built.head.index >= 0) {\n ledgerEntries = built.entries\n ledgerHead = built.head\n deks.set(LEDGER_COLLECTION, ledgerDek)\n }\n }\n\n // Carry the slice's blobs — re-keyed under a FRESH transfer `_blob` DEK\n // (HARDENED: never carries the source's shared blob DEK). Runs BEFORE\n // sealDeks so the fresh DEK is sealed alongside the data DEKs; non-destructive\n // on the source (mints + reads `_blob` only when the closure has blobs).\n const blobs = await reKeyBlobs(vault, closure, deks, opts.fieldProjection)\n if (blobs.blobDek) deks.set(BLOB_COLLECTION, blobs.blobDek)\n\n // Build _internal (schemas + ledger + blobs). reKeySchemas reads data-\n // collection DEKs only, so it is unaffected by the _ledger DEK added above.\n const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks, opts.fieldProjection) : {}\n const internal: Record<string, Record<string, EncryptedEnvelope>> = {}\n if (Object.keys(internalSchemas).length > 0) internal[SCHEMAS_COLLECTION] = internalSchemas\n if (ledgerEntries) internal[LEDGER_COLLECTION] = ledgerEntries\n for (const [collection, records] of Object.entries(blobs.internal)) internal[collection] = records\n const hasInternal = Object.keys(internal).length > 0\n\n const { seal, transferKey } = await sealDeks(deks)\n\n // Source-side audit: record that a partition\n // was handed over. Non-destructive — an audit append, no record touched.\n // No-op when the source vault has no history strategy. append() fills\n // index/prevHash/ts and (since actor is '') the ledger's configured actor.\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle',\n collection: '',\n id: '',\n version: 0,\n actor: '',\n payloadHash: '',\n reason: `partition-handed-over:${seal.sealId}`,\n })\n\n // Build the dump JSON: unowned (empty keyrings), empty ledger (default),\n // re-keyed collections only.\n const { name: vaultName } = vault._introspectState()\n const backup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: vaultName,\n _exported_at: new Date().toISOString(),\n _exported_by: '', // unowned — no source user travels\n keyrings: {},\n collections,\n ...(hasInternal ? { _internal: internal } : {}),\n ...(ledgerHead ? { ledgerHead: { hash: ledgerHead.hash, index: ledgerHead.index, ts: ledgerHead.ts } } : {}),\n }\n const bodyJsonStr = JSON.stringify(buildExtractedPartitionWrapper(JSON.stringify(backup), seal))\n\n // An extracted partition is a NEW vault, not a re-export of the source —\n // mint a fresh handle rather than reusing the source's stable ULID\n // (which would collide if a recipient imports both source + partition).\n const handle = generateULID()\n const bundleBytes = await assemblePodContainer({\n handle,\n bodyJsonStr,\n compression: opts.compression,\n headerExtras: {\n bundleKind: 'extracted-partition',\n transferSeal: { v: seal.v, alg: seal.alg, sealId: seal.sealId }, // indicator only\n },\n })\n\n return { bundleBytes, transferKey, sealId: seal.sealId, danglingRefs }\n}\n"],"mappings":"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","names":["plaintext","iv","data"]}
|
|
@@ -3,22 +3,22 @@ import {
|
|
|
3
3
|
} from "./chunk-EPFF7LOX.js";
|
|
4
4
|
import {
|
|
5
5
|
loadFence
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FFSQFH6D.js";
|
|
7
7
|
import {
|
|
8
8
|
SCHEMAS_COLLECTION,
|
|
9
9
|
loadPersistedSchema
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-6WO62AJW.js";
|
|
11
11
|
import {
|
|
12
12
|
canonicalJson,
|
|
13
13
|
sha256Hex
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ZLHHE5XZ.js";
|
|
15
15
|
import {
|
|
16
16
|
openEnvelopeJson,
|
|
17
17
|
writeEnvelopeBody
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-OHOKBMTM.js";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
buildSealedRecordEnvelope
|
|
21
|
+
} from "./chunk-PENPHA45.js";
|
|
22
22
|
|
|
23
23
|
// src/with-shape/manifest/types.ts
|
|
24
24
|
var MANIFEST_SCHEMA_RECORD_ID = "schema";
|
|
@@ -29,7 +29,7 @@ async function loadSchemaManifestEntry(store, vault, getDEK) {
|
|
|
29
29
|
if (!envelope) return void 0;
|
|
30
30
|
const dek = await getDEK(MANIFEST_COLLECTION);
|
|
31
31
|
try {
|
|
32
|
-
const plaintext = await openEnvelopeJson(envelope, dek);
|
|
32
|
+
const plaintext = await openEnvelopeJson({ collection: MANIFEST_COLLECTION, id: MANIFEST_SCHEMA_RECORD_ID }, envelope, dek);
|
|
33
33
|
const parsed = JSON.parse(plaintext);
|
|
34
34
|
if (parsed.v !== 1 || parsed.kind !== "schema") return void 0;
|
|
35
35
|
return { version: envelope._v, manifest: parsed };
|
|
@@ -40,13 +40,11 @@ async function loadSchemaManifestEntry(store, vault, getDEK) {
|
|
|
40
40
|
async function saveSchemaManifest(store, vault, manifest, expectedVersion, getDEK) {
|
|
41
41
|
const dek = await getDEK(MANIFEST_COLLECTION);
|
|
42
42
|
const json = JSON.stringify(manifest);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...body
|
|
49
|
-
};
|
|
43
|
+
const env = await buildSealedRecordEnvelope(
|
|
44
|
+
{ collection: MANIFEST_COLLECTION, id: MANIFEST_SCHEMA_RECORD_ID, version: expectedVersion + 1 },
|
|
45
|
+
(identity) => writeEnvelopeBody(identity, json, dek),
|
|
46
|
+
{}
|
|
47
|
+
);
|
|
50
48
|
await store.put(vault, MANIFEST_COLLECTION, MANIFEST_SCHEMA_RECORD_ID, env, expectedVersion);
|
|
51
49
|
}
|
|
52
50
|
async function computeAggregateHash(collections) {
|
|
@@ -88,4 +86,4 @@ export {
|
|
|
88
86
|
saveSchemaManifest,
|
|
89
87
|
deriveSchemaManifest
|
|
90
88
|
};
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
89
|
+
//# sourceMappingURL=chunk-BEZDLIDG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-shape/manifest/types.ts","../src/with-shape/manifest/storage.ts","../src/with-shape/manifest/derive.ts"],"sourcesContent":["/**\n * The pod-wide **schema manifest** record type (#941).\n *\n * This is an INDEX, not a full-inline manifest: `SchemaManifest.collections`\n * carries per-collection *metadata* (generation / content hash / field ids),\n * never the JSON Schema body itself. The JSON Schema stays the source of\n * truth at `_schemas/<collection>`, encrypted under that collection's own\n * DEK (see `with-shape/persisted-schemas`). Inlining full schemas into one\n * pod-wide manifest record would either leak every collection's schema to a\n * principal scoped to a single collection, or force the manifest itself\n * onto a collection-DEK it has no business holding — the index preserves\n * per-collection DEK isolation while still giving a single place to answer\n * \"what schema generation is this pod at, and does collection X's stored\n * schema match its declared content hash.\"\n *\n * `aggregateHash` binds the whole per-collection index's content: it is\n * `sha256Hex(canonicalJson(collections))`, so any change to any collection's\n * entry (a new generation, a changed hash, added/renamed field ids) changes\n * `aggregateHash` too. Canonical JSON (sorted object keys, no whitespace)\n * makes the hash independent of the map's key insertion order.\n *\n * @module\n */\n\n/** Per-collection entry inside a {@link SchemaManifest}'s `collections` index. */\nexport interface SchemaManifestEntry {\n /** The vault-wide schema-fence generation this collection's schema was last written at. */\n readonly generation: number\n /** SHA-256 (hex) of the collection's canonicalised JSON Schema (see `persisted-schemas/derive.ts`). */\n readonly contentHash: string\n /** Stable per-field ids (#946), keyed by the field's current name. Absent when the collection has none. */\n readonly fieldIds?: Record<string, string>\n}\n\n/**\n * The pod-wide schema manifest — one record per pod, stored at\n * `_manifest/schema` (see `storage.ts`). An INDEX over the per-collection\n * `_schemas/<collection>` entries, not the schemas themselves.\n */\nexport interface SchemaManifest {\n readonly v: 1\n readonly kind: 'schema'\n /** Pod-wide schema-fence generation (`FenceDoc.currentSchemaVersion`) as of this manifest's derivation. */\n readonly generation: number\n /** Per-collection index: collection name → its manifest entry. */\n readonly collections: Record<string, SchemaManifestEntry>\n /** `sha256Hex(canonicalJson(collections))` — the pod-wide content binding over the index. */\n readonly aggregateHash: string\n}\n\n/** The record id the schema manifest is stored under inside `_manifest`. */\nexport const MANIFEST_SCHEMA_RECORD_ID = 'schema' as const\n","/**\n * Read / write the pod-wide `_manifest/schema` envelope.\n *\n * Mirrors `with-shape/persisted-schemas/storage.ts`'s shape: the same\n * standard noy-db record envelope, AES-GCM encrypted with a collection DEK\n * (here, the `_manifest` collection's own DEK — the manifest INDEX\n * discloses collection names + hashes + generations + field ids, metadata\n * rather than schema bodies, so it gets the same encryption grain as\n * `_schemas` itself).\n *\n * Storage layout:\n *\n * <vault>/_manifest/schema → EncryptedEnvelope\n *\n * Unlike `persisted-schemas/storage.ts` (which takes an already-resolved\n * `EnclaveKey`), these functions take a `getDEK` resolver and call\n * `getDEK(MANIFEST_COLLECTION)` themselves — there is exactly one manifest\n * record per pod, so there is no per-collection DEK the caller needs to\n * pick; resolving it here keeps every manifest call-site symmetric.\n *\n * @module\n */\n\nimport { buildSealedRecordEnvelope, openEnvelopeJson, writeEnvelopeBody, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { canonicalJson, sha256Hex } from '../../with-commit/history/ledger/index.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { MANIFEST_COLLECTION } from './reserved-collections.js'\nimport { MANIFEST_SCHEMA_RECORD_ID, type SchemaManifest, type SchemaManifestEntry } from './types.js'\n\n/** A resolver for a collection's DEK — the same shape as `Vault`'s internal `getDEK`. */\nexport type GetManifestDEK = (collectionName: string) => Promise<EnclaveKey>\n\n/**\n * A decrypted schema-manifest read paired with the wrapping envelope's `_v`\n * — the optimistic-concurrency token. Pass `version` back to\n * {@link saveSchemaManifest} (via `writeSchemaManifest`'s `expectedVersion`)\n * to make the write a strict CAS.\n */\nexport interface LoadedSchemaManifest {\n /** Wrapping envelope `_v`. */\n readonly version: number\n /** Decrypted schema manifest. */\n readonly manifest: SchemaManifest\n}\n\n/**\n * Read the `_manifest/schema` envelope together with its wrapping `_v`.\n * Returns `undefined` when no manifest has ever been written, when the\n * envelope fails to decrypt (wrong DEK), or when it doesn't parse as a\n * `v: 1, kind: 'schema'` manifest.\n */\nexport async function loadSchemaManifestEntry(\n store: NoydbStore,\n vault: string,\n getDEK: GetManifestDEK,\n): Promise<LoadedSchemaManifest | undefined> {\n const envelope = await store.get(vault, MANIFEST_COLLECTION, MANIFEST_SCHEMA_RECORD_ID)\n if (!envelope) return undefined\n const dek = await getDEK(MANIFEST_COLLECTION)\n try {\n const plaintext = await openEnvelopeJson({ collection: MANIFEST_COLLECTION, id: MANIFEST_SCHEMA_RECORD_ID }, envelope, dek)\n const parsed = JSON.parse(plaintext) as SchemaManifest\n if (parsed.v !== 1 || parsed.kind !== 'schema') return undefined\n return { version: envelope._v, manifest: parsed }\n } catch {\n return undefined\n }\n}\n\n/**\n * Encrypt and persist the schema manifest as a strict CAS write:\n * `expectedVersion` must match the stored envelope's `_v` (0 when no\n * envelope exists yet) or `store.put` throws `ConflictError` — surfaced by\n * `writer.ts`'s `writeSchemaManifest` as `ManifestConflictError` rather than\n * retried.\n */\nexport async function saveSchemaManifest(\n store: NoydbStore,\n vault: string,\n manifest: SchemaManifest,\n expectedVersion: number,\n getDEK: GetManifestDEK,\n): Promise<void> {\n const dek = await getDEK(MANIFEST_COLLECTION)\n const json = JSON.stringify(manifest)\n const env = await buildSealedRecordEnvelope(\n { collection: MANIFEST_COLLECTION, id: MANIFEST_SCHEMA_RECORD_ID, version: expectedVersion + 1 },\n (identity) => writeEnvelopeBody(identity, json, dek),\n {},\n )\n await store.put(vault, MANIFEST_COLLECTION, MANIFEST_SCHEMA_RECORD_ID, env, expectedVersion)\n}\n\n/**\n * `sha256Hex(canonicalJson(collections))` — the pod-wide content binding\n * over a schema manifest's per-collection index. Canonical JSON sorts\n * object keys at every depth, so the result is independent of the map's\n * key insertion order. Shared by the writer's tests and (#941 Task 3) the\n * derivation path, so the hash is computed identically everywhere.\n */\nexport async function computeAggregateHash(\n collections: Record<string, SchemaManifestEntry>,\n): Promise<string> {\n return sha256Hex(canonicalJson(collections))\n}\n","/**\n * Derive the pod-wide {@link SchemaManifest} from the per-collection\n * `_schemas/<collection>` records — the source of truth (#941 Task 3).\n *\n * The manifest is an INDEX, never a cache with independent state: every\n * field in a {@link SchemaManifestEntry} is a direct projection of the\n * matching `PersistedSchemaEnvelope` (`hash` → `contentHash`, `fieldIds` →\n * `fieldIds`, `generation` → `generation`, falling back to the live fence's\n * `currentSchemaVersion` for a legacy pre-#946 envelope that never got a\n * generation stamp). Re-running this function against the same stored state\n * always produces the same manifest — that's what makes round-trip identity\n * (AC #2: dump → restore → re-derive matches the original) hold for free,\n * with no special-cased restore logic.\n *\n * A collection whose `_schemas/<collection>` envelope decrypts successfully\n * but has no derivable content (`hash === null` — a stub envelope for a\n * non-Zod validator) is legitimately omitted from the index: there is no\n * content hash to bind.\n *\n * ## Scoped principals: \"cannot decrypt\" is NOT \"doesn't exist\"\n *\n * A `_schemas/<collection>` envelope is encrypted under **that data\n * collection's own DEK** — the same key its records use — NOT a shared\n * `_schemas`-wide key. In a team vault, a collection-scoped grantee (e.g.\n * `grant(..., { role: 'operator', permissions: { invoices: 'rw' } })`) holds\n * only the DEKs their `permissions` name, plus every system-prefixed (`_*`)\n * collection's DEK — `_manifest` included, by `grant()`'s \"propagate system\n * collections to every role\" rule (`keyring.ts`). So a member scoped to\n * `invoices` CAN decrypt `_manifest/schema` (the pod-wide index) but CANNOT\n * decrypt a sibling's `_schemas/customers` envelope — even though that\n * envelope is right there in `store.list(vault, SCHEMAS_COLLECTION)`.\n *\n * `lookupDEK` (the {@link LookupDEK} passed in) reflects this: it returns\n * `undefined` for a collection name whose DEK the caller does not hold —\n * deliberately NEVER minting a fresh one (see the module doc on\n * `LookupDEK` for why). This function distinguishes that case\n * (`undecodableCollections`) from \"the schema envelope legitimately has no\n * derivable content\" (`hash === null`) — the caller (`sync.ts`) MUST treat\n * an incomplete view differently: writing a manifest derived from a\n * partial view would silently drop every collection this principal can't\n * see. See `sync.ts`'s module doc for how the sync path handles this.\n *\n * @module\n */\n\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport { loadFence } from '../schema-update/fence.js'\nimport { loadPersistedSchema, SCHEMAS_COLLECTION } from '../persisted-schemas/storage.js'\nimport { computeAggregateHash } from './storage.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { SchemaManifest, SchemaManifestEntry } from './types.js'\n\n/**\n * A NON-MINTING DEK lookup: returns the collection's DEK if the caller\n * already holds it, `undefined` otherwise. Deliberately distinct from\n * `storage.ts`'s `GetManifestDEK` (which mints+persists a fresh DEK for an\n * absent collection — correct for a caller about to WRITE that collection,\n * e.g. `_manifest` itself). `deriveSchemaManifest` only ever READS sibling\n * collections it never created and has no business minting keys for — an\n * absent DEK here means \"not my collection to see,\" never \"new collection\n * that needs a key.\" Minting here would be worse than a no-op: it would\n * silently persist a garbage DEK into the caller's OWN `_keyring/<user>`\n * file (via `ensureCollectionDEK`'s `persistKeyring` side effect) that\n * still can't decrypt anything — see `sync.ts` / `with-pod/open.ts`'s\n * fix notes (#941 review).\n */\nexport type LookupDEK = (collectionName: string) => Promise<EnclaveKey | undefined>\n\nexport interface DeriveSchemaManifestResult {\n /** The derived manifest — an index over every collection this principal could decrypt. */\n readonly manifest: SchemaManifest\n /**\n * Names present in `_schemas` (per `store.list`) whose envelope this\n * principal could NOT decrypt — either no DEK held for that collection,\n * or the DEK held failed to decrypt/parse it (treated the same: the\n * caller cannot verify what's there, so it cannot safely be omitted or\n * asserted). Empty for a principal with full visibility (typically the\n * owner/admin/custodian). Non-empty means `manifest.collections` is\n * DEFINITELY INCOMPLETE relative to the pod's true collection set —\n * see `sync.ts` for why that must gate whether the manifest gets written.\n */\n readonly undecodableCollections: readonly string[]\n}\n\n/**\n * Read the fence generation + every `_schemas/<collection>` envelope this\n * principal can decrypt, and project them into a fresh {@link SchemaManifest}.\n * `lookupDEK` is called once per collection found under `_schemas` (never\n * mints — see {@link LookupDEK}). Names whose DEK isn't held, or whose\n * envelope fails to decrypt/parse despite holding a DEK, are reported via\n * `undecodableCollections` rather than silently dropped from the result.\n */\nexport async function deriveSchemaManifest(\n store: NoydbStore,\n vault: string,\n lookupDEK: LookupDEK,\n): Promise<DeriveSchemaManifestResult> {\n const fence = await loadFence(store, vault)\n const collectionNames = await store.list(vault, SCHEMAS_COLLECTION)\n\n const collections: Record<string, SchemaManifestEntry> = {}\n const undecodableCollections: string[] = []\n for (const name of collectionNames) {\n const dek = await lookupDEK(name)\n if (dek === undefined) {\n undecodableCollections.push(name) // present in _schemas, but not decodable by this principal\n continue\n }\n\n const payload = await loadPersistedSchema(store, vault, name, dek)\n if (!payload) {\n // Held a DEK but still couldn't decrypt/parse — cannot distinguish\n // \"wrong key after all\" from real corruption from here; treat as\n // undecodable (never silently drop) rather than \"no content.\"\n undecodableCollections.push(name)\n continue\n }\n if (payload.hash === null) continue // legitimately no derivable content (stub envelope)\n\n const entry: SchemaManifestEntry = {\n generation: payload.generation ?? fence.currentSchemaVersion,\n contentHash: payload.hash,\n }\n collections[name] = payload.fieldIds !== undefined ? { ...entry, fieldIds: payload.fieldIds } : entry\n }\n\n const aggregateHash = await computeAggregateHash(collections)\n const manifest: SchemaManifest = { v: 1, kind: 'schema', generation: fence.currentSchemaVersion, collections, aggregateHash }\n return { manifest, undecodableCollections }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDO,IAAM,4BAA4B;;;ACAzC,eAAsB,wBACpB,OACA,OACA,QAC2C;AAC3C,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,qBAAqB,yBAAyB;AACtF,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,MAAM,MAAM,OAAO,mBAAmB;AAC5C,MAAI;AACF,UAAM,YAAY,MAAM,iBAAiB,EAAE,YAAY,qBAAqB,IAAI,0BAA0B,GAAG,UAAU,GAAG;AAC1H,UAAM,SAAS,KAAK,MAAM,SAAS;AACnC,QAAI,OAAO,MAAM,KAAK,OAAO,SAAS,SAAU,QAAO;AACvD,WAAO,EAAE,SAAS,SAAS,IAAI,UAAU,OAAO;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,mBACpB,OACA,OACA,UACA,iBACA,QACe;AACf,QAAM,MAAM,MAAM,OAAO,mBAAmB;AAC5C,QAAM,OAAO,KAAK,UAAU,QAAQ;AACpC,QAAM,MAAM,MAAM;AAAA,IAChB,EAAE,YAAY,qBAAqB,IAAI,2BAA2B,SAAS,kBAAkB,EAAE;AAAA,IAC/F,CAAC,aAAa,kBAAkB,UAAU,MAAM,GAAG;AAAA,IACnD,CAAC;AAAA,EACH;AACA,QAAM,MAAM,IAAI,OAAO,qBAAqB,2BAA2B,KAAK,eAAe;AAC7F;AASA,eAAsB,qBACpB,aACiB;AACjB,SAAO,UAAU,cAAc,WAAW,CAAC;AAC7C;;;ACZA,eAAsB,qBACpB,OACA,OACA,WACqC;AACrC,QAAM,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1C,QAAM,kBAAkB,MAAM,MAAM,KAAK,OAAO,kBAAkB;AAElE,QAAM,cAAmD,CAAC;AAC1D,QAAM,yBAAmC,CAAC;AAC1C,aAAW,QAAQ,iBAAiB;AAClC,UAAM,MAAM,MAAM,UAAU,IAAI;AAChC,QAAI,QAAQ,QAAW;AACrB,6BAAuB,KAAK,IAAI;AAChC;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,oBAAoB,OAAO,OAAO,MAAM,GAAG;AACjE,QAAI,CAAC,SAAS;AAIZ,6BAAuB,KAAK,IAAI;AAChC;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,KAAM;AAE3B,UAAM,QAA6B;AAAA,MACjC,YAAY,QAAQ,cAAc,MAAM;AAAA,MACxC,aAAa,QAAQ;AAAA,IACvB;AACA,gBAAY,IAAI,IAAI,QAAQ,aAAa,SAAY,EAAE,GAAG,OAAO,UAAU,QAAQ,SAAS,IAAI;AAAA,EAClG;AAEA,QAAM,gBAAgB,MAAM,qBAAqB,WAAW;AAC5D,QAAM,WAA2B,EAAE,GAAG,GAAG,MAAM,UAAU,YAAY,MAAM,sBAAsB,aAAa,cAAc;AAC5H,SAAO,EAAE,UAAU,uBAAuB;AAC5C;","names":[]}
|