@noy-db/hub 0.6.0-pre.21 → 0.6.0-pre.23
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 +238 -0
- package/dist/api-ETB5Y4DY.js +20 -0
- package/dist/attestation/index.js +16 -16
- package/dist/{backup-FSC36VWC.js → backup-UOLAXOJ4.js} +10 -10
- package/dist/blobs/index.js +20 -18
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +10 -10
- package/dist/cargo/index.js +28 -28
- package/dist/{chunk-3B6S6RSI.js → chunk-22VRCUV2.js} +2 -2
- package/dist/{chunk-ESKIAXO2.js → chunk-23ODLABM.js} +4 -4
- package/dist/{chunk-IJ46VCWD.js → chunk-2DHEBIJA.js} +2 -2
- package/dist/{chunk-TMBSPLUG.js → chunk-2LHPMFSR.js} +2 -2
- package/dist/{chunk-ONVD6GBY.js → chunk-2QN6NPV6.js} +2 -2
- package/dist/{chunk-NW4TY6VC.js → chunk-3UIS6O6N.js} +3 -3
- package/dist/{chunk-LXECF2ZR.js → chunk-3ZOER5GD.js} +6 -2
- package/dist/chunk-3ZOER5GD.js.map +1 -0
- package/dist/{chunk-EKEIEJL6.js → chunk-4UZDDS4S.js} +3 -3
- package/dist/{chunk-S56VYR4Q.js → chunk-5FRZ7YYU.js} +2 -2
- package/dist/{chunk-H3G4I3PN.js → chunk-5MU7PUNA.js} +2 -2
- package/dist/{chunk-NCCEDL2K.js → chunk-66X4XWTM.js} +6 -6
- package/dist/{chunk-TQPASLY4.js → chunk-6G4NORTR.js} +2 -2
- package/dist/{chunk-RKCRPPNB.js → chunk-6W3BZCW6.js} +3 -3
- package/dist/{chunk-DVRZYAGT.js → chunk-6ZN6X2LW.js} +4 -4
- package/dist/{chunk-PPCOKVFA.js → chunk-6ZTPSVY7.js} +2 -2
- package/dist/{chunk-MZTUTEHP.js → chunk-7DRKK5FQ.js} +3 -3
- package/dist/{chunk-GBBDFE6C.js → chunk-7LWBD3NO.js} +2 -2
- package/dist/{chunk-KX57HODD.js → chunk-7W765AVV.js} +6 -6
- package/dist/{chunk-LN7P6MZB.js → chunk-7ZPAL3F7.js} +2 -2
- package/dist/{chunk-CAGI4LLJ.js → chunk-AK6EWGDO.js} +5 -5
- package/dist/chunk-AK6EWGDO.js.map +1 -0
- package/dist/{chunk-CADUYBQD.js → chunk-ATL3ZVXT.js} +2 -2
- package/dist/{chunk-RSXQNFGO.js → chunk-ATSIGJRX.js} +2 -2
- package/dist/{chunk-MSLPDFK2.js → chunk-AZPPARMG.js} +6 -6
- package/dist/{chunk-NR2SUJ3H.js → chunk-B3X6S37C.js} +2 -2
- package/dist/{chunk-GWO2IDAB.js → chunk-BNGGSNO7.js} +2 -2
- package/dist/{chunk-JLYQQNY4.js → chunk-BYX3AUXH.js} +80 -22
- package/dist/chunk-BYX3AUXH.js.map +1 -0
- package/dist/{chunk-INMHEMXX.js → chunk-C7B3XUI4.js} +2 -2
- package/dist/{chunk-D3R7Q4QK.js → chunk-CJU3KXHJ.js} +2 -2
- package/dist/{chunk-RFO4UXKQ.js → chunk-CWYTN4IK.js} +5 -5
- package/dist/{chunk-HR5R5JOM.js → chunk-D5DZ4R7X.js} +2 -2
- package/dist/{chunk-6YD5SYQS.js → chunk-DEKA2MV3.js} +2 -2
- package/dist/{chunk-SYS46BL7.js → chunk-DG4KFJNA.js} +2 -2
- package/dist/{chunk-P2MR5UNE.js → chunk-DRVF2YS5.js} +55 -16
- package/dist/chunk-DRVF2YS5.js.map +1 -0
- package/dist/{chunk-B6IYATME.js → chunk-DXJXVWWE.js} +6 -6
- package/dist/{chunk-O742QBZZ.js → chunk-DZBZCFS7.js} +5 -5
- package/dist/{chunk-FW7NGYLT.js → chunk-FNCX4N7X.js} +4 -4
- package/dist/{chunk-OHOKBMTM.js → chunk-FUE2KPKM.js} +6 -6
- package/dist/{chunk-YALD3SST.js → chunk-G4577GK7.js} +4 -4
- package/dist/{chunk-DIZ6HM7Q.js → chunk-G6DAESDS.js} +5 -5
- package/dist/{chunk-HYYVT2KZ.js → chunk-GAZTPQ3D.js} +2 -2
- package/dist/{chunk-LX46B56Q.js → chunk-GJ5TMHKC.js} +4 -4
- package/dist/{chunk-KRVS7YWY.js → chunk-GNSLP7KA.js} +2 -2
- package/dist/{chunk-CWM4GQD2.js → chunk-HKANJAOP.js} +28 -5
- package/dist/chunk-HKANJAOP.js.map +1 -0
- package/dist/{chunk-5Q7AJ7DN.js → chunk-I3D67XD7.js} +4 -4
- package/dist/{chunk-GQ3BX5EA.js → chunk-I3ISCYT7.js} +2 -2
- package/dist/{chunk-HQSPF5IY.js → chunk-ISRAQIYY.js} +5 -5
- package/dist/{chunk-JWMBG5S3.js → chunk-J2KGT2A5.js} +6 -6
- package/dist/{chunk-BEZDLIDG.js → chunk-JMN3I4OC.js} +4 -4
- package/dist/{chunk-7PJ3BKHS.js → chunk-JO4MRPEY.js} +5 -5
- package/dist/{chunk-4N5Q23KV.js → chunk-JXFQEOAQ.js} +5 -5
- package/dist/{chunk-ICDHUUMQ.js → chunk-KF34JCIJ.js} +4 -4
- package/dist/{chunk-6KTVJ5ZZ.js → chunk-KKVVBFKW.js} +3 -3
- package/dist/{chunk-DXKLLKME.js → chunk-L374ZR75.js} +3 -3
- package/dist/{chunk-EES37S2V.js → chunk-L5PTLL7A.js} +3 -3
- package/dist/{chunk-2LFOMUEA.js → chunk-LBDG6FHB.js} +3 -3
- package/dist/{chunk-T7JGWTES.js → chunk-LIWA5ROZ.js} +51 -16
- package/dist/chunk-LIWA5ROZ.js.map +1 -0
- package/dist/{chunk-3O72ZD2E.js → chunk-LPVZDTQF.js} +135 -18
- package/dist/chunk-LPVZDTQF.js.map +1 -0
- package/dist/{chunk-OIBEJN5H.js → chunk-LYQUFIHL.js} +2 -2
- package/dist/{chunk-JNMTN5D5.js → chunk-LZHIYH32.js} +2 -2
- package/dist/{chunk-7KAE7CQ2.js → chunk-M2KVSM43.js} +2 -2
- package/dist/{chunk-IGJCYUT5.js → chunk-NSITFOCJ.js} +1 -1
- package/dist/chunk-NSITFOCJ.js.map +1 -0
- package/dist/{chunk-32B7VCKU.js → chunk-O2A5YCFF.js} +2 -2
- package/dist/{chunk-4A34KWOU.js → chunk-OGB2UTBC.js} +5 -5
- package/dist/{chunk-GQXFOAYZ.js → chunk-OMXUQAZJ.js} +3 -3
- package/dist/{chunk-45YHJ3GV.js → chunk-PFHF3A72.js} +3 -3
- package/dist/{chunk-KV6JM2IA.js → chunk-PUGHLCBW.js} +2 -2
- package/dist/{chunk-ZZXLB6AS.js → chunk-R6BGN6IY.js} +2 -2
- package/dist/{chunk-CLAHBPOI.js → chunk-RBBTJ2HY.js} +3 -3
- package/dist/{chunk-YKXZHUOW.js → chunk-RGUAY5IX.js} +4 -4
- package/dist/{chunk-AU3AUJIZ.js → chunk-RQQIB7CD.js} +2 -2
- package/dist/{chunk-TX5MKAHA.js → chunk-RTKREHPV.js} +2 -2
- package/dist/{chunk-BAHOV5I5.js → chunk-SIACMXFK.js} +8 -8
- package/dist/{chunk-6WO62AJW.js → chunk-SZFNO7L4.js} +3 -3
- package/dist/{chunk-4OQZNXQG.js → chunk-TA67ZQUH.js} +53 -6
- package/dist/chunk-TA67ZQUH.js.map +1 -0
- package/dist/{chunk-U27Y4DJZ.js → chunk-TWELPVXG.js} +3 -3
- package/dist/{chunk-WVE6YKMX.js → chunk-U5ZDYJLQ.js} +3 -3
- package/dist/{chunk-4YL2LMXK.js → chunk-UW5YKCE4.js} +6 -6
- package/dist/{chunk-E5SC6ENM.js → chunk-UWRVDBDC.js} +10 -10
- package/dist/{chunk-GTHVPVAR.js → chunk-VNPSPQNN.js} +10 -10
- package/dist/{chunk-A4TOGAUW.js → chunk-VXH5Z22C.js} +2 -2
- package/dist/{chunk-FS7NTGCJ.js → chunk-WBFJZCDJ.js} +2 -2
- package/dist/{chunk-4RAS6HN6.js → chunk-WBRLSPA7.js} +2 -2
- package/dist/{chunk-AUPSEJ7O.js → chunk-WG66ZPEV.js} +4 -4
- package/dist/{chunk-QSIEL6TL.js → chunk-XXONVJWA.js} +5 -5
- package/dist/{chunk-ZLHHE5XZ.js → chunk-YLIGPHN4.js} +3 -3
- package/dist/{chunk-VVSOPRUE.js → chunk-YNWBBHQK.js} +3 -3
- package/dist/{chunk-I6QKB7MT.js → chunk-Z6UMKUAI.js} +2 -2
- package/dist/{chunk-7LNE2N2V.js → chunk-Z7AHYSZF.js} +5 -5
- package/dist/{chunk-3PMFMUFX.js → chunk-ZA3RBGT3.js} +2 -2
- package/dist/{chunk-LGFTOU27.js → chunk-ZILONQGE.js} +95 -80
- package/dist/chunk-ZILONQGE.js.map +1 -0
- package/dist/{chunk-TDSN2O2V.js → chunk-ZKZXS352.js} +5 -5
- package/dist/{chunk-OMYCSTXL.js → chunk-ZLU3IWHK.js} +3 -3
- package/dist/{chunk-6JPGVL7X.js → chunk-ZYG4UWHF.js} +2 -2
- package/dist/classified/index.js +4 -4
- package/dist/{classified-marker-NA6HUV33.js → classified-marker-RYWYYZDB.js} +3 -3
- package/dist/{collection-facade-VBM35WAH.js → collection-facade-XV3IMJBO.js} +15 -15
- package/dist/{computed-GSTLWSY5.js → computed-M4QZVSPX.js} +3 -3
- package/dist/consent/index.js +7 -7
- package/dist/cover/index.js +7 -7
- package/dist/custody/index.js +9 -9
- package/dist/{dead-filter-UALP5DJX.js → dead-filter-DRKJDNSF.js} +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/{delegation-JLNXM7KH.js → delegation-UEJN2LDC.js} +9 -9
- package/dist/derivations/index.js +10 -10
- package/dist/{derive-YUO3TQYN.js → derive-WIWSPZ5M.js} +8 -8
- package/dist/directory/index.js +8 -8
- package/dist/{dispatch-77CB75KB.js → dispatch-2ZE7Z5V2.js} +16 -16
- package/dist/{dispatch-4ZHLSFZQ.js → dispatch-KNWYDUYM.js} +9 -6
- package/dist/dispatch-KNWYDUYM.js.map +1 -0
- package/dist/{enclave-O4NOROCJ.js → enclave-VNHDWCB6.js} +7 -7
- package/dist/executor-4SMYWPSU.js +29 -0
- package/dist/executor-BJ7PCXCV.js +9 -0
- package/dist/executor-O4UP4M3C.js +9 -0
- package/dist/export-accessible-A3N2V4IX.js +23 -0
- package/dist/extract-partition-3EUJXWYG.js +38 -0
- package/dist/{fanout-sidecar-NLQ7QAYW.js → fanout-sidecar-PBEVZ5IQ.js} +7 -7
- package/dist/find-W2DL2BEE.js +11 -0
- package/dist/forget/index.js +7 -7
- package/dist/guards/index.js +4 -4
- package/dist/history/index.js +11 -11
- package/dist/i18n/index.js +12 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +91 -89
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +4 -4
- package/dist/introspection/index.js +9 -9
- package/dist/issue-NLJ3FCKL.js +19 -0
- package/dist/kernel/collection.d.ts +8 -2
- package/dist/kernel/errors.d.ts +26 -0
- package/dist/kernel/query/builder.d.ts +36 -2
- package/dist/kernel/query/live.d.ts +31 -9
- package/dist/kernel/query/predicate.d.ts +9 -0
- package/dist/kernel/refs.d.ts +3 -1
- package/dist/kernel/via/reconcile.d.ts +12 -0
- package/dist/{ledger-MVPNG5OS.js → ledger-DDIDLWA3.js} +9 -9
- package/dist/liberate-EPTCY3DF.js +24 -0
- package/dist/{link-set-3POMQR3I.js → link-set-2AYA6BEI.js} +9 -9
- package/dist/{managed-secret-IARIJJ7Q.js → managed-secret-6QTGWZTW.js} +7 -7
- package/dist/materialized-views/index.js +20 -20
- package/dist/{migrate-cek-XM2ETTZW.js → migrate-cek-YBNJDJ2Q.js} +2 -2
- package/dist/money/index.js +5 -5
- package/dist/noydb-RFBNRUHH.js +85 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +10 -10
- package/dist/pod/index.js +54 -53
- package/dist/{pod-handle-MELERCKU.js → pod-handle-77YAUM4J.js} +7 -7
- package/dist/policy/index.js +7 -7
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-Z5T7FY45.js → post-register-WBDTOLWD.js} +6 -6
- package/dist/query/index.js +6 -6
- package/dist/reduce/index.js +6 -6
- package/dist/{register-UZ37QPDB.js → register-VSPL7VRW.js} +13 -13
- package/dist/registry-A7JADQXC.js +9 -0
- package/dist/registry-AOPJN2V4.js +18 -0
- package/dist/registry-I4I3QIQ6.js +9 -0
- package/dist/registry-QSMKWF4O.js +19 -0
- package/dist/{request-withdrawal-PVFEYX57.js → request-withdrawal-VL5N623Q.js} +12 -12
- package/dist/{reveal-DQ2E3QAZ.js → reveal-Z2X4JW6E.js} +4 -4
- package/dist/{revoke-YXY6SHUH.js → revoke-XQ5STGT4.js} +9 -9
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +10 -10
- package/dist/search/index.js +4 -4
- package/dist/{seed-DZ4ORU5N.js → seed-WJWCFPZL.js} +9 -9
- package/dist/sequence/index.js +7 -7
- package/dist/session/index.js +7 -7
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-Q7XQBMPA.js → signer-N5TGVOTN.js} +8 -8
- package/dist/snapshots/index.js +8 -8
- package/dist/{stale-NI7SAXAZ.js → stale-MIO5KGEP.js} +8 -8
- package/dist/{storage-3VNGSHXS.js → storage-NLYOMCDM.js} +8 -8
- package/dist/{store-coordination-provider-TYKNR3OU.js → store-coordination-provider-XCX77ZBE.js} +7 -7
- package/dist/sync/index.js +8 -8
- package/dist/team/index.js +12 -12
- package/dist/tiers/index.js +9 -9
- package/dist/to/index.js +1 -1
- package/dist/transactions/index.js +3 -3
- package/dist/util/index.js +1 -1
- package/dist/vault-head/index.js +6 -6
- package/dist/{verify-6J2YLKSO.js → verify-5QI3DGO2.js} +5 -5
- package/dist/via/blob/index.d.ts +3 -0
- package/dist/walk-G5OAA3RE.js +21 -0
- package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -0
- package/dist/with-formula/materialized-views/projection-legs.d.ts +59 -0
- package/dist/with-formula/materialized-views/registry.d.ts +50 -0
- package/dist/with-formula/materialized-views/types.d.ts +68 -10
- package/dist/with-shape/blobs/blob-compaction.d.ts +13 -0
- package/dist/with-shape/blobs/blob-set.d.ts +61 -7
- package/dist/with-shape/blobs/orphan-report.d.ts +62 -0
- package/dist/{withdraw-accessible-4JTDFRFZ.js → withdraw-accessible-H7G2AMSP.js} +11 -11
- package/package.json +3 -3
- package/dist/api-EYNT6KGS.js +0 -20
- package/dist/chunk-3O72ZD2E.js.map +0 -1
- package/dist/chunk-4OQZNXQG.js.map +0 -1
- package/dist/chunk-CAGI4LLJ.js.map +0 -1
- package/dist/chunk-CWM4GQD2.js.map +0 -1
- package/dist/chunk-IGJCYUT5.js.map +0 -1
- package/dist/chunk-JLYQQNY4.js.map +0 -1
- package/dist/chunk-LGFTOU27.js.map +0 -1
- package/dist/chunk-LXECF2ZR.js.map +0 -1
- package/dist/chunk-P2MR5UNE.js.map +0 -1
- package/dist/chunk-T7JGWTES.js.map +0 -1
- package/dist/dispatch-4ZHLSFZQ.js.map +0 -1
- package/dist/executor-CZDXBXKE.js +0 -29
- package/dist/executor-DVJAWN3B.js +0 -9
- package/dist/executor-VRLG4TQJ.js +0 -9
- package/dist/export-accessible-UXSMG7AB.js +0 -23
- package/dist/extract-partition-TVY7AMKH.js +0 -38
- package/dist/find-WSARMJBW.js +0 -11
- package/dist/issue-3JNJNPT3.js +0 -19
- package/dist/liberate-4LOJOLDT.js +0 -24
- package/dist/noydb-IRILUDED.js +0 -84
- package/dist/registry-ETJNA7PO.js +0 -9
- package/dist/registry-F7U6KSLX.js +0 -9
- package/dist/registry-L4ELKX6C.js +0 -18
- package/dist/registry-SDT5LIGO.js +0 -19
- package/dist/walk-WRA7RAPY.js +0 -21
- /package/dist/{api-EYNT6KGS.js.map → api-ETB5Y4DY.js.map} +0 -0
- /package/dist/{backup-FSC36VWC.js.map → backup-UOLAXOJ4.js.map} +0 -0
- /package/dist/{chunk-3B6S6RSI.js.map → chunk-22VRCUV2.js.map} +0 -0
- /package/dist/{chunk-ESKIAXO2.js.map → chunk-23ODLABM.js.map} +0 -0
- /package/dist/{chunk-IJ46VCWD.js.map → chunk-2DHEBIJA.js.map} +0 -0
- /package/dist/{chunk-TMBSPLUG.js.map → chunk-2LHPMFSR.js.map} +0 -0
- /package/dist/{chunk-ONVD6GBY.js.map → chunk-2QN6NPV6.js.map} +0 -0
- /package/dist/{chunk-NW4TY6VC.js.map → chunk-3UIS6O6N.js.map} +0 -0
- /package/dist/{chunk-EKEIEJL6.js.map → chunk-4UZDDS4S.js.map} +0 -0
- /package/dist/{chunk-S56VYR4Q.js.map → chunk-5FRZ7YYU.js.map} +0 -0
- /package/dist/{chunk-H3G4I3PN.js.map → chunk-5MU7PUNA.js.map} +0 -0
- /package/dist/{chunk-NCCEDL2K.js.map → chunk-66X4XWTM.js.map} +0 -0
- /package/dist/{chunk-TQPASLY4.js.map → chunk-6G4NORTR.js.map} +0 -0
- /package/dist/{chunk-RKCRPPNB.js.map → chunk-6W3BZCW6.js.map} +0 -0
- /package/dist/{chunk-DVRZYAGT.js.map → chunk-6ZN6X2LW.js.map} +0 -0
- /package/dist/{chunk-PPCOKVFA.js.map → chunk-6ZTPSVY7.js.map} +0 -0
- /package/dist/{chunk-MZTUTEHP.js.map → chunk-7DRKK5FQ.js.map} +0 -0
- /package/dist/{chunk-GBBDFE6C.js.map → chunk-7LWBD3NO.js.map} +0 -0
- /package/dist/{chunk-KX57HODD.js.map → chunk-7W765AVV.js.map} +0 -0
- /package/dist/{chunk-LN7P6MZB.js.map → chunk-7ZPAL3F7.js.map} +0 -0
- /package/dist/{chunk-CADUYBQD.js.map → chunk-ATL3ZVXT.js.map} +0 -0
- /package/dist/{chunk-RSXQNFGO.js.map → chunk-ATSIGJRX.js.map} +0 -0
- /package/dist/{chunk-MSLPDFK2.js.map → chunk-AZPPARMG.js.map} +0 -0
- /package/dist/{chunk-NR2SUJ3H.js.map → chunk-B3X6S37C.js.map} +0 -0
- /package/dist/{chunk-GWO2IDAB.js.map → chunk-BNGGSNO7.js.map} +0 -0
- /package/dist/{chunk-INMHEMXX.js.map → chunk-C7B3XUI4.js.map} +0 -0
- /package/dist/{chunk-D3R7Q4QK.js.map → chunk-CJU3KXHJ.js.map} +0 -0
- /package/dist/{chunk-RFO4UXKQ.js.map → chunk-CWYTN4IK.js.map} +0 -0
- /package/dist/{chunk-HR5R5JOM.js.map → chunk-D5DZ4R7X.js.map} +0 -0
- /package/dist/{chunk-6YD5SYQS.js.map → chunk-DEKA2MV3.js.map} +0 -0
- /package/dist/{chunk-SYS46BL7.js.map → chunk-DG4KFJNA.js.map} +0 -0
- /package/dist/{chunk-B6IYATME.js.map → chunk-DXJXVWWE.js.map} +0 -0
- /package/dist/{chunk-O742QBZZ.js.map → chunk-DZBZCFS7.js.map} +0 -0
- /package/dist/{chunk-FW7NGYLT.js.map → chunk-FNCX4N7X.js.map} +0 -0
- /package/dist/{chunk-OHOKBMTM.js.map → chunk-FUE2KPKM.js.map} +0 -0
- /package/dist/{chunk-YALD3SST.js.map → chunk-G4577GK7.js.map} +0 -0
- /package/dist/{chunk-DIZ6HM7Q.js.map → chunk-G6DAESDS.js.map} +0 -0
- /package/dist/{chunk-HYYVT2KZ.js.map → chunk-GAZTPQ3D.js.map} +0 -0
- /package/dist/{chunk-LX46B56Q.js.map → chunk-GJ5TMHKC.js.map} +0 -0
- /package/dist/{chunk-KRVS7YWY.js.map → chunk-GNSLP7KA.js.map} +0 -0
- /package/dist/{chunk-5Q7AJ7DN.js.map → chunk-I3D67XD7.js.map} +0 -0
- /package/dist/{chunk-GQ3BX5EA.js.map → chunk-I3ISCYT7.js.map} +0 -0
- /package/dist/{chunk-HQSPF5IY.js.map → chunk-ISRAQIYY.js.map} +0 -0
- /package/dist/{chunk-JWMBG5S3.js.map → chunk-J2KGT2A5.js.map} +0 -0
- /package/dist/{chunk-BEZDLIDG.js.map → chunk-JMN3I4OC.js.map} +0 -0
- /package/dist/{chunk-7PJ3BKHS.js.map → chunk-JO4MRPEY.js.map} +0 -0
- /package/dist/{chunk-4N5Q23KV.js.map → chunk-JXFQEOAQ.js.map} +0 -0
- /package/dist/{chunk-ICDHUUMQ.js.map → chunk-KF34JCIJ.js.map} +0 -0
- /package/dist/{chunk-6KTVJ5ZZ.js.map → chunk-KKVVBFKW.js.map} +0 -0
- /package/dist/{chunk-DXKLLKME.js.map → chunk-L374ZR75.js.map} +0 -0
- /package/dist/{chunk-EES37S2V.js.map → chunk-L5PTLL7A.js.map} +0 -0
- /package/dist/{chunk-2LFOMUEA.js.map → chunk-LBDG6FHB.js.map} +0 -0
- /package/dist/{chunk-OIBEJN5H.js.map → chunk-LYQUFIHL.js.map} +0 -0
- /package/dist/{chunk-JNMTN5D5.js.map → chunk-LZHIYH32.js.map} +0 -0
- /package/dist/{chunk-7KAE7CQ2.js.map → chunk-M2KVSM43.js.map} +0 -0
- /package/dist/{chunk-32B7VCKU.js.map → chunk-O2A5YCFF.js.map} +0 -0
- /package/dist/{chunk-4A34KWOU.js.map → chunk-OGB2UTBC.js.map} +0 -0
- /package/dist/{chunk-GQXFOAYZ.js.map → chunk-OMXUQAZJ.js.map} +0 -0
- /package/dist/{chunk-45YHJ3GV.js.map → chunk-PFHF3A72.js.map} +0 -0
- /package/dist/{chunk-KV6JM2IA.js.map → chunk-PUGHLCBW.js.map} +0 -0
- /package/dist/{chunk-ZZXLB6AS.js.map → chunk-R6BGN6IY.js.map} +0 -0
- /package/dist/{chunk-CLAHBPOI.js.map → chunk-RBBTJ2HY.js.map} +0 -0
- /package/dist/{chunk-YKXZHUOW.js.map → chunk-RGUAY5IX.js.map} +0 -0
- /package/dist/{chunk-AU3AUJIZ.js.map → chunk-RQQIB7CD.js.map} +0 -0
- /package/dist/{chunk-TX5MKAHA.js.map → chunk-RTKREHPV.js.map} +0 -0
- /package/dist/{chunk-BAHOV5I5.js.map → chunk-SIACMXFK.js.map} +0 -0
- /package/dist/{chunk-6WO62AJW.js.map → chunk-SZFNO7L4.js.map} +0 -0
- /package/dist/{chunk-U27Y4DJZ.js.map → chunk-TWELPVXG.js.map} +0 -0
- /package/dist/{chunk-WVE6YKMX.js.map → chunk-U5ZDYJLQ.js.map} +0 -0
- /package/dist/{chunk-4YL2LMXK.js.map → chunk-UW5YKCE4.js.map} +0 -0
- /package/dist/{chunk-E5SC6ENM.js.map → chunk-UWRVDBDC.js.map} +0 -0
- /package/dist/{chunk-GTHVPVAR.js.map → chunk-VNPSPQNN.js.map} +0 -0
- /package/dist/{chunk-A4TOGAUW.js.map → chunk-VXH5Z22C.js.map} +0 -0
- /package/dist/{chunk-FS7NTGCJ.js.map → chunk-WBFJZCDJ.js.map} +0 -0
- /package/dist/{chunk-4RAS6HN6.js.map → chunk-WBRLSPA7.js.map} +0 -0
- /package/dist/{chunk-AUPSEJ7O.js.map → chunk-WG66ZPEV.js.map} +0 -0
- /package/dist/{chunk-QSIEL6TL.js.map → chunk-XXONVJWA.js.map} +0 -0
- /package/dist/{chunk-ZLHHE5XZ.js.map → chunk-YLIGPHN4.js.map} +0 -0
- /package/dist/{chunk-VVSOPRUE.js.map → chunk-YNWBBHQK.js.map} +0 -0
- /package/dist/{chunk-I6QKB7MT.js.map → chunk-Z6UMKUAI.js.map} +0 -0
- /package/dist/{chunk-7LNE2N2V.js.map → chunk-Z7AHYSZF.js.map} +0 -0
- /package/dist/{chunk-3PMFMUFX.js.map → chunk-ZA3RBGT3.js.map} +0 -0
- /package/dist/{chunk-TDSN2O2V.js.map → chunk-ZKZXS352.js.map} +0 -0
- /package/dist/{chunk-OMYCSTXL.js.map → chunk-ZLU3IWHK.js.map} +0 -0
- /package/dist/{chunk-6JPGVL7X.js.map → chunk-ZYG4UWHF.js.map} +0 -0
- /package/dist/{classified-marker-NA6HUV33.js.map → classified-marker-RYWYYZDB.js.map} +0 -0
- /package/dist/{collection-facade-VBM35WAH.js.map → collection-facade-XV3IMJBO.js.map} +0 -0
- /package/dist/{computed-GSTLWSY5.js.map → computed-M4QZVSPX.js.map} +0 -0
- /package/dist/{dead-filter-UALP5DJX.js.map → dead-filter-DRKJDNSF.js.map} +0 -0
- /package/dist/{delegation-JLNXM7KH.js.map → delegation-UEJN2LDC.js.map} +0 -0
- /package/dist/{derive-YUO3TQYN.js.map → derive-WIWSPZ5M.js.map} +0 -0
- /package/dist/{dispatch-77CB75KB.js.map → dispatch-2ZE7Z5V2.js.map} +0 -0
- /package/dist/{enclave-O4NOROCJ.js.map → enclave-VNHDWCB6.js.map} +0 -0
- /package/dist/{executor-CZDXBXKE.js.map → executor-4SMYWPSU.js.map} +0 -0
- /package/dist/{executor-DVJAWN3B.js.map → executor-BJ7PCXCV.js.map} +0 -0
- /package/dist/{executor-VRLG4TQJ.js.map → executor-O4UP4M3C.js.map} +0 -0
- /package/dist/{export-accessible-UXSMG7AB.js.map → export-accessible-A3N2V4IX.js.map} +0 -0
- /package/dist/{extract-partition-TVY7AMKH.js.map → extract-partition-3EUJXWYG.js.map} +0 -0
- /package/dist/{fanout-sidecar-NLQ7QAYW.js.map → fanout-sidecar-PBEVZ5IQ.js.map} +0 -0
- /package/dist/{find-WSARMJBW.js.map → find-W2DL2BEE.js.map} +0 -0
- /package/dist/{issue-3JNJNPT3.js.map → issue-NLJ3FCKL.js.map} +0 -0
- /package/dist/{ledger-MVPNG5OS.js.map → ledger-DDIDLWA3.js.map} +0 -0
- /package/dist/{liberate-4LOJOLDT.js.map → liberate-EPTCY3DF.js.map} +0 -0
- /package/dist/{link-set-3POMQR3I.js.map → link-set-2AYA6BEI.js.map} +0 -0
- /package/dist/{managed-secret-IARIJJ7Q.js.map → managed-secret-6QTGWZTW.js.map} +0 -0
- /package/dist/{migrate-cek-XM2ETTZW.js.map → migrate-cek-YBNJDJ2Q.js.map} +0 -0
- /package/dist/{noydb-IRILUDED.js.map → noydb-RFBNRUHH.js.map} +0 -0
- /package/dist/{pod-handle-MELERCKU.js.map → pod-handle-77YAUM4J.js.map} +0 -0
- /package/dist/{post-register-Z5T7FY45.js.map → post-register-WBDTOLWD.js.map} +0 -0
- /package/dist/{register-UZ37QPDB.js.map → register-VSPL7VRW.js.map} +0 -0
- /package/dist/{registry-ETJNA7PO.js.map → registry-A7JADQXC.js.map} +0 -0
- /package/dist/{registry-F7U6KSLX.js.map → registry-AOPJN2V4.js.map} +0 -0
- /package/dist/{registry-L4ELKX6C.js.map → registry-I4I3QIQ6.js.map} +0 -0
- /package/dist/{registry-SDT5LIGO.js.map → registry-QSMKWF4O.js.map} +0 -0
- /package/dist/{request-withdrawal-PVFEYX57.js.map → request-withdrawal-VL5N623Q.js.map} +0 -0
- /package/dist/{reveal-DQ2E3QAZ.js.map → reveal-Z2X4JW6E.js.map} +0 -0
- /package/dist/{revoke-YXY6SHUH.js.map → revoke-XQ5STGT4.js.map} +0 -0
- /package/dist/{seed-DZ4ORU5N.js.map → seed-WJWCFPZL.js.map} +0 -0
- /package/dist/{signer-Q7XQBMPA.js.map → signer-N5TGVOTN.js.map} +0 -0
- /package/dist/{stale-NI7SAXAZ.js.map → stale-MIO5KGEP.js.map} +0 -0
- /package/dist/{storage-3VNGSHXS.js.map → storage-NLYOMCDM.js.map} +0 -0
- /package/dist/{store-coordination-provider-TYKNR3OU.js.map → store-coordination-provider-XCX77ZBE.js.map} +0 -0
- /package/dist/{verify-6J2YLKSO.js.map → verify-5QI3DGO2.js.map} +0 -0
- /package/dist/{walk-WRA7RAPY.js.map → walk-G5OAA3RE.js.map} +0 -0
- /package/dist/{withdraw-accessible-4JTDFRFZ.js.map → withdraw-accessible-H7G2AMSP.js.map} +0 -0
|
@@ -424,16 +424,70 @@ export declare class BlobSet {
|
|
|
424
424
|
* - not yet stamped → apply via `casUpdateRefCountStamped`, then proceed
|
|
425
425
|
* exactly like the unstamped path below.
|
|
426
426
|
*
|
|
427
|
-
* A crash can also land
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
427
|
+
* A crash can also land part-way through the deletion itself. Since #1127
|
|
428
|
+
* that deletion runs CHUNKS FIRST (see `deleteChunksThenIndex`), so what
|
|
429
|
+
* survives is an index row with some chunks already gone — a reachable,
|
|
430
|
+
* re-runnable state rather than chunks nothing can ever address again.
|
|
431
|
+
*
|
|
432
|
+
* An eTag whose index row is ALREADY absent on entry is therefore either
|
|
433
|
+
* pre-#1127 residue or a completed deletion. `loadBlobObject` returns null
|
|
434
|
+
* (indistinguishable from "never existed") and the object's `chunkCount` is
|
|
435
|
+
* gone with it. A stamped caller who knows the true count (the journal
|
|
436
|
+
* marker's captured `holds`, once that plumbing lands) supplies it via
|
|
437
|
+
* `chunkCountHint` so this call can still finish the chunk deletion; without
|
|
438
|
+
* it, an already-index-gone eTag is reported `'shredded'` same as today (best
|
|
434
439
|
* effort — nothing left to key the cleanup off of).
|
|
435
440
|
*/
|
|
436
441
|
private releaseRef;
|
|
442
|
+
/**
|
|
443
|
+
* Delete a blob's chunks, THEN its index row. The order is the whole point
|
|
444
|
+
* (#1127).
|
|
445
|
+
*
|
|
446
|
+
* Both deletions used to run index-first, which meant a crash in between
|
|
447
|
+
* stranded chunks with no index row. Nothing can ever reach those again:
|
|
448
|
+
* `loadBlobObject` returns null, so no reader addresses them — and
|
|
449
|
+
* `rekeyBlobSet` derives its chunk ids from each index entry's `chunkCount`,
|
|
450
|
+
* so a rotation never visits them either. For a LEGACY blob (bytes under the
|
|
451
|
+
* `_blob` DEK itself rather than a per-blob content CEK) the bodies then stay
|
|
452
|
+
* openable under the retired `_blob` DEK — precisely the key a revoked member
|
|
453
|
+
* walked away with. Crash during `forget()`, revoke later, and real blob
|
|
454
|
+
* content sits readable indefinitely, with no error on either side.
|
|
455
|
+
*
|
|
456
|
+
* Reversing it removes the class rather than cleaning up after it: every
|
|
457
|
+
* chunk that still exists has an index row, so every chunk is reachable by
|
|
458
|
+
* `rekeyBlobSet` and gets re-keyed. There is no orphan to sweep because none
|
|
459
|
+
* is produced.
|
|
460
|
+
*
|
|
461
|
+
* The residue this ordering leaves instead is an index row whose chunks are
|
|
462
|
+
* partly gone. That state is better, but it is NOT free — it cost one reader
|
|
463
|
+
* correction (`resolveRehomedVersionETag`, which read an old blob's bytes
|
|
464
|
+
* while resuming a rehome and now checks `refCount <= 0` instead of relying
|
|
465
|
+
* on the row having vanished). An earlier draft of this comment called the
|
|
466
|
+
* new state "strictly better"; the full suite disproved that, and any future
|
|
467
|
+
* path that reads a blob by index needs the same care. What is true of it:
|
|
468
|
+
* - it is REACHABLE, so a re-run of `releaseRef` completes the deletion
|
|
469
|
+
* idempotently (both `store.delete` calls are void on an absent key);
|
|
470
|
+
* - `rekeyBlobSet` already tolerates it — a missing chunk hits its
|
|
471
|
+
* `if (!chunkEnv || !chunkEnv._iv) continue`, so a rotation still
|
|
472
|
+
* succeeds and still re-keys every chunk that remains;
|
|
473
|
+
* - it cannot leak, because the bytes that survive are the ones still
|
|
474
|
+
* covered by rotation.
|
|
475
|
+
*
|
|
476
|
+
* This also makes one ordering rule true across the blob subsystem rather
|
|
477
|
+
* than two opposite ones: `kernel/enclave/record-keys/rekey-blob.ts` already
|
|
478
|
+
* re-encrypts chunks before re-sealing their index entry, and documents that
|
|
479
|
+
* order as its resume property.
|
|
480
|
+
*
|
|
481
|
+
* Deliberately NOT paired with a sweep that deletes pre-existing orphans:
|
|
482
|
+
* such a sweep must decide "orphaned" from `store.list(_blob_index)`, and the
|
|
483
|
+
* store is untrusted. A store that withholds one index row could make a LIVE
|
|
484
|
+
* blob's chunks look orphaned and have us destroy them — turning withholding,
|
|
485
|
+
* which is reversible, into permanent loss. Reporting them is safe; deleting
|
|
486
|
+
* them on the store's word is not. The report half shipped as
|
|
487
|
+
* `reportOrphanBlobChunks` (#1133), surfaced through `vault.compact()`; a
|
|
488
|
+
* reclaim pass remains deliberately unbuilt for the reason above.
|
|
489
|
+
*/
|
|
490
|
+
private deleteChunksThenIndex;
|
|
437
491
|
/**
|
|
438
492
|
* Crypto-shred this record's blob attachments — called by
|
|
439
493
|
* `vault.forget()`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan blob-chunk REPORT (#1133) — the read-only half of the residue #1127
|
|
3
|
+
* stopped producing.
|
|
4
|
+
*
|
|
5
|
+
* Before #1127, `releaseRef` deleted a blob's index row BEFORE its chunks, so a
|
|
6
|
+
* crash in between stranded chunk bodies nothing can ever reach: `loadBlobObject`
|
|
7
|
+
* returns null so no reader addresses them, and `rekeyBlobSet` derives its chunk
|
|
8
|
+
* ids from each index entry's `chunkCount` so a rotation never visits them. For a
|
|
9
|
+
* LEGACY blob (bytes under the shared `_blob` DEK rather than a per-blob content
|
|
10
|
+
* CEK) those bodies then stay openable under the retired DEK indefinitely.
|
|
11
|
+
*
|
|
12
|
+
* #1127 reversed the order, which removes the class at the source. It does
|
|
13
|
+
* nothing for orphans created before the fix, and this module does not delete
|
|
14
|
+
* them either. It counts them.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this REPORTS and does not RECLAIM
|
|
17
|
+
*
|
|
18
|
+
* Deciding "orphaned" requires `store.list(_blob_index)`, and the store is
|
|
19
|
+
* untrusted — that is the premise of the product, not a caveat on it. A store
|
|
20
|
+
* that withholds a single index row makes a LIVE blob's chunks look orphaned. A
|
|
21
|
+
* reclaim pass acting on that would convert **withholding**, which is reversible
|
|
22
|
+
* the moment the store stops lying, into **permanent destruction** — handing an
|
|
23
|
+
* untrusted store a data-loss primitive it does not currently have, in order to
|
|
24
|
+
* tidy up residue.
|
|
25
|
+
*
|
|
26
|
+
* Reporting inverts the risk to something harmless: a lying store can only
|
|
27
|
+
* inflate this count. Nothing is deleted on its word, so the worst outcome is an
|
|
28
|
+
* operator investigating residue that turns out not to exist.
|
|
29
|
+
*
|
|
30
|
+
* Re-keying is not an alternative either: the chunk AAD is
|
|
31
|
+
* `{eTag}:{chunkIndex}:{chunkCount}`, and `chunkCount` lives only in the index
|
|
32
|
+
* row that is already gone — an orphan's AAD is unreconstructable, so it cannot
|
|
33
|
+
* be decrypted-and-re-encrypted at all.
|
|
34
|
+
*/
|
|
35
|
+
import type { NoydbStore } from '../../kernel/types.js';
|
|
36
|
+
export interface OrphanChunkReport {
|
|
37
|
+
/** Chunk rows whose eTag has no `_blob_index` entry. */
|
|
38
|
+
readonly chunks: number;
|
|
39
|
+
/** Distinct eTags those chunks belong to. */
|
|
40
|
+
readonly eTags: number;
|
|
41
|
+
/**
|
|
42
|
+
* Up to {@link SAMPLE_LIMIT} of those eTags, so an operator can inspect a real
|
|
43
|
+
* one rather than only a count. Deterministic (store list order), not random.
|
|
44
|
+
*/
|
|
45
|
+
readonly sampleETags: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Chunk ids that do not match the `{eTag}_{index}` grammar at all. Counted
|
|
48
|
+
* rather than skipped: an id this module cannot parse is a thing it cannot
|
|
49
|
+
* classify either way, and reporting zero orphans while silently dropping
|
|
50
|
+
* such ids would be the reassuring-but-wrong answer.
|
|
51
|
+
*/
|
|
52
|
+
readonly unparseable: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Count chunk rows with no surviving index row. Read-only: two `list` calls and
|
|
56
|
+
* no `get`, no `delete`, and no decryption — an orphan cannot be opened anyway.
|
|
57
|
+
*
|
|
58
|
+
* A store that cannot list either collection yields an empty report rather than
|
|
59
|
+
* throwing: this is a diagnostic riding along with a maintenance pass, and it
|
|
60
|
+
* must not be able to fail one.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reportOrphanBlobChunks(adapter: NoydbStore, vault: string): Promise<OrphanChunkReport>;
|
|
@@ -4,19 +4,19 @@ import {
|
|
|
4
4
|
freezeSnapshotOnly,
|
|
5
5
|
randomId,
|
|
6
6
|
withdrawAccessibleData
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-I3D67XD7.js";
|
|
8
|
+
import "./chunk-7LWBD3NO.js";
|
|
9
|
+
import "./chunk-XXONVJWA.js";
|
|
10
|
+
import "./chunk-5FRZ7YYU.js";
|
|
11
|
+
import "./chunk-FUE2KPKM.js";
|
|
12
12
|
import "./chunk-PENPHA45.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-OMXUQAZJ.js";
|
|
14
|
+
import "./chunk-PUGHLCBW.js";
|
|
15
|
+
import "./chunk-2LHPMFSR.js";
|
|
16
16
|
import "./chunk-LP7BEXCT.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-RQQIB7CD.js";
|
|
18
18
|
import "./chunk-IGNJOJEP.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-HKANJAOP.js";
|
|
20
20
|
import "./chunk-PZ5AY32C.js";
|
|
21
21
|
export {
|
|
22
22
|
FROZEN_SNAPSHOTS_COLLECTION,
|
|
@@ -25,4 +25,4 @@ export {
|
|
|
25
25
|
randomId,
|
|
26
26
|
withdrawAccessibleData
|
|
27
27
|
};
|
|
28
|
-
//# sourceMappingURL=withdraw-accessible-
|
|
28
|
+
//# sourceMappingURL=withdraw-accessible-H7G2AMSP.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/hub",
|
|
3
|
-
"version": "0.6.0-pre.
|
|
3
|
+
"version": "0.6.0-pre.23",
|
|
4
4
|
"description": "Zero-knowledge, offline-first, encrypted document store — core library with AES-256-GCM, PBKDF2, multi-user keyring, and sync engine",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -216,14 +216,14 @@
|
|
|
216
216
|
"node": ">=22.0.0"
|
|
217
217
|
},
|
|
218
218
|
"dependencies": {
|
|
219
|
-
"@noy-db/attestation": "0.6.0-pre.
|
|
219
|
+
"@noy-db/attestation": "0.6.0-pre.23"
|
|
220
220
|
},
|
|
221
221
|
"devDependencies": {
|
|
222
222
|
"@types/node": "^22.0.0",
|
|
223
223
|
"esbuild": "^0.25.0",
|
|
224
224
|
"zod": "^4.0.0",
|
|
225
225
|
"zod-to-json-schema": "^3.25.2",
|
|
226
|
-
"@noy-db/on-shamir": "0.6.0-pre.
|
|
226
|
+
"@noy-db/on-shamir": "0.6.0-pre.23"
|
|
227
227
|
},
|
|
228
228
|
"peerDependencies": {
|
|
229
229
|
"zod-to-json-schema": "^3.25.0"
|
package/dist/api-EYNT6KGS.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
UserApi,
|
|
3
|
-
createUserApi
|
|
4
|
-
} from "./chunk-RKCRPPNB.js";
|
|
5
|
-
import "./chunk-LX46B56Q.js";
|
|
6
|
-
import "./chunk-OHOKBMTM.js";
|
|
7
|
-
import "./chunk-PENPHA45.js";
|
|
8
|
-
import "./chunk-GQXFOAYZ.js";
|
|
9
|
-
import "./chunk-KV6JM2IA.js";
|
|
10
|
-
import "./chunk-TMBSPLUG.js";
|
|
11
|
-
import "./chunk-LP7BEXCT.js";
|
|
12
|
-
import "./chunk-AU3AUJIZ.js";
|
|
13
|
-
import "./chunk-IGNJOJEP.js";
|
|
14
|
-
import "./chunk-CWM4GQD2.js";
|
|
15
|
-
import "./chunk-PZ5AY32C.js";
|
|
16
|
-
export {
|
|
17
|
-
UserApi,
|
|
18
|
-
createUserApi
|
|
19
|
-
};
|
|
20
|
-
//# sourceMappingURL=api-EYNT6KGS.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-formula/materialized-views/dependency-analyzer.ts","../src/with-formula/materialized-views/query-hash.ts","../src/with-formula/materialized-views/registry.ts"],"sourcesContent":["import type { Query, QueryPlan } from '../../kernel/query/builder.js'\nimport type { JoinContext } from '../../kernel/query/join.js'\nimport type { MaterializedViewSpec } from './types.js'\n\n/**\n * Walks a `Query<T>` plan and returns the set of source collection\n * names that any source-write should trigger a refresh on.\n *\n * Handles:\n * - root collection (the one the query was built from)\n * - FK join targets (`.join(field, { as })`)\n *\n * Also handles:\n * - cross-join targets (`.crossJoin(target, { as })`) — v3\n *\n * Deferred:\n * - `.wherePredicate(name)` — v2 predicate primitive\n * - Overlay-name expansion to {base, overlay}\n *\n * The set is materialized at MV registration time. The MV registry\n * uses it to (a) dispatch `onSourceWrite` only to MVs that actually\n * care, and (b) contribute edges to the shared cycle-detection graph.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function analyzeDependencies(query: Query<any>): Set<string> {\n const deps = new Set<string>()\n const plan = query._plan()\n const ctx = query._joinContext()\n\n // The root collection is always a dependency.\n if (ctx?.leftCollection) {\n deps.add(ctx.leftCollection)\n }\n\n // FK join targets contribute additional sources.\n for (const leg of plan.joins) {\n deps.add(leg.target)\n }\n\n // Cross-join targets are also dependency sources — writes to either side\n // must trigger MV refresh. Symmetric with FK-join target handling above.\n for (const clause of plan.clauses) {\n if (clause.type === 'crossJoin') {\n deps.add(clause.target)\n }\n }\n\n // Sub-plans inside OR clauses can carry nested joins. Walk them.\n // (Today only top-level `.join()` populates `plan.joins`, but the\n // OR-group machinery permits sub-plans, so we recurse defensively.)\n walkClausesForJoins(plan, deps, ctx)\n\n return deps\n}\n\nfunction walkClausesForJoins(\n plan: QueryPlan,\n deps: Set<string>,\n ctx: JoinContext | undefined,\n): void {\n void ctx\n // Today `plan.joins` carries all join legs at top level. Sub-plans\n // inside OR groups don't currently support nested joins, so the loop\n // below is a no-op safety net for future builder extensions.\n for (const clause of plan.clauses) {\n if (clause.type === 'group') {\n // Group clauses don't (yet) carry their own joins; this is a\n // forward-compat anchor for when OR-groups support nested\n // sources.\n }\n }\n}\n\n/**\n * Convenience: produce a stable string summary of the query plan\n * suitable for `queryHash` derivation. Captures everything the\n * dependency analyzer reads + the where/orderBy/limit/offset\n * structure that affects materialized rows.\n *\n * `joinContext` is intentionally NOT included — the join-resolution\n * function references would defeat hash determinism. The set of join\n * TARGETS (collection names) IS included via the plan.joins legs.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function summarizeQueryPlan(query: Query<any>): string {\n const plan = query._plan()\n const ctx = query._joinContext()\n return JSON.stringify({\n root: ctx?.leftCollection ?? null,\n clauses: plan.clauses.map(c => {\n if (c.type === 'crossJoin') {\n return {\n type: 'crossJoin',\n target: c.target,\n as: c.as,\n // Inline on: callback: use sentinel — drift detection disabled for this MV\n onPredicateName: c.onPredicateName ?? (c.on ? '[inline]' : null),\n maxRows: c.maxRows ?? null,\n }\n }\n return c\n }),\n orderBy: plan.orderBy,\n limit: plan.limit ?? null,\n offset: plan.offset,\n joins: plan.joins.map(j => ({ field: j.field, as: j.as, target: j.target, mode: j.mode })),\n })\n}\n\n/**\n * Canonical string description of a UNION MV's plan, used as input to\n * `computeQueryHash`.\n *\n * Asymmetry note:\n * - Arm collection names are NOT sorted. Declaration order is\n * semantically meaningful for the dedup-only UNION path —\n * `materializeUnionResult` iterates `spec.unionSources` in\n * declaration order and keeps the first-seen row per composite key\n * (tie-break precedence). If we sorted arms here, a consumer who\n * reordered `unionSources` to change precedence would compute the\n * same `queryHash`, refresh would be a no-op, and stale MV rows\n * would persist. Hashing in declaration order makes any reorder\n * trigger a refresh.\n * - `groupBy` fields ARE sorted. Multi-key groupBy buckets are\n * commutative (`canonicalGroupKey` produces the same composite key\n * regardless of field order in the input spec).\n * - `aggregate` keys ARE sorted. Reducer-spec keys are independent\n * of each other — order of declaration doesn't change output.\n *\n * Per-arm `map` functions are NOT fingerprinted; consumers must bump\n * the MV's `name` (or rely on application-level cache busting) when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeUnionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return `union(${summarizeUnionArms(spec)})${summarizeGroupingTail(spec)}`\n}\n\n/**\n * Canonical string description of a projection MV's plan (#810), used\n * as input to `computeQueryHash`.\n *\n * Leg descriptors ARE sorted (contrast with UNION arm order): every\n * leg attaches under a distinct `as` alias, forward legs are\n * one-to-one attachments and collect legs are independent\n * hash-grouped passes, so declaration order never changes the\n * materialized rows — a pure reorder must NOT force a refresh.\n *\n * The projection `map` is NOT fingerprinted (identical limitation as\n * the UNION arm `map`); consumers must bump the MV's `name` when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeProjectionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n const projection = spec.projection!\n const legs = projection.joins\n .map(leg =>\n 'collect' in leg\n ? `collect:${leg.collect}.${leg.on}→${leg.as}`\n : `field:${leg.field}→${leg.as}`,\n )\n .sort()\n const body = JSON.stringify({ source: projection.source, legs })\n return `projection(${body})${summarizeGroupingTail(spec)}`\n}\n\n/** Arm descriptors for `summarizeUnionPlan` — declaration order preserved (see its doc). */\nfunction summarizeUnionArms<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return (spec.unionSources ?? [])\n .map(s => {\n // Fold each arm's join legs into the summary so adding or\n // reordering joins (which changes the materialized rows) bumps\n // queryHash. Leg order is declaration-significant (legs chain), so\n // it is NOT sorted — same rationale as arm declaration order.\n const joins = s.join?.length\n ? `[${s.join.map(j => `${j.field}→${j.as}`).join(';')}]`\n : ''\n return `${s.collection}${joins}`\n })\n .join(',')\n}\n\n/**\n * Shared `|groupBy(…)|aggregate(…)|money(…)` tail for the UNION and\n * projection summaries — both feed the same post-map grouping pipeline,\n * so the same spec fields fold into both hashes with the same rules.\n */\nfunction summarizeGroupingTail<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n const groupBy: string = Array.isArray(spec.groupBy)\n ? [...spec.groupBy].sort().join(',')\n : typeof spec.groupBy === 'string'\n ? spec.groupBy\n : ''\n const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(',') : ''\n // `moneyFields` changes reducer semantics (float → exact BigInt), so\n // declaring / removing / re-keying it must bump queryHash. Keys are\n // sorted — they're independent of each other (one descriptor per\n // output field), same rationale as aggregate keys.\n const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(',') : ''\n return `|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`\n}\n","/**\n * Deterministic hash of a materialized view strategy's \"shape\": MV\n * name + canonical query-plan summary + sorted dependency-set.\n *\n * Used to detect strategy drift: a row whose `_materializedFrom.queryHash`\n * doesn't match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps. Mirrors the v1\n * `computeStrategyHash` pattern.\n */\nimport { sha256Hex } from '../../kernel/enclave/index.js'\n\nexport async function computeQueryHash(\n mvName: string,\n /**\n * Source-collection set the query depends on. Sorted before\n * canonicalization so set iteration order doesn't affect the hash.\n */\n dependencies: ReadonlySet<string>,\n /**\n * Stringified query-plan summary. The caller produces this from the\n * `Query<T>` builder — concretely: a JSON serialization of clauses +\n * orderBy + limit + offset + joins. Function bodies inside\n * `wherePredicate` are NOT included here (those carry their own\n * `predicateHash` to be folded in by a later sub-issue).\n */\n queryPlanSummary: string,\n): Promise<string> {\n const canonical = JSON.stringify({\n mvName,\n dependencies: [...dependencies].sort(),\n queryPlanSummary,\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n\n/**\n * Canonicalize a query plan for hashing. Walks the plan structure\n * with sorted keys so insertion order doesn't perturb the result.\n * Lives here rather than in `query/builder.ts` to keep that module\n * stable across MV-specific evolutions.\n *\n * @internal exported for testing\n */\nexport function canonicalizeQueryPlan(plan: unknown): string {\n return JSON.stringify(plan, (_key, value) => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n}\n","import { MaterializedViewSourceUnknownError } from '../../kernel/errors.js'\nimport type { ViaGraph, FieldRef, EdgeKind, Grain } from '../../kernel/via/graph.js'\nimport type { Clause, FieldClause } from '../../kernel/query/predicate.js'\nimport type { DeclaredPredicate } from '../../kernel/query/builder.js'\nimport { analyzeDependencies, summarizeQueryPlan, summarizeUnionPlan, summarizeProjectionPlan } from './dependency-analyzer.js'\nimport { computeQueryHash } from './query-hash.js'\nimport type { MaterializedViewSpec, MVQueryContext } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2) — MUST match\n * `derivations/registry.ts`'s and `vault.ts`'s overlay-edge marker so\n * cross-registry edges (a derivation feeding an MV, or vice versa) resolve\n * to the SAME graph node. See that file's `WHOLE_RECORD` doc comment.\n */\nconst WHOLE_RECORD = '*'\n\n/**\n * One registered MV strategy alongside its derived metadata. Stored\n * type-erased on `TRow` so the registry can hold heterogeneous MVs.\n */\nexport interface RegisteredMV {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly spec: MaterializedViewSpec<any>\n /** Output collection name (`spec.output?.collection ?? spec.name`). */\n readonly outputCollection: string\n /** Set of source collections; populated at registration via the analyzer. */\n readonly dependencies: ReadonlySet<string>\n /** Canonical `queryHash` — `_materializedFrom.queryHash` for every emitted row. */\n readonly queryHash: string\n /**\n * Top-level FieldClauses on the partition field, captured at\n * registration time. Used by the cycle detector to resolve\n * same-collection-as-source edges via the partition-discriminator\n * check. Empty when `spec.output?.partition` is undefined.\n */\n readonly partitionClauses: readonly FieldClause[]\n /**\n * #638 Task 2 — `'record'` for a row-per-source-row Query<T> or\n * `unionSources` MV, `'aggregate'` for a `.groupBy().aggregate()` MV\n * (the shape that requires explicit `sources`, since the dependency\n * analyzer can't walk an aggregate plan). Feeds `edges()`'s grain.\n */\n readonly grain: Grain\n}\n\n/**\n * Vault-internal registry of MV strategies. Owned by `Vault`; not\n * exported. Parallel to v1's `DerivationRegistry`; the two graphs share\n * a single cycle-detection pass at vault open (see `validate`).\n *\n * @internal\n */\nexport class MaterializedViewRegistry {\n /** Keyed by `spec.name`. */\n private readonly _byName = new Map<string, RegisteredMV>()\n /** Keyed by dependency source-collection → MVs that depend on it. */\n private readonly _bySource = new Map<string, RegisteredMV[]>()\n /**\n * Projection MVs (#810) whose forward-leg ref() targets could not be\n * resolved at registration time. A forward leg names only the FK\n * FIELD; its dependency is the field's ref() TARGET, declared on the\n * source collection — but registration runs at vault open, BEFORE\n * user code declares collections (and their refs). Constructing the\n * source collection here to look them up would be worse: a later\n * `vault.collection(source, { refs })` call only registers refs on a\n * cache miss, so an early construction would silently drop them.\n * Instead each entry carries a non-constructive ref-registry probe;\n * `mvsForSource` retries on every dispatch until every forward\n * target has resolved (see `_resolvePendingForwardDeps`).\n */\n private readonly _pendingForwardDeps: Array<{\n reg: RegisteredMV\n fields: Set<string>\n resolveTarget: (field: string) => string | null\n }> = []\n\n /**\n * Register an MV. Invokes `spec.query()` once at registration time to\n * read the plan + join context; the resulting `Query<T>` is discarded\n * after dependency extraction. `vault.collection(...)` must therefore\n * be functional by the time this runs — typically wired from\n * `Vault._initMaterializedViews` after collection bootstrap.\n *\n * Throws `MaterializedViewSourceUnknownError` if the analyzer\n * surfaces a dependency the vault doesn't know about (when a\n * `knownCollections` checker is supplied).\n */\n async register(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewSpec<any>,\n db: MVQueryContext,\n options?: { knownCollections?: (name: string) => boolean },\n ): Promise<void> {\n // Build a predicate-aware db wrapper. If `spec.predicates` is\n // declared, the wrapper intercepts `.collection().query()` and\n // attaches the predicates map to the resulting Query<T>. With no\n // predicates declared, the wrapper is the original db unchanged.\n const dbForQuery = spec.predicates ? wrapDbWithPredicates(db, spec.predicates) : db\n\n // Invoke the query callback once to inspect its plan / dependencies.\n // For Query<T> shapes the analyzer extracts deps + plan summary\n // automatically. Reduction / GroupedReduction shapes don't\n // expose the underlying Query, so the spec must declare `sources`\n // explicitly. `partitionClauses` are only populated for Query<T>\n // since same-collection-partition is a non-aggregate concern.\n // UNION-form strategies: dependencies and plan summary come\n // straight off the strategy — no `query` callback to introspect.\n // The dependency-analyzer + summarizer are bypassed entirely; the\n // executor handles materialization via `materializeUnionResult`.\n let dependencies: Set<string>\n let queryPlanSummary: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let qAny: any = null\n let isQuery = false\n // Projection-form (#810): forward legs whose ref() target wasn't\n // resolvable at registration time — folded in lazily, see\n // `_pendingForwardDeps`.\n const pendingForwardFields: string[] = []\n if (spec.unionSources) {\n dependencies = new Set(spec.unionSources.map(s => s.collection))\n // Per-arm joins resolve right-side collections that aren't among\n // the arm `collection`s. The consumer lists those in `sources`;\n // fold them into the dependency set so a write to a join-target\n // collection triggers MV refresh (and contributes a cycle edge).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n queryPlanSummary = summarizeUnionPlan(spec)\n } else if (spec.projection) {\n // Projection-form (#810): dependencies are all AUTO — the primary\n // source, every collect leg's collection (both literal names), and\n // every forward leg's ref() target. Forward targets usually can't\n // resolve yet (refs are declared after vault open) — those go\n // through the pending-resolution path below. Explicit `sources`\n // remains additive, same as the other two forms.\n const projection = spec.projection\n dependencies = new Set([projection.source])\n for (const leg of projection.joins) {\n if ('collect' in leg) dependencies.add(leg.collect)\n else pendingForwardFields.push(leg.field)\n }\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n queryPlanSummary = summarizeProjectionPlan(spec)\n } else {\n const q = spec.query!(dbForQuery)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n qAny = q as any\n isQuery = typeof qAny._plan === 'function'\n if (isQuery) {\n // `q` is the `Query` arm of the union here (runtime-confirmed via\n // `qAny._plan` above); reuse the already-`any` `qAny` the block uses\n // for `_plan()` rather than re-narrowing.\n dependencies = analyzeDependencies(qAny)\n queryPlanSummary = summarizeQueryPlan(qAny)\n // Fold `.wherePredicate(name, ctx)` references into the plan\n // summary so predicate function or ctx changes (signalled by\n // bumping `hash` or supplying a different ctx) propagate into\n // `queryHash` and force refresh on next visit.\n const predicateRefs = extractPredicateRefs(qAny._plan())\n if (predicateRefs.length > 0) {\n queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs })\n }\n // If `sources` is ALSO declared, take the union (consumer's\n // explicit list extends the auto-analyzed set).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n } else {\n // Aggregate shape: require explicit `sources`.\n if (!spec.sources || spec.sources.length === 0) {\n throw new Error(\n `withMaterializedView \"${spec.name}\": query() returned an aggregate ` +\n `(Reduction or GroupedReduction) but no \\`sources\\` field is declared. ` +\n `The dependency analyzer cannot walk through groupBy().aggregate() ` +\n `back to the source — declare sources: [...] explicitly.`,\n )\n }\n dependencies = new Set(spec.sources)\n // Aggregate plans don't carry a chainable query plan for summary\n // purposes; the dep-set + spec.name serve as the queryHash inputs.\n queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() })\n }\n }\n\n // Sanity-check declared dependencies against the vault's known\n // collections. Optional — when the checker isn't supplied (test\n // wiring, in-process composition) the registration succeeds and\n // any typo surfaces at first onSourceWrite as a no-op.\n if (options?.knownCollections) {\n for (const dep of dependencies) {\n if (!options.knownCollections(dep)) {\n throw new MaterializedViewSourceUnknownError(spec.name, dep)\n }\n }\n }\n\n const outputCollection = spec.output?.collection ?? spec.name\n const queryHash = await computeQueryHash(spec.name, dependencies, queryPlanSummary)\n // For same-collection-as-source MVs, capture the where-clauses on\n // the partition field so cycle detection can prove disjointness.\n // Only applicable to Query<T> shapes — aggregate MVs don't carry\n // a chainable plan to inspect (and same-collection aggregation\n // doesn't make sense for same-collection aggregation).\n const partitionClauses: FieldClause[] = []\n const partitionField = spec.output?.partition?.field\n if (partitionField !== undefined && isQuery) {\n const plan = qAny._plan()\n for (const clause of plan.clauses) {\n if (isFieldClauseOnField(clause, partitionField)) partitionClauses.push(clause)\n }\n }\n // #638 Task 2 — 'aggregate' only for the explicit-sources aggregate shape\n // (groupBy().aggregate() with no chainable plan); a row-per-source-row\n // Query<T>, unionSources, or projection MV is 'record'.\n const grain: Grain = spec.unionSources || spec.projection || isQuery ? 'record' : 'aggregate'\n const reg: RegisteredMV = { spec, outputCollection, dependencies, queryHash, partitionClauses, grain }\n\n this._byName.set(spec.name, reg)\n for (const dep of dependencies) {\n const arr = this._bySource.get(dep)\n if (arr) arr.push(reg)\n else this._bySource.set(dep, [reg])\n }\n if (pendingForwardFields.length > 0) {\n // Non-constructive ref-registry probe: the Vault (the real\n // MVQueryContext) carries its RefRegistry as a private field,\n // reachable structurally at runtime. Test stubs without one simply\n // never resolve — forward deps then come only from explicit `sources`.\n const refRegistry = (db as unknown as {\n refRegistry?: { getOutbound(collection: string): Record<string, { target: string }> }\n }).refRegistry\n const source = spec.projection!.source\n this._pendingForwardDeps.push({\n reg,\n fields: new Set(pendingForwardFields),\n resolveTarget: (field) => refRegistry?.getOutbound(source)[field]?.target ?? null,\n })\n // Immediate attempt — covers refs already declared by the time\n // this MV registers (re-registration flows, test wiring).\n this._resolvePendingForwardDeps()\n }\n }\n\n /** All MVs that depend on `source`, in registration order. */\n mvsForSource(source: string): ReadonlyArray<RegisteredMV> {\n if (this._pendingForwardDeps.length > 0) this._resolvePendingForwardDeps()\n return this._bySource.get(source) ?? []\n }\n\n /**\n * Retry ref() resolution for every pending projection forward leg\n * (#810). A field resolves once its source collection's refs have\n * been declared; the target then folds into the owning MV's\n * dependency set and `_bySource`, so subsequent writes to it\n * dispatch a refresh. Idempotent and cheap — the pending list\n * empties as fields resolve, and `mvsForSource` skips the call\n * entirely once it's empty.\n *\n * Late-resolved targets do NOT retro-feed `edges()` — the cycle\n * pass runs once at vault open, before refs exist. A cycle routed\n * exclusively through a forward leg is therefore not detected at\n * open (collect + source edges, the literal names, are).\n */\n private _resolvePendingForwardDeps(): void {\n for (let i = this._pendingForwardDeps.length - 1; i >= 0; i--) {\n const entry = this._pendingForwardDeps[i]!\n for (const field of [...entry.fields]) {\n const target = entry.resolveTarget(field)\n if (target === null) continue\n entry.fields.delete(field)\n if (entry.reg.dependencies.has(target)) continue\n // `dependencies` is declared ReadonlySet for consumers; the\n // registry owns the underlying Set and is the one writer.\n ;(entry.reg.dependencies as Set<string>).add(target)\n const arr = this._bySource.get(target)\n if (arr) arr.push(entry.reg)\n else this._bySource.set(target, [entry.reg])\n }\n if (entry.fields.size === 0) this._pendingForwardDeps.splice(i, 1)\n }\n }\n\n /** Single MV by name, or `undefined`. */\n byName(name: string): RegisteredMV | undefined {\n return this._byName.get(name)\n }\n\n /** Iterate over every registered MV. */\n all(): ReadonlyArray<RegisteredMV> {\n return [...this._byName.values()]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'mv'` edge per registered MV — target\n * = the output collection (a `WHOLE_RECORD` artifact node), sources =\n * every dependency (also `WHOLE_RECORD` nodes). Same-collection edges\n * (`dep === outputCollection`) are skipped IFF the MV declares an\n * `output.partition` discriminator AND the query has a where-clause that\n * provably excludes the partition value (`partitionDisjoint`) — the SAME\n * condition the old local DFS used. Grain comes from the registration-time\n * `RegisteredMV.grain`.\n */\n edges(): ReadonlyArray<{ readonly target: FieldRef; readonly sources: readonly FieldRef[]; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; sources: FieldRef[]; kind: EdgeKind; grain: Grain }> = []\n for (const reg of this._byName.values()) {\n const sources: FieldRef[] = []\n for (const dep of reg.dependencies) {\n if (dep === reg.outputCollection && partitionDisjoint(reg)) continue\n sources.push({ collection: dep, field: WHOLE_RECORD })\n }\n if (sources.length === 0) continue\n out.push({ target: { collection: reg.outputCollection, field: WHOLE_RECORD }, sources, kind: 'mv', grain: reg.grain })\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). `graph` is the caller's shared per-vault graph,\n * ALREADY carrying this registry's `edges()` AND `DerivationRegistry`'s\n * (registered by the caller — see `Vault._initMaterializedViews`, which\n * runs after `_initDerivations`, so a pure-derivation cycle already threw\n * `DerivationCycleError` there; any cycle surfacing here necessarily\n * touches an MV edge). Throws `MaterializedViewCycleError` — SAME class as\n * before.\n */\n validate(graph: ViaGraph): void {\n graph.assertAcyclic()\n }\n}\n\n/**\n * Type guard: is the clause a top-level `FieldClause` on the given\n * field? Used by the partition-disjoint check.\n *\n * @internal\n */\nfunction isFieldClauseOnField(clause: Clause, field: string): clause is FieldClause {\n return clause.type === 'field' && clause.field === field\n}\n\n/**\n * Wrap an `MVQueryContext` so its `.collection().query()` returns a\n * Query<T> with the MV's declared predicates attached. Bare Queries\n * (outside of any MV) don't gain `.wherePredicate()` — only Queries\n * obtained through this wrapped db do.\n *\n * @internal\n */\nexport function wrapDbWithPredicates(\n db: MVQueryContext,\n predicates: NonNullable<MaterializedViewSpec<Record<string, unknown>>['predicates']>,\n): MVQueryContext {\n // Build the predicate map once — the fn signature in the MV spec\n // is row-typed but the QueryBuilder casts to unknown, so we widen\n // here for the Map.\n const map = new Map<string, DeclaredPredicate>()\n for (const [name, decl] of Object.entries(predicates)) {\n map.set(name, {\n hash: decl.hash,\n fn: decl.fn as (record: unknown, ctx?: unknown) => boolean,\n })\n }\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collection<T extends Record<string, unknown>>(name: string): any {\n const c = db.collection<T>(name)\n // Return an object that delegates everything to `c` but\n // overrides `.query()` to attach predicates via the new\n // `Query._withPredicates()` accessor.\n return new Proxy(c, {\n get(target, prop, receiver) {\n if (prop === 'query') {\n return (...args: unknown[]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = (target.query as any)(...args)\n // For non-aggregate Query<T>, attach predicates. For\n // legacy predicate-arg overload that returns T[] (sync\n // filter), pass through unchanged.\n \n if (q && typeof q._withPredicates === 'function') {\n return q._withPredicates(map)\n }\n return q\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n },\n }\n}\n\n/**\n * Walk a QueryPlan's clauses and collect predicate-reference markers\n * for `queryHash` derivation. Returns a sorted array (deterministic\n * order) of `{ name, predicateHash, ctxHash }` tuples — these are the\n * hashable identity of each `.wherePredicate()` call site.\n *\n * @internal\n */\nfunction extractPredicateRefs(\n plan: { clauses: readonly Clause[] },\n): Array<{ name: string; predicateHash: string; ctxHash: string }> {\n const refs: Array<{ name: string; predicateHash: string; ctxHash: string }> = []\n const walk = (clauses: readonly Clause[]): void => {\n for (const c of clauses) {\n if (c.type === 'wherePredicate') {\n refs.push({ name: c.name, predicateHash: c.predicateHash, ctxHash: c.ctxHash })\n } else if (c.type === 'group') {\n walk(c.clauses)\n }\n }\n }\n walk(plan.clauses)\n // Stable-sort by (name, predicateHash, ctxHash) — same predicate\n // appearing twice with different ctx hashes both flow through.\n refs.sort((a, b) => {\n if (a.name !== b.name) return a.name < b.name ? -1 : 1\n if (a.predicateHash !== b.predicateHash) return a.predicateHash < b.predicateHash ? -1 : 1\n return a.ctxHash < b.ctxHash ? -1 : a.ctxHash > b.ctxHash ? 1 : 0\n })\n return refs\n}\n\n/**\n * Provability check for the same-collection partition-discriminator.\n * Returns `true` when\n * the captured partition clauses on the MV's query provably exclude\n * the partition's value — meaning the input filter and the output\n * partition are disjoint and the same-collection edge isn't really a\n * cycle.\n *\n * Supported provability shapes (narrow on purpose — DERIV-PP30-001\n * is the load-bearing case):\n *\n * - `.where(field, '==', X)` where X !== partition.value → disjoint\n * - `.where(field, '!=', partition.value)` → disjoint\n * - `.where(field, 'in', [...])` where partition.value NOT in list → disjoint\n *\n * Anything else (no clause on the partition field, an 'in' list that\n * contains partition.value, unsupported operators) → not disjoint,\n * the cycle detector surfaces `MaterializedViewCycleError`.\n *\n * @internal\n */\nfunction partitionDisjoint(reg: RegisteredMV): boolean {\n const partition = reg.spec.output?.partition\n if (partition === undefined) return false\n const value = partition.value\n // The OR-semantics of multiple where-clauses on the same field\n // would muddy this check. v2 only treats AND-chained clauses;\n // any clause that proves disjoint is sufficient.\n for (const c of reg.partitionClauses) {\n if (c.op === '==' && c.value !== value) return true\n if (c.op === '!=' && c.value === value) return true\n if (c.op === 'in' && Array.isArray(c.value)) {\n const list = c.value as readonly unknown[]\n if (!list.includes(value)) return true\n }\n }\n return false\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/via/blob/binding.ts","../src/port/with/blob-strategy.ts","../src/with-shape/blobs/export-blobs.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * The blob `ViaBinding` (#629 Task 7) — wires the `blobFields` declaration\n * into the kernel's generic Via port as a deliberately THIN binding:\n * declaration + posture + `describeFragment` + `erase` glue, and nothing\n * else. Blob content is fully out-of-band (`collection.blob(id)` →\n * `BlobSet` writing `_blob_*` side-collections; it never touches\n * `_putInternal` or the record codec), so this binding declares **no\n * write/read pipeline hooks** — in particular no `encodeAtRest`/\n * `decodeAtRest`: a blobFields collection must not flip the pipeline's\n * `hasAtRestHooks`, or the codec would abandon its inline seal path for a\n * feature that never seals record fields. The routing/compaction/TTL/\n * legal-hold/exportBlobs machinery stays service-side in\n * `with-shape/blobs/` (the i18n-dictionary precedent — that machinery\n * does real chunk AEAD + key-lifecycle work `via-enclave-isolation`\n * forbids under `via/*`).\n *\n * Like classified (see `port/with/classified-strategy.ts`), the link is\n * EAGER — `port/with/blob-strategy.ts` calls {@link linkBlobVia} at module\n * load. `blobFields` policies are plain object literals (there is no\n * `blob.*()` declaration factory to hang a lazy link on), so the binder\n * must be installed before `compileViaBindings` ever sees one.\n *\n * There is no `declare`-time validation: `blobFields` has never had a\n * construction-time refusal matrix (policies are consulted lazily by\n * `vault.compact()` and `BlobSet.put`), and inventing one here would\n * break the behavior lock.\n *\n * `covers` (#629 Task 8) is a passive coverage predicate, not a pipeline\n * hook — it lets `ViaPipeline.postureFor` recognize a `blobFields` slot so\n * `.where()`/`.orderBy()`/`.aggregate()` can refuse it (posture\n * `queryable: 'none'`); it does not participate in clause building or\n * evaluation.\n */\nimport type { ViaBinding, ViaEraseCtx, ViaEraseReport } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\n\n/**\n * Config a blob collection's `blobFields` declaration resolves to — the\n * binding's construction input. `purgeBlobsForRecord` is an OPTIONAL\n * vault/service-provided closure for the `erase` hook, mapping\n * `collection.blob(id).shredAllForRecord()`'s `{shredded, retainedShared,\n * residue}: string[]` accounting onto `ViaEraseReport` — `shredded`/\n * `retainedShared` become counts, each residue eTag becomes a tagged\n * `{kind: 'blob-residue', eTag}` entry.\n *\n * DELIBERATELY left unwired by `compileViaBindings` (#629 Task 10, resolved\n * within parity-first — see task-10-report.md \"blob purge\" section):\n * `per-blob-cek.test.ts`/`forget.test.ts` prove blob-shred-on-forget is\n * gated SOLELY on the vault's `blobsStrategy` being configured, never on a\n * given collection's OWN `blobFields` declaration (`.blob(id)` is callable,\n * and IS exercised by those tests, on collections with no `blobFields` at\n * all). Routing it exclusively through this binding would silently stop\n * blob crypto-shredding for every such collection; `vault.forget()` keeps\n * calling `collection.blob(id).shredAllForRecord()` directly for that\n * reason. The hook stays real/tested (see `blob-binding.test.ts`) for a\n * future caller that wants it.\n */\nexport interface BlobViaConfig {\n readonly fields: BlobFieldsConfig\n readonly collectionName: string\n /** Purge one record's blob slots (crypto-shred participation). Returns the erase report for `forget()`'s summary. */\n readonly purgeBlobsForRecord?: (id: string) => Promise<ViaEraseReport>\n}\n\n/**\n * `forget()`'s per-ref blob-purge participation: delegate to the wired\n * closure, or report a clean zero-shredded, zero-residue no-op — the\n * default in production too (#629 Task 10 deliberately leaves this unwired;\n * see `BlobViaConfig`'s doc comment).\n */\nasync function eraseBlobs(ctx: ViaEraseCtx, cfg: BlobViaConfig): Promise<ViaEraseReport> {\n if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id)\n return { shredded: 0, residue: [] }\n}\n\n/**\n * One field's blob knobs as they appear on `describeFragment()`'s payload\n * (#657 — the second real `ViaBinding.describeFragment` consumer after\n * `lookup`'s; see `with-shape/introspection/describe.ts`'s `buildDescription`).\n * Declarative scalars (`retainDays`/`external`/`public`/`backlink`) verbatim;\n * predicate knobs (`evictWhen`/`legalHold`/`retainUntil` — functions over the\n * decrypted record) as presence flags, since a predicate has no serializable\n * form — mirrors `buildBlobDescribeFragment`'s per-field shape below.\n */\nexport interface BlobDescribeFragmentEntry {\n readonly retainDays?: number\n readonly evictWhen?: true\n readonly legalHold?: true\n readonly retainUntil?: true\n readonly external?: true\n readonly public?: true\n readonly backlink?: string\n}\n\n/** The `'blob'` binding's `describeFragment()` payload shape. */\nexport interface BlobDescribeFragment {\n readonly blobFields: Record<string, BlobDescribeFragmentEntry>\n}\n\n/**\n * `{ blobFields: { <field>: <knobs> } }` — declarative scalars\n * (`retainDays`/`external`/`public`/`backlink`) verbatim; predicate knobs\n * (`evictWhen`/`legalHold`/`retainUntil` — functions over the decrypted\n * record) as presence flags, since a predicate has no serializable form.\n */\nfunction buildBlobDescribeFragment(fields: BlobFieldsConfig): Record<string, unknown> {\n return {\n blobFields: Object.fromEntries(\n Object.entries(fields).map(([field, p]) => [field, {\n ...(p.retainDays !== undefined ? { retainDays: p.retainDays } : {}),\n ...(p.evictWhen !== undefined ? { evictWhen: true } : {}),\n ...(p.legalHold !== undefined ? { legalHold: true } : {}),\n ...(p.retainUntil !== undefined ? { retainUntil: true } : {}),\n ...(p.external === true ? { external: true } : {}),\n ...(p.public === true ? { public: true } : {}),\n ...(p.backlink !== undefined ? { backlink: p.backlink } : {}),\n }]),\n ),\n }\n}\n\nexport function blobBinding(cfg: BlobViaConfig): ViaBinding {\n return {\n brand: 'blob',\n // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes\n // in `_blob_chunks` (service-side), not sealed record fields; queryable:\n // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()\n // is a first-class door; forgettable — shredAllForRecord participates in\n // vault.forget().\n posture: { encryptedAtRest: 'envelope', queryable: 'none', exportable: true, forgettable: true },\n covers: (field) => field in cfg.fields,\n erase: (ctx) => eraseBlobs(ctx, cfg),\n describeFragment: () => buildBlobDescribeFragment(cfg.fields),\n }\n}\n\nexport function linkBlobVia(): void {\n installViaBinder('blob', (c) => blobBinding(c as BlobViaConfig))\n}\n","/**\n * Strategy seam between core Collection and the optional blob service\n * (#629 Task 7 — moved out of `with-shape/blobs/strategy.ts`, precedent:\n * `port/with/classified-strategy.ts`/`i18n-strategy.ts`). Lives on the\n * `/with` port (the one seam the kernel spine may import statically) so\n * `Collection`/`Vault` can hold the `NO_BLOBS` default without a\n * spine→service static import.\n *\n * Core imports `BlobsStrategy` as a TYPE-ONLY symbol and `NO_BLOBS` as a\n * minimal runtime stub. Neither pulls in the heavy `BlobSet` / chunk /\n * MIME machinery — those only arrive when the consumer explicitly\n * imports `@noy-db/hub/blobs` (see `via/blob/index.ts` →\n * `withBlobs()` factory).\n *\n * This file is intentionally tiny so the bundler keeps it in the graph\n * without dragging everything else in.\n *\n * @internal\n */\n\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { ObjectProjection } from '../../with-shape/blobs/object-projection.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { linkBlobVia } from '../../via/blob/binding.js'\n\n// Install the blob Via binder EAGERLY (#629 Task 7) — `blobFields` policies\n// are plain object literals (there is no `blob.*()` declaration factory to\n// hang a lazy money/i18n-style link on), so the binder must be installed\n// before `compileViaBindings` ever compiles one. This port module is\n// already unconditionally imported by the kernel spine (for the `NO_BLOBS`\n// strategy default below), so linking here guarantees it — same rationale\n// as `port/with/classified-strategy.ts`'s eager `linkClassifiedVia()`. The\n// binding module is hook-free glue (declare/describe/erase) and imports no\n// machinery, so the eager cost is a few hundred bytes, not `BlobSet`.\nlinkBlobVia()\n\n/**\n * Args forwarded by `Collection.blob(id)` to the active strategy's\n * `openSlot`. The strategy is responsible for returning a live\n * `BlobSet` bound to the given record.\n *\n * @internal\n */\nexport interface BlobsStrategyOpenArgs {\n readonly store: NoydbStore\n readonly vault: string\n readonly collection: string\n readonly recordId: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly encrypted: boolean\n readonly userId: string\n /**\n * Collection opts into per-record keys (`perRecordKeys`), so its blobs are\n * erasable: new uploads mint a per-blob content CEK (crypto-shreddable at\n * `refCount → 0`). Off → legacy shared-`_blob`-DEK chunks. See the per-blob\n * CEK design spec.\n */\n readonly erasableBlobs?: boolean\n /**\n * Collection has `tiers` active (#724 I1 completion). Combined with\n * `erasableBlobs` false, a content write is refused —\n * `UnsupportedTierCompositionError` — since a legacy blob has no\n * per-blob `_cek` and can never be tier-isolated on elevate/demote.\n */\n readonly tiersActive?: boolean\n /**\n * Vault is in debug-plaintext mode (`encrypt: false` + `debugPlaintext: true`).\n * Blobs are then written as a single un-gzipped object (one base64 chunk) so\n * the stored object is directly recoverable with native tools (`base64 -d`),\n * matching the directly-inspectable record layout. Dev-only — see the\n * plaintext/debug-store-mode design.\n */\n readonly debugPlaintext?: boolean\n /**\n * Object projection for `external` blob fields (`createNoydb({ objectStore })`).\n * When present, fields declared `external` in `blobFields` route their raw\n * bytes here instead of the encrypted-chunk path.\n */\n readonly objectStore?: ObjectProjection\n /** Per-collection blob field policies — used to resolve `external` / `public`. */\n readonly blobFields?: BlobFieldsConfig\n /**\n * The caller's unlocked keyring (#749). Threaded through so `BlobSet.atTier()`\n * can run `assertTierAccess` — the SAME clearance gate `putAtTier`/`elevate`/\n * `demote` run before any tier `getDEK` call — rather than relying on a bare\n * `getDEK`, which auto-mints a DEK for ANY role and never throws (verified: it\n * is not itself a gate). Omitted only by a construction path that never calls\n * `atTier()`.\n */\n readonly keyring?: UnlockedKeyring\n}\n\n/**\n * The seam interface. `@internal` — do not build public APIs on this\n * shape; it can evolve freely until blobs are extracted into their\n * own package, at which point it will be promoted to public.\n *\n * @internal\n */\nexport interface BlobsStrategy {\n openSlot(args: BlobsStrategyOpenArgs): BlobSet\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store (#808).\n *\n * Lives on the seam rather than on the `withBlobs()` return type so the\n * service has exactly one name (#844b) — `NO_BLOBS` answers with zeros,\n * which is the truthful reading of \"blobs were never enabled\".\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Default strategy for collections that did not opt into blob storage.\n * Every operation surfaces an actionable error that points the caller\n * at the opt-in path.\n *\n * @internal\n */\nexport const NO_BLOBS: BlobsStrategy = {\n openSlot() {\n throw new Error(\n 'Blob storage is not enabled on this Noydb instance. ' +\n 'Import `{ withBlobs }` from \"@noy-db/hub/blobs\" and pass `withBlobs()` to `createNoydb({ blobsStrategy: withBlobs() })`.',\n )\n },\n cacheStats() {\n return { hits: 0, misses: 0, bytesDownloaded: 0 }\n },\n}\n","/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../../kernel/collection.js'\nimport type { SlotInfo } from '../../kernel/types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../../kernel/types.js'\nimport { ValidationError } from '../../kernel/errors.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Via port brand marker — lets a `BlobFieldPolicy` satisfy the kernel's\n * opaque `ViaDescriptor` (#629 Task 7). Optional (the\n * `ClassifiedFieldSpec._viaBrand` precedent): `blobFields` policies are\n * plain object literals with no declaration factory to stamp it, so a\n * mandatory field would break every existing declaration.\n */\n readonly _viaBrand?: 'blob'\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n /**\n * **Legal hold.** When this predicate returns `true`, the slot is\n * never evicted — `retainDays`/`evictWhen` are overridden. Use for a\n * litigation / audit hold on a fiscal document: the blob stays until\n * the predicate returns `false` (the hold is released). Fail-closed:\n * if the predicate throws, the slot is treated as held.\n */\n readonly legalHold?: (record: T) => boolean\n /**\n * **Period-bound retention.** Returns the date (Date / ISO string /\n * epoch ms) until which the slot must be retained — typically derived\n * from the record's fiscal period (e.g. period end + 10 years). While\n * `now < retainUntil`, the slot is never evicted, regardless of\n * `retainDays`. Return `null`/`undefined` to impose no floor.\n * Fail-closed: a throwing function holds the slot.\n */\n readonly retainUntil?: (record: T) => Date | string | number | null | undefined\n /**\n * **External projection.** When `true`, this field's bytes are stored in the\n * vault's `ObjectProjection` (`createNoydb({ objectStore })`) as a single raw,\n * **unencrypted** object — servable directly from S3/CDN and processable by\n * native tooling — instead of the encrypted-chunk path. The encrypted slot\n * record remains the catalog (anchoring invariant). Requires an `objectStore`;\n * **outside the zero-knowledge guarantee** — use only for assets meant to\n * leave the vault. See the as-aws-s3 design spec.\n */\n readonly external?: boolean\n /**\n * For an `external` field: make the object world-readable (CDN origin) rather\n * than presigned-only. Default `false` (presigned). Ignored unless `external`.\n */\n readonly public?: boolean\n /**\n * For an `external` field: how to stamp a **backlink** (this record's\n * vault/collection/id/field) onto the object's metadata — the self-describing\n * \"secondary store\" that powers reconcile / DR / import re-pairing.\n * - `'opaque-token'` (default): a random id; preserves the opaque-bucket\n * property (no names leak); the token is also recorded on the slot.\n * - `'encrypted'`: the reference encrypted under the blob DEK (ZK-preserving;\n * falls back to `'opaque-token'` on a plaintext vault).\n * - `'plain'`: the reference in cleartext metadata — **leaks structure** to\n * bucket readers; only for non-sensitive deployments.\n * - `'none'`: no backlink.\n */\n readonly backlink?: 'opaque-token' | 'encrypted' | 'plain' | 'none'\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n /** `'budget'` — evicted by the #808 cache-budget LRU pass, not a policy. */\n readonly reason: 'ttl' | 'predicate' | 'both' | 'budget'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted by the policy pass across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /**\n * Number of audit entries written — one per policy eviction plus one per\n * budget SLOT eviction (dropping a device-local cache copy writes none).\n */\n readonly auditEntries: number\n /**\n * Number of slots that would have evicted (TTL/predicate triggered)\n * but were retained by a `legalHold` or `retainUntil` floor.\n */\n readonly held: number\n /**\n * #808: number of slots whose due policy eviction was skipped because they\n * are pinned for offline on THIS device (pin state is device-local).\n */\n readonly pinned: number\n /**\n * #808: number of evictions performed by the `cacheBudget` LRU pass —\n * internal slots evicted through the standard eviction writer plus\n * device-local external cache copies dropped. 0 when no budget was given.\n */\n readonly budgetEvicted: number\n /** #808: plaintext bytes freed by the `cacheBudget` LRU pass. */\n readonly budgetBytesFreed: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n /**\n * #808 mobile cache budget: cap the locally-cached UNPINNED blob bytes.\n * After the policy pass, a dedicated LRU pass walks every collection's\n * slots (pinned slots and `legalHold`/`retainUntil`-held slots are exempt\n * and uncounted) and evicts oldest-access-first — internal slots through\n * the standard eviction writer (`deleteSlot` + a `'budget'` audit entry),\n * external slots by dropping their device-local encrypted cache copy only\n * (the object-store copy is authoritative and untouched) — until the\n * remaining unpinned bytes fit `maxBytes`. Internal slots count their\n * uncompressed `size`; external slots count `cachedBytes` (0 = nothing\n * local, nothing to evict). LRU order comes from the device-local\n * `lastAccessAt` stamp, falling back to `uploadedAt`.\n */\n readonly cacheBudget?: { readonly maxBytes: number }\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<EnclaveKey>\n /**\n * Resolve a collection's declared `blobFields` config. Returns an\n * empty map for collections without the config — the walk skips\n * those.\n */\n readonly getBlobFields: <T>(collection: string) => BlobFieldsConfig<T> | null\n /** List collection names in the vault. */\n readonly listCollections: () => Promise<string[]>\n /** List record ids in a collection. */\n readonly listRecords: (collection: string) => Promise<string[]>\n /** Decrypt and return the record. Null when absent. */\n readonly getRecord: <T>(collection: string, id: string) => Promise<T | null>\n /** Return the BlobSet-like handle for a record's slots. */\n readonly listSlots: (collection: string, id: string) => Promise<SlotInfo[]>\n /** Delete a slot and decrement its blob's refCount. */\n readonly deleteSlot: (collection: string, id: string, slotName: string) => Promise<void>\n /**\n * #808: drop a slot's DEVICE-LOCAL cached copy (the encrypted external\n * side-cache) without touching the slot or the object store. Returns the\n * plaintext bytes freed. Optional — absent (or a 0 return) means external\n * cache entries simply cannot be budget-evicted through this context.\n */\n readonly dropLocalCache?: (collection: string, id: string, slotName: string) => Promise<number>\n}\n\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n if (options.cacheBudget !== undefined\n && (!Number.isFinite(options.cacheBudget.maxBytes) || options.cacheBudget.maxBytes < 0)) {\n throw new ValidationError('compact(): cacheBudget.maxBytes must be a non-negative finite number (#808)')\n }\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let held = 0\n let pinned = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n // #808: device-local offline pin — an exemption inside this existing\n // pass, exactly like a hold: counted, never evicted. `slot.pinned` is\n // the device-local annotation `BlobSet.list()` stamps from the\n // withBlobs() pin registry (never synced state).\n if (slot.pinned === true) {\n pinned += 1\n continue\n }\n\n // Retention floor: a legal hold or period-bound retainUntil\n // blocks an otherwise-due eviction. Counted, never evicted.\n if (isHeld(policy, record, now)) {\n held += 1\n continue\n }\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n // #808: cache-budget LRU pass — runs AFTER the policy pass so a slot that\n // just evicted no longer counts toward the budget. Reuses this module's own\n // eviction writer (deleteSlot + audit), never a parallel one.\n let budgetEvicted = 0\n let budgetBytesFreed = 0\n if (options.cacheBudget !== undefined) {\n const pass = await runBudgetPass(ctx, options.cacheBudget.maxBytes, {\n now, dryRun, remainingEvictions: maxEvictions - evicted,\n })\n budgetEvicted = pass.evicted\n budgetBytesFreed = pass.bytesFreed\n auditEntries += pass.auditEntries\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n pinned,\n budgetEvicted,\n budgetBytesFreed,\n byCollection,\n }\n}\n\n// ─── #808: cache-budget LRU pass ───────────────────────────────────────\n\ninterface BudgetCandidate {\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly eTag: string\n readonly bytes: number\n /** `'slot'` — internal blob, evicted via `deleteSlot`; `'cache'` — external device-local copy, dropped via `dropLocalCache`. */\n readonly kind: 'slot' | 'cache'\n readonly lastAccessMs: number\n}\n\n/**\n * Enforce `cacheBudget.maxBytes` over the locally-cached UNPINNED blob bytes\n * (see {@link CompactRunOptions.cacheBudget} for the full contract). Walks\n * EVERY collection — unlike the policy pass, the budget is not gated on a\n * declared `blobFields` config — but still honors a declared policy's\n * `legalHold`/`retainUntil` floor where one exists.\n */\nasync function runBudgetPass(\n ctx: CompactionContext,\n maxBytes: number,\n opts: { now: Date; dryRun: boolean; remainingEvictions: number },\n): Promise<{ evicted: number; bytesFreed: number; auditEntries: number }> {\n const candidates: BudgetCandidate[] = []\n let total = 0\n\n for (const collectionName of await ctx.listCollections()) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n for (const recordId of await ctx.listRecords(collectionName)) {\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n if (slots.length === 0) continue\n // The record is only needed to evaluate a declared policy's retention\n // floor — skip the decrypt entirely for policy-less collections.\n const record = config\n ? await ctx.getRecord<unknown>(collectionName, recordId).catch(() => null)\n : null\n for (const slot of slots) {\n if (slot.pinned === true) continue // pinned: exempt AND uncounted (#808)\n const policy = config?.[slot.name]\n if (policy && record !== null && isHeld(policy, record, opts.now)) continue // floor blocks budget too\n const lastAccessMs = Date.parse(slot.lastAccessAt ?? slot.uploadedAt) || 0\n const isExternal = slot.external !== undefined\n const bytes = isExternal ? slot.cachedBytes ?? 0 : slot.size\n if (bytes <= 0) continue // external with no local copy: nothing cached to evict\n total += bytes\n candidates.push({\n collection: collectionName, recordId, slotName: slot.name, eTag: slot.eTag,\n bytes, kind: isExternal ? 'cache' : 'slot', lastAccessMs,\n })\n }\n }\n }\n\n // Oldest access first; deterministic tiebreak so equal timestamps (common\n // within one ms) never make the eviction order flap between runs.\n candidates.sort((a, b) =>\n a.lastAccessMs - b.lastAccessMs\n || a.collection.localeCompare(b.collection)\n || a.recordId.localeCompare(b.recordId)\n || a.slotName.localeCompare(b.slotName))\n\n let evicted = 0\n let bytesFreed = 0\n let auditEntries = 0\n for (const c of candidates) {\n if (total <= maxBytes || evicted >= opts.remainingEvictions) break\n if (!opts.dryRun) {\n if (c.kind === 'slot') {\n await ctx.deleteSlot(c.collection, c.recordId, c.slotName)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(c.collection, c.recordId, c.slotName),\n collection: c.collection,\n recordId: c.recordId,\n slotName: c.slotName,\n blobHash: c.eTag,\n reason: 'budget',\n evictedAt: opts.now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n } else {\n // Device-local cache drop only — the slot (catalog) and the\n // object-store copy are untouched, so no eviction audit entry.\n await ctx.dropLocalCache?.(c.collection, c.recordId, c.slotName)\n }\n }\n evicted += 1\n bytesFreed += c.bytes\n total -= c.bytes\n }\n\n return { evicted, bytesFreed, auditEntries }\n}\n\n/**\n * Whether a retention floor (legal hold or period-bound `retainUntil`)\n * currently blocks eviction of this record's slots. Fail-closed: a\n * throwing predicate holds the slot.\n */\nfunction isHeld<T>(policy: BlobFieldPolicy<T>, record: T, now: Date): boolean {\n if (policy.legalHold) {\n try {\n if (policy.legalHold(record)) return true\n } catch {\n return true\n }\n }\n if (policy.retainUntil) {\n try {\n const until = policy.retainUntil(record)\n if (until !== null && until !== undefined) {\n const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(String(until))\n if (!Number.isFinite(t)) return true // fail-closed: unparseable retainUntil holds the slot\n if (t > now.getTime()) return true\n }\n } catch {\n return true\n }\n }\n return false\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n const identity = { collection: BLOB_EVICTION_AUDIT_COLLECTION, id: entry.id, by: entry.actor, version: 1 }\n const body = { ts: entry.evictedAt }\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n envelope = buildRecordEnvelope(identity, { ...body, iv, data })\n } else {\n envelope = buildRecordEnvelope(identity, { ...body, iv: '', data: json })\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
|