@noy-db/hub 0.4.0-pre.1 → 0.4.0-pre.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/api-ZDKTWNBB.js +20 -0
- package/dist/attestation/index.js +18 -18
- package/dist/{backup-LU2DTZTI.js → backup-2UX7PRM5.js} +13 -13
- package/dist/blobs/index.js +30 -15
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +12 -12
- package/dist/broker/index.js.map +1 -1
- package/dist/cargo/index.js +53 -43
- package/dist/{chunk-YTGDPCPW.js → chunk-2J6UEN2V.js} +42 -18
- package/dist/chunk-2J6UEN2V.js.map +1 -0
- package/dist/{chunk-IY23KFNZ.js → chunk-35NMVJ7I.js} +3 -3
- package/dist/{chunk-S3BFKGET.js → chunk-3HUYRN24.js} +4 -4
- package/dist/chunk-3HUYRN24.js.map +1 -0
- package/dist/{walk-XS3PKM6I.js → chunk-3V3XR4S4.js} +4 -13
- package/dist/{walk-XS3PKM6I.js.map → chunk-3V3XR4S4.js.map} +1 -1
- package/dist/{chunk-RTEK4C4I.js → chunk-4JQK3L4V.js} +2 -2
- package/dist/{chunk-3ELCWU56.js → chunk-4ZKNC6A6.js} +2 -2
- package/dist/{chunk-VQNJ7UZL.js → chunk-55LKVALY.js} +106 -10
- package/dist/chunk-55LKVALY.js.map +1 -0
- package/dist/{chunk-VAIS3SOM.js → chunk-55LX6FSX.js} +2 -2
- package/dist/chunk-5D2ALSM5.js +19 -0
- package/dist/chunk-5D2ALSM5.js.map +1 -0
- package/dist/{chunk-6HPG2YNX.js → chunk-5DRROK5W.js} +2 -2
- package/dist/{chunk-S6Q37VGE.js → chunk-5GQEU4DN.js} +2 -2
- package/dist/chunk-5JOFWL4G.js +25 -0
- package/dist/chunk-5JOFWL4G.js.map +1 -0
- package/dist/{chunk-TOPYT5U5.js → chunk-63FE525P.js} +6 -6
- package/dist/chunk-6LJY7X5A.js +18 -0
- package/dist/chunk-6LJY7X5A.js.map +1 -0
- package/dist/{chunk-JVBO4SZL.js → chunk-6UBZ6I5Z.js} +2 -2
- package/dist/{chunk-BOKIKJJB.js → chunk-6ZNSB3H6.js} +5 -17
- package/dist/chunk-6ZNSB3H6.js.map +1 -0
- package/dist/{chunk-BMQWTQZ4.js → chunk-7YVBQJ7K.js} +13 -355
- package/dist/chunk-7YVBQJ7K.js.map +1 -0
- package/dist/{chunk-UFEVO57O.js → chunk-ABS7FMMD.js} +6 -6
- package/dist/chunk-AFHQATY4.js +47 -0
- package/dist/chunk-AFHQATY4.js.map +1 -0
- package/dist/{chunk-HOONYZY2.js → chunk-AKKRXT23.js} +7 -7
- package/dist/chunk-AKKRXT23.js.map +1 -0
- package/dist/{chunk-DDHM2TLX.js → chunk-AT2JJEKJ.js} +4 -4
- package/dist/chunk-AT2JJEKJ.js.map +1 -0
- package/dist/{chunk-M75ZZHSR.js → chunk-BLBAQRBM.js} +2 -2
- package/dist/{chunk-3SV2EPMN.js → chunk-BLCWSJLN.js} +88 -81
- package/dist/chunk-BLCWSJLN.js.map +1 -0
- package/dist/chunk-CADUYBQD.js +151 -0
- package/dist/chunk-CADUYBQD.js.map +1 -0
- package/dist/{chunk-G6EQYAL6.js → chunk-CI32CAFS.js} +5 -5
- package/dist/{chunk-FH52K7EG.js → chunk-CUZLNGD5.js} +2 -6
- package/dist/{chunk-FH52K7EG.js.map → chunk-CUZLNGD5.js.map} +1 -1
- package/dist/{chunk-2P7EGNKF.js → chunk-CXQW2NWO.js} +145 -3
- package/dist/chunk-CXQW2NWO.js.map +1 -0
- package/dist/{chunk-6B4DYGWO.js → chunk-DABACXEF.js} +3 -3
- package/dist/{chunk-F6T6L2UK.js → chunk-DMH3VZU5.js} +4 -4
- package/dist/chunk-DSDXAKIP.js +50 -0
- package/dist/chunk-DSDXAKIP.js.map +1 -0
- package/dist/{chunk-MQ6WOPSY.js → chunk-DZ2JXHWK.js} +5 -5
- package/dist/chunk-DZFQVICS.js +23 -0
- package/dist/chunk-DZFQVICS.js.map +1 -0
- package/dist/chunk-E23UGFVC.js +63 -0
- package/dist/chunk-E23UGFVC.js.map +1 -0
- package/dist/{chunk-UBNCR3YK.js → chunk-EE7UHLKH.js} +4 -4
- package/dist/chunk-EIPVYUEP.js +654 -0
- package/dist/chunk-EIPVYUEP.js.map +1 -0
- package/dist/{chunk-EGCRBCNA.js → chunk-EKRSCRNM.js} +3 -3
- package/dist/{chunk-YMUGBZN2.js → chunk-FQ5CHJR7.js} +1 -1
- package/dist/chunk-FQ5CHJR7.js.map +1 -0
- package/dist/{chunk-LB6OH6K2.js → chunk-FQZ6XTQ7.js} +17 -20
- package/dist/chunk-FQZ6XTQ7.js.map +1 -0
- package/dist/{chunk-IRQY7IR5.js → chunk-FRMPM7WG.js} +4 -4
- package/dist/{chunk-DVUZMT2W.js → chunk-G2JUIDFA.js} +9 -9
- package/dist/{chunk-DVUZMT2W.js.map → chunk-G2JUIDFA.js.map} +1 -1
- package/dist/{chunk-RND5ZPNP.js → chunk-G6WWZNJP.js} +13 -13
- package/dist/chunk-G6WWZNJP.js.map +1 -0
- package/dist/chunk-GJW4HX2O.js +1 -0
- package/dist/{chunk-T6GU5PPZ.js → chunk-GU3IP74F.js} +6 -6
- package/dist/{chunk-5OI6BR4H.js → chunk-GWWOGNBK.js} +308 -18
- package/dist/chunk-GWWOGNBK.js.map +1 -0
- package/dist/{chunk-UNCOC7QH.js → chunk-H2GPHUZD.js} +12 -13
- package/dist/chunk-H2GPHUZD.js.map +1 -0
- package/dist/{chunk-7CI6CP7Z.js → chunk-HMU5P6RL.js} +5 -5
- package/dist/chunk-HMU5P6RL.js.map +1 -0
- package/dist/{chunk-YJV2UZ7H.js → chunk-I5GEJX3R.js} +5 -5
- package/dist/chunk-I5GEJX3R.js.map +1 -0
- package/dist/{chunk-GWKAVKHI.js → chunk-IG6KUWBX.js} +2 -2
- package/dist/chunk-IG6KUWBX.js.map +1 -0
- package/dist/{chunk-MTQPRQHB.js → chunk-IPUG56GJ.js} +15 -5
- package/dist/{chunk-MTQPRQHB.js.map → chunk-IPUG56GJ.js.map} +1 -1
- package/dist/{chunk-SMIRGMS7.js → chunk-IXNCZTWA.js} +2 -2
- package/dist/{chunk-RM2XV574.js → chunk-IXTSNGYY.js} +3 -3
- package/dist/{chunk-KUPKLSX5.js → chunk-IYZISPGD.js} +7 -5
- package/dist/chunk-IYZISPGD.js.map +1 -0
- package/dist/{chunk-SMGEYW6G.js → chunk-JDOMW53O.js} +26 -16
- package/dist/chunk-JDOMW53O.js.map +1 -0
- package/dist/{chunk-PK6QCH2C.js → chunk-JJZGIE4U.js} +6 -5
- package/dist/{chunk-DRWBEDYO.js → chunk-JYJFGD2H.js} +2 -2
- package/dist/{chunk-TAGR72IR.js → chunk-KRCBZYTZ.js} +26 -177
- package/dist/chunk-KRCBZYTZ.js.map +1 -0
- package/dist/{chunk-HPUWOVR7.js → chunk-L2DGWBCT.js} +14 -14
- package/dist/chunk-L2DGWBCT.js.map +1 -0
- package/dist/{chunk-VEALDPHW.js → chunk-L3YYLT4E.js} +15 -2
- package/dist/chunk-L3YYLT4E.js.map +1 -0
- package/dist/{chunk-A7JJLVGH.js → chunk-LNPUSMZL.js} +29 -31
- package/dist/chunk-LNPUSMZL.js.map +1 -0
- package/dist/{chunk-KUIB4B5Q.js → chunk-LNZKOUJR.js} +2 -2
- package/dist/{chunk-IM3QVF53.js → chunk-MBKVNOJS.js} +6 -6
- package/dist/chunk-MCHBCNCE.js +367 -0
- package/dist/chunk-MCHBCNCE.js.map +1 -0
- package/dist/{chunk-VCBHAE4R.js → chunk-MHMLXJ3I.js} +2 -2
- package/dist/chunk-MHMLXJ3I.js.map +1 -0
- package/dist/{chunk-WNGGJWF7.js → chunk-MSMJJB4J.js} +2 -2
- package/dist/chunk-MSMJJB4J.js.map +1 -0
- package/dist/{chunk-T36RFODW.js → chunk-MXUT7FTE.js} +2 -2
- package/dist/chunk-MXUT7FTE.js.map +1 -0
- package/dist/{chunk-BGK233EA.js → chunk-N4BBNYIB.js} +11 -9
- package/dist/chunk-N4BBNYIB.js.map +1 -0
- package/dist/{chunk-3PF6CRDT.js → chunk-NFIBXF4V.js} +1082 -1583
- package/dist/chunk-NFIBXF4V.js.map +1 -0
- package/dist/{chunk-LOF2W3JU.js → chunk-NFNJLRY7.js} +368 -52
- package/dist/chunk-NFNJLRY7.js.map +1 -0
- package/dist/{chunk-N6WF5PKC.js → chunk-NFZGZ4AK.js} +2 -2
- package/dist/{chunk-JRBNCUNT.js → chunk-NGSJZFGL.js} +21 -4
- package/dist/chunk-NGSJZFGL.js.map +1 -0
- package/dist/chunk-NJ4DYYO5.js +81 -0
- package/dist/chunk-NJ4DYYO5.js.map +1 -0
- package/dist/{chunk-2IALOXZS.js → chunk-NM5UYF6Q.js} +2 -2
- package/dist/chunk-NM5UYF6Q.js.map +1 -0
- package/dist/{chunk-JBBSYJHP.js → chunk-NYEFP7DY.js} +4 -4
- package/dist/chunk-NYEFP7DY.js.map +1 -0
- package/dist/{chunk-CCOP5XDW.js → chunk-NZ24MX4J.js} +8 -46
- package/dist/chunk-NZ24MX4J.js.map +1 -0
- package/dist/{chunk-KWVJAOIN.js → chunk-OLEPKFTW.js} +4 -4
- package/dist/chunk-OLEPKFTW.js.map +1 -0
- package/dist/{chunk-OF75ACKS.js → chunk-PEATRIYJ.js} +9 -9
- package/dist/chunk-PEOZ34EM.js +13 -0
- package/dist/chunk-PEOZ34EM.js.map +1 -0
- package/dist/{chunk-UC2ETWF3.js → chunk-PTHKZWEH.js} +2 -14
- package/dist/{chunk-UC2ETWF3.js.map → chunk-PTHKZWEH.js.map} +1 -1
- package/dist/chunk-PVEWA2DU.js +23 -0
- package/dist/chunk-PVEWA2DU.js.map +1 -0
- package/dist/{chunk-LSASLXGC.js → chunk-QBAC3GXA.js} +1 -1
- package/dist/chunk-QBAC3GXA.js.map +1 -0
- package/dist/{chunk-RDXW3OBQ.js → chunk-QD46HPZG.js} +2 -2
- package/dist/chunk-QD46HPZG.js.map +1 -0
- package/dist/{chunk-N55W7KUL.js → chunk-QDIH2744.js} +2 -2
- package/dist/{chunk-6HY2X62D.js → chunk-QJNCB5SK.js} +2 -2
- package/dist/{chunk-4BRCIYFG.js → chunk-QX76XVQH.js} +6 -6
- package/dist/chunk-QX76XVQH.js.map +1 -0
- package/dist/{chunk-W2OG6JOL.js → chunk-QZWTRENR.js} +24 -7
- package/dist/chunk-QZWTRENR.js.map +1 -0
- package/dist/{chunk-6NQMXTYC.js → chunk-RO5NM6UI.js} +2 -2
- package/dist/chunk-RO5NM6UI.js.map +1 -0
- package/dist/chunk-SGDPRCC4.js +1075 -0
- package/dist/chunk-SGDPRCC4.js.map +1 -0
- package/dist/{chunk-2PCAVW2I.js → chunk-SHEEBRZ4.js} +2 -2
- package/dist/chunk-SMY5X46Z.js +14 -0
- package/dist/chunk-SMY5X46Z.js.map +1 -0
- package/dist/{chunk-ISMV2QVG.js → chunk-TG634KCO.js} +11 -2
- package/dist/chunk-TG634KCO.js.map +1 -0
- package/dist/{chunk-J6Q7UVQE.js → chunk-TUXQSFHZ.js} +4 -16
- package/dist/{chunk-J6Q7UVQE.js.map → chunk-TUXQSFHZ.js.map} +1 -1
- package/dist/{chunk-GULTLPFB.js → chunk-UADPR6F4.js} +3 -3
- package/dist/{chunk-GULTLPFB.js.map → chunk-UADPR6F4.js.map} +1 -1
- package/dist/{chunk-ZEQWCBCM.js → chunk-UDHBXYGG.js} +27 -5
- package/dist/chunk-UDHBXYGG.js.map +1 -0
- package/dist/{chunk-RA5VTXXG.js → chunk-V6UQZA2H.js} +2 -2
- package/dist/chunk-VG2ZSFYD.js +32 -0
- package/dist/chunk-VG2ZSFYD.js.map +1 -0
- package/dist/chunk-VKQCVU2Z.js +21 -0
- package/dist/chunk-VKQCVU2Z.js.map +1 -0
- package/dist/{chunk-QDPZWTN2.js → chunk-W3G5REIU.js} +2 -2
- package/dist/{chunk-WEYZTLXN.js → chunk-W3L64Y3J.js} +2 -2
- package/dist/{chunk-ONTBCHCP.js → chunk-W66AM5IB.js} +2 -2
- package/dist/chunk-W66AM5IB.js.map +1 -0
- package/dist/{chunk-A5BDDEXK.js → chunk-WBKZUO5V.js} +26 -43
- package/dist/chunk-WBKZUO5V.js.map +1 -0
- package/dist/{chunk-CBSY2N76.js → chunk-WLXMTYGO.js} +2 -2
- package/dist/{chunk-3U3FBR7O.js → chunk-X3F5YJG2.js} +2 -2
- package/dist/{chunk-GQNGCEP5.js → chunk-X45JYIAY.js} +3 -3
- package/dist/chunk-X45JYIAY.js.map +1 -0
- package/dist/{chunk-6XE5TMPR.js → chunk-X4MVWPHV.js} +3 -3
- package/dist/chunk-X4MVWPHV.js.map +1 -0
- package/dist/chunk-XZHEOSQ2.js +16 -0
- package/dist/chunk-XZHEOSQ2.js.map +1 -0
- package/dist/chunk-Y2NBBT6K.js +18 -0
- package/dist/chunk-Y2NBBT6K.js.map +1 -0
- package/dist/{chunk-DDATL6PU.js → chunk-Y2NJBNX3.js} +8 -8
- package/dist/{chunk-DDATL6PU.js.map → chunk-Y2NJBNX3.js.map} +1 -1
- package/dist/chunk-YMUFFJCO.js +23 -0
- package/dist/chunk-YMUFFJCO.js.map +1 -0
- package/dist/{chunk-O7WJ47EF.js → chunk-YWWQM7KP.js} +5 -14
- package/dist/chunk-YWWQM7KP.js.map +1 -0
- package/dist/{chunk-7O3AYBC7.js → chunk-YZJSJOUI.js} +235 -65
- package/dist/chunk-YZJSJOUI.js.map +1 -0
- package/dist/{chunk-5WZXVULX.js → chunk-ZTLOXFPB.js} +2 -2
- package/dist/chunk-ZTLOXFPB.js.map +1 -0
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-WQVIJAW2.js → classified-marker-7PKJBFCG.js} +3 -3
- package/dist/collection-facade-6I5K5IOD.js +45 -0
- package/dist/{computed-DSMPXG2Y.js → computed-AB45PS4V.js} +3 -3
- package/dist/consent/index.js +13 -9
- package/dist/consent/index.js.map +1 -1
- package/dist/cover/index.js +37 -0
- package/dist/crdt/index.js +4 -0
- package/dist/crdt/index.js.map +1 -1
- package/dist/custody/index.js +26 -0
- package/dist/{dead-filter-2A7IAJ3D.js → dead-filter-FL3WIUZS.js} +2 -2
- package/dist/{chunk-GYYCX5K6.js → delegation-QXZ4MPPH.js} +12 -6
- package/dist/{chunk-GYYCX5K6.js.map → delegation-QXZ4MPPH.js.map} +1 -1
- package/dist/derivations/index.js +12 -12
- package/dist/derive-VGS7FKE3.js +22 -0
- package/dist/directory/index.js +46 -0
- package/dist/dispatch-EGGC7UD7.js +72 -0
- package/dist/dispatch-EGGC7UD7.js.map +1 -0
- package/dist/dispatch-GDSTUTVP.js +194 -0
- package/dist/dispatch-GDSTUTVP.js.map +1 -0
- package/dist/{enclave-7MW6ZGXW.js → enclave-SPEEDYZ4.js} +11 -11
- package/dist/executor-IEBLTBCW.js +9 -0
- package/dist/executor-OTF556PL.js +28 -0
- package/dist/executor-RM7XWQDU.js +9 -0
- package/dist/export-accessible-P42BQ4PB.js +23 -0
- package/dist/extract-partition-TCYXRWBN.js +38 -0
- package/dist/{fanout-sidecar-WDYQ77VB.js → fanout-sidecar-SSMRSC2L.js} +9 -9
- package/dist/find-OZMTRIK3.js +11 -0
- package/dist/forget/index.js +11 -11
- package/dist/forget/index.js.map +1 -1
- package/dist/guards/index.js +6 -6
- package/dist/history/index.js +16 -12
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +16 -14
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.d.ts +42 -42
- package/dist/index.js +330 -2106
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +4 -2
- package/dist/indexing/index.js.map +1 -1
- package/dist/introspection/index.js +28 -0
- package/dist/issue-FCKN5O6C.js +19 -0
- package/dist/kernel/best-effort-revert.d.ts +9 -1
- package/dist/kernel/collection-config.d.ts +12 -70
- package/dist/kernel/collection.d.ts +11 -23
- package/dist/kernel/enclave/crypto.d.ts +8 -8
- package/dist/kernel/enclave/index.d.ts +3 -3
- package/dist/kernel/errors.d.ts +41 -22
- package/dist/kernel/lazy.d.ts +30 -0
- package/dist/kernel/noydb.d.ts +81 -124
- package/dist/kernel/query/builder.d.ts +10 -10
- package/dist/kernel/query/index.d.ts +0 -6
- package/dist/kernel/query/scan-builder.d.ts +4 -4
- package/dist/kernel/sync-policy.d.ts +76 -4
- package/dist/kernel/types.d.ts +146 -86
- package/dist/kernel/validation.d.ts +33 -33
- package/dist/kernel/vault.d.ts +41 -229
- package/dist/kernel/via/dispatch.d.ts +7 -0
- package/dist/{ledger-33BC4TR7.js → ledger-6XBEVWYP.js} +11 -11
- package/dist/liberate-CYUOS4NC.js +24 -0
- package/dist/{link-set-QK22TDEX.js → link-set-NK6L2A5S.js} +11 -11
- package/dist/materialized-views/index.js +22 -19
- package/dist/money/index.js +31 -0
- package/dist/noydb-FZZIMLCI.js +81 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +15 -13
- package/dist/pod/index.js +26 -12
- package/dist/{policy-NAHB2TUT.js → policy/index.js} +6 -6
- package/dist/port/with/archive-strategy.d.ts +31 -0
- package/dist/port/with/blob-strategy.d.ts +15 -5
- package/dist/port/with/collection-options.d.ts +171 -0
- package/dist/port/with/collection-registries.d.ts +62 -0
- package/dist/port/with/strategies.d.ts +134 -0
- package/dist/port/with/team-strategy.d.ts +3 -2
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-JVTMJ6OW.js → post-register-VFIYISUK.js} +6 -6
- package/dist/query/index.js +6 -46
- package/dist/{aggregate → reduce}/index.js +24 -17
- package/dist/reduce/index.js.map +1 -0
- package/dist/register-UKU2J6MN.js +25 -0
- package/dist/registry-2VCWVHZS.js +19 -0
- package/dist/registry-7Y5WF7GS.js +9 -0
- package/dist/{registry-VW6P6A3J.js → registry-BWD2ED52.js} +2 -2
- package/dist/registry-DTWRJLSF.js +18 -0
- package/dist/request-withdrawal-JYDZ5XY2.js +31 -0
- package/dist/{reveal-LGBOM2BO.js → reveal-CL2BCW6A.js} +6 -6
- package/dist/revoke-2TSAD2T4.js +24 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +26 -0
- package/dist/sealed-record/index.js +12 -12
- package/dist/search/index.js +36 -0
- package/dist/{seed-PWK45HDF.js → seed-NUVJEL7T.js} +12 -12
- package/dist/seed-NUVJEL7T.js.map +1 -0
- package/dist/sequence/index.js +30 -0
- package/dist/session/index.js +13 -9
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.js +6 -2
- package/dist/shadow/index.js.map +1 -1
- package/dist/signer-K7Z2EOMG.js +25 -0
- package/dist/snapshots/index.js +14 -10
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-FJED42DM.js → stale-A4J26OIC.js} +11 -10
- package/dist/storage-YVHZUISN.js +23 -0
- package/dist/store/index.js +22 -0
- package/dist/{store-coordination-provider-APODTV4W.js → store-coordination-provider-EWRZL7XF.js} +3 -3
- package/dist/sync/index.js +15 -12
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.js +33 -48
- package/dist/tiers/index.js +10 -10
- package/dist/to/index.js +1 -1
- package/dist/{tx → transactions}/index.js +7 -3
- package/dist/transactions/index.js.map +1 -0
- package/dist/util/index.js +1 -1
- package/dist/{verify-26Y5GNMK.js → verify-7XWHJWUN.js} +5 -5
- package/dist/via/blob/active.d.ts +23 -7
- package/dist/via/blob/binding.d.ts +1 -1
- package/dist/via/blob/index.d.ts +7 -1
- package/dist/via/i18n/core.d.ts +1 -1
- package/dist/via/i18n/index.d.ts +2 -0
- package/dist/via/money/money-reducer.d.ts +2 -2
- package/dist/walk-P5D6CF4V.js +21 -0
- package/dist/with-audit/consent/index.d.ts +2 -0
- package/dist/with-audit/forget/active.d.ts +3 -3
- package/dist/with-audit/forget/index.d.ts +6 -5
- package/dist/with-audit/forget/strategy.d.ts +3 -3
- package/dist/with-audit/guards/executor.d.ts +4 -4
- package/dist/with-audit/guards/immutable-guard.d.ts +3 -3
- package/dist/with-audit/guards/index.d.ts +1 -1
- package/dist/with-audit/guards/registry.d.ts +3 -3
- package/dist/with-audit/guards/transition-guard.d.ts +3 -3
- package/dist/with-audit/guards/types.d.ts +10 -10
- package/dist/with-audit/guards/with-guard.d.ts +2 -2
- package/dist/with-audit/periods/index.d.ts +2 -0
- package/dist/with-audit/periods/vault-facade.d.ts +6 -0
- package/dist/with-audit/portability/export-accessible.d.ts +4 -4
- package/dist/with-audit/portability/request-withdrawal.d.ts +3 -3
- package/dist/with-audit/portability/withdraw-accessible.d.ts +2 -2
- package/dist/with-audit/sealed-record/strategy.d.ts +1 -1
- package/dist/with-audit/tiers/index.d.ts +1 -0
- package/dist/with-cargo/adopt-partition.d.ts +7 -7
- package/dist/{legacy/kernel.d.ts → with-cargo/floor.d.ts} +4 -4
- package/dist/with-cargo/index.d.ts +9 -2
- package/dist/with-commit/crdt/index.d.ts +2 -0
- package/dist/with-commit/history/index.d.ts +2 -0
- package/dist/with-commit/history/time-machine.d.ts +2 -2
- package/dist/with-commit/numbering/descriptor.d.ts +16 -6
- package/dist/with-commit/tx/active.d.ts +5 -5
- package/dist/with-commit/tx/index.d.ts +2 -2
- package/dist/with-commit/tx/invariants.d.ts +1 -1
- package/dist/with-commit/tx/strategy.d.ts +3 -3
- package/dist/with-fork/shadow/index.d.ts +2 -0
- package/dist/with-fork/snapshots/active.d.ts +2 -2
- package/dist/with-fork/snapshots/index.d.ts +3 -1
- package/dist/with-fork/snapshots/noydb-facade.d.ts +3 -3
- package/dist/with-fork/snapshots/strategy.d.ts +2 -2
- package/dist/with-formula/derivations/dispatch.d.ts +98 -0
- package/dist/with-formula/derivations/executor.d.ts +2 -2
- package/dist/with-formula/derivations/index.d.ts +4 -1
- package/dist/with-formula/derivations/registry.d.ts +3 -3
- package/dist/with-formula/derivations/stale.d.ts +2 -2
- package/dist/with-formula/derivations/types.d.ts +3 -3
- package/dist/with-formula/derivations/with-derivation.d.ts +2 -2
- package/dist/with-formula/derivations/with-rollup.d.ts +15 -8
- package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +3 -3
- package/dist/with-formula/materialized-views/dispatch.d.ts +75 -0
- package/dist/with-formula/materialized-views/index.d.ts +2 -1
- package/dist/with-formula/materialized-views/registry.d.ts +4 -4
- package/dist/with-formula/materialized-views/types.d.ts +11 -11
- package/dist/with-formula/materialized-views/with-materialized-view.d.ts +2 -2
- package/dist/with-formula/overlay-views/index.d.ts +1 -1
- package/dist/with-formula/overlay-views/registry.d.ts +4 -4
- package/dist/with-formula/overlay-views/types.d.ts +3 -3
- package/dist/with-formula/overlay-views/virtual-collection.d.ts +2 -2
- package/dist/with-formula/overlay-views/with-overlayed-view.d.ts +2 -2
- package/dist/with-lookup/indexing/active.d.ts +5 -5
- package/dist/with-lookup/indexing/index.d.ts +3 -1
- package/dist/with-lookup/indexing/strategy.d.ts +3 -3
- package/dist/with-lookup/{aggregate → reduce}/active.d.ts +8 -8
- package/dist/with-lookup/{aggregate → reduce}/groupby.d.ts +21 -21
- package/dist/with-lookup/reduce/index.d.ts +34 -0
- package/dist/with-lookup/{aggregate → reduce}/reducers.d.ts +11 -12
- package/dist/with-lookup/{aggregate/aggregation.d.ts → reduce/reduction.d.ts} +21 -21
- package/dist/with-lookup/{aggregate → reduce}/strategy.d.ts +15 -15
- package/dist/with-lookup/search/build-docs.d.ts +7 -2
- package/dist/with-lookup/search/index.d.ts +1 -1
- package/dist/with-party/broker/index.d.ts +1 -0
- package/dist/with-party/broker/seed.d.ts +1 -1
- package/dist/with-party/custody/liberate.d.ts +3 -3
- package/dist/with-party/directory/cover/index.d.ts +1 -5
- package/dist/with-party/directory/cover/schema.d.ts +0 -6
- package/dist/with-party/directory/cover/storage.d.ts +0 -8
- package/dist/with-party/directory/cover/types.d.ts +0 -21
- package/dist/with-party/directory/index.d.ts +2 -0
- package/dist/with-party/session/dev-unlock.d.ts +2 -2
- package/dist/with-party/session/index.d.ts +2 -0
- package/dist/with-party/session/session.d.ts +2 -2
- package/dist/with-party/team/deed.d.ts +8 -8
- package/dist/with-party/team/index.d.ts +4 -7
- package/dist/with-party/team/keyring.d.ts +83 -35
- package/dist/with-party/team/{managed-passphrase.d.ts → managed-secret.d.ts} +27 -27
- package/dist/with-party/team/noydb-facade.d.ts +43 -42
- package/dist/with-party/team/peer-recover.d.ts +13 -13
- package/dist/with-party/team/recovery.d.ts +5 -5
- package/dist/with-party/team/rotate-recover.d.ts +30 -30
- package/dist/with-pod/bundle.d.ts +30 -32
- package/dist/with-pod/format.d.ts +2 -2
- package/dist/with-pod/index.d.ts +2 -1
- package/dist/with-shape/blobs/blob-compaction.d.ts +43 -3
- package/dist/with-shape/blobs/blob-pinning.d.ts +112 -0
- package/dist/with-shape/blobs/blob-set.d.ts +86 -2
- package/dist/with-shape/introspection/index.d.ts +6 -0
- package/dist/with-shape/schema-update/index.d.ts +1 -0
- package/dist/with-store/index.d.ts +3 -2
- package/dist/with-store/route-store.d.ts +1 -1
- package/dist/{with-party/team/sync-active.d.ts → with-sync/active.d.ts} +1 -1
- package/dist/{with-party/team/sync-credentials.d.ts → with-sync/credentials.d.ts} +3 -3
- package/dist/{with-party/team/sync.d.ts → with-sync/engine.d.ts} +5 -5
- package/dist/{with-party/sync → with-sync}/index.d.ts +4 -2
- package/dist/{with-party/team/sync-period-scope.d.ts → with-sync/period-scope.d.ts} +2 -2
- package/dist/{with-party/team → with-sync}/presence.d.ts +2 -2
- package/dist/{with-party/team/sync-strategy.d.ts → with-sync/strategy.d.ts} +6 -6
- package/dist/{with-party/team/sync-transaction.d.ts → with-sync/transaction.d.ts} +3 -3
- package/dist/withdraw-accessible-A2PWF5MZ.js +28 -0
- package/package.json +45 -36
- package/dist/aggregate/index.js.map +0 -1
- package/dist/api-IQFZJSQO.js +0 -20
- package/dist/as/index.js +0 -30
- package/dist/at/index.js +0 -1
- package/dist/bundle/index.js +0 -105
- package/dist/by/index.js +0 -11
- package/dist/chunk-2IALOXZS.js.map +0 -1
- package/dist/chunk-2MUZ3LY6.js +0 -205
- package/dist/chunk-2MUZ3LY6.js.map +0 -1
- package/dist/chunk-2P7EGNKF.js.map +0 -1
- package/dist/chunk-3PF6CRDT.js.map +0 -1
- package/dist/chunk-3SV2EPMN.js.map +0 -1
- package/dist/chunk-4BRCIYFG.js.map +0 -1
- package/dist/chunk-5OI6BR4H.js.map +0 -1
- package/dist/chunk-5WZXVULX.js.map +0 -1
- package/dist/chunk-6NQMXTYC.js.map +0 -1
- package/dist/chunk-6XE5TMPR.js.map +0 -1
- package/dist/chunk-7CI6CP7Z.js.map +0 -1
- package/dist/chunk-7O3AYBC7.js.map +0 -1
- package/dist/chunk-A5BDDEXK.js.map +0 -1
- package/dist/chunk-A7JJLVGH.js.map +0 -1
- package/dist/chunk-BGK233EA.js.map +0 -1
- package/dist/chunk-BMQWTQZ4.js.map +0 -1
- package/dist/chunk-BOKIKJJB.js.map +0 -1
- package/dist/chunk-CCOP5XDW.js.map +0 -1
- package/dist/chunk-CHFZDSE4.js +0 -1
- package/dist/chunk-DDHM2TLX.js.map +0 -1
- package/dist/chunk-GQNGCEP5.js.map +0 -1
- package/dist/chunk-GVZQ4NAM.js +0 -14
- package/dist/chunk-GVZQ4NAM.js.map +0 -1
- package/dist/chunk-GWKAVKHI.js.map +0 -1
- package/dist/chunk-HOONYZY2.js.map +0 -1
- package/dist/chunk-HPUWOVR7.js.map +0 -1
- package/dist/chunk-ISMV2QVG.js.map +0 -1
- package/dist/chunk-JBBSYJHP.js.map +0 -1
- package/dist/chunk-JRBNCUNT.js.map +0 -1
- package/dist/chunk-JZWWLLVY.js +0 -99
- package/dist/chunk-JZWWLLVY.js.map +0 -1
- package/dist/chunk-KUPKLSX5.js.map +0 -1
- package/dist/chunk-KWVJAOIN.js.map +0 -1
- package/dist/chunk-LB6OH6K2.js.map +0 -1
- package/dist/chunk-LB7FD65R.js +0 -163
- package/dist/chunk-LB7FD65R.js.map +0 -1
- package/dist/chunk-LOF2W3JU.js.map +0 -1
- package/dist/chunk-LSASLXGC.js.map +0 -1
- package/dist/chunk-O7WJ47EF.js.map +0 -1
- package/dist/chunk-ONTBCHCP.js.map +0 -1
- package/dist/chunk-PT37L5DV.js +0 -255
- package/dist/chunk-PT37L5DV.js.map +0 -1
- package/dist/chunk-RDXW3OBQ.js.map +0 -1
- package/dist/chunk-RND5ZPNP.js.map +0 -1
- package/dist/chunk-S3BFKGET.js.map +0 -1
- package/dist/chunk-SMGEYW6G.js.map +0 -1
- package/dist/chunk-T36RFODW.js.map +0 -1
- package/dist/chunk-TAGR72IR.js.map +0 -1
- package/dist/chunk-UNCOC7QH.js.map +0 -1
- package/dist/chunk-VCBHAE4R.js.map +0 -1
- package/dist/chunk-VEALDPHW.js.map +0 -1
- package/dist/chunk-VQNJ7UZL.js.map +0 -1
- package/dist/chunk-W2OG6JOL.js.map +0 -1
- package/dist/chunk-WNGGJWF7.js.map +0 -1
- package/dist/chunk-YJV2UZ7H.js.map +0 -1
- package/dist/chunk-YMUGBZN2.js.map +0 -1
- package/dist/chunk-YTGDPCPW.js.map +0 -1
- package/dist/chunk-ZEQWCBCM.js.map +0 -1
- package/dist/collection-facade-MJXSC3Q6.js +0 -44
- package/dist/cover-B5SJDHR3.js +0 -52
- package/dist/delegation-A5A75KQO.js +0 -25
- package/dist/derive-HJSMH7RA.js +0 -21
- package/dist/executor-AXE4XMLT.js +0 -26
- package/dist/executor-KGNLJD6M.js +0 -9
- package/dist/executor-XGIS6CR7.js +0 -9
- package/dist/export-accessible-VVBJM3MM.js +0 -23
- package/dist/extract-partition-LGGBTQET.js +0 -38
- package/dist/find-YS6XWB46.js +0 -11
- package/dist/in/index.js +0 -1
- package/dist/issue-LSR5PS6H.js +0 -19
- package/dist/legacy/bundle.d.ts +0 -30
- package/dist/liberate-GBSAYLPJ.js +0 -24
- package/dist/noydb-MTUBMWFE.js +0 -70
- package/dist/on/index.js +0 -17
- package/dist/port/as/index.d.ts +0 -26
- package/dist/port/at/index.d.ts +0 -18
- package/dist/port/in/index.d.ts +0 -21
- package/dist/port/on/index.d.ts +0 -27
- package/dist/port/ui/index.d.ts +0 -21
- package/dist/port/with/index.d.ts +0 -18
- package/dist/register-X462DNDM.js +0 -23
- package/dist/registry-3XMCT7MQ.js +0 -18
- package/dist/registry-KHD2I3M2.js +0 -19
- package/dist/registry-NPOWJHC4.js +0 -9
- package/dist/request-withdrawal-U7K3JTMY.js +0 -31
- package/dist/revoke-OCWDSWRQ.js +0 -24
- package/dist/seed-PWK45HDF.js.map +0 -1
- package/dist/signer-3YFBLZRN.js +0 -25
- package/dist/storage-W3GURVFU.js +0 -23
- package/dist/tx/index.js.map +0 -1
- package/dist/ui/index.js +0 -1
- package/dist/with/index.js +0 -31
- package/dist/with-lookup/aggregate/index.d.ts +0 -23
- package/dist/withdraw-accessible-Y3B2535F.js +0 -28
- /package/dist/{api-IQFZJSQO.js.map → api-ZDKTWNBB.js.map} +0 -0
- /package/dist/{backup-LU2DTZTI.js.map → backup-2UX7PRM5.js.map} +0 -0
- /package/dist/{chunk-IY23KFNZ.js.map → chunk-35NMVJ7I.js.map} +0 -0
- /package/dist/{chunk-RTEK4C4I.js.map → chunk-4JQK3L4V.js.map} +0 -0
- /package/dist/{chunk-3ELCWU56.js.map → chunk-4ZKNC6A6.js.map} +0 -0
- /package/dist/{chunk-VAIS3SOM.js.map → chunk-55LX6FSX.js.map} +0 -0
- /package/dist/{chunk-6HPG2YNX.js.map → chunk-5DRROK5W.js.map} +0 -0
- /package/dist/{chunk-S6Q37VGE.js.map → chunk-5GQEU4DN.js.map} +0 -0
- /package/dist/{chunk-TOPYT5U5.js.map → chunk-63FE525P.js.map} +0 -0
- /package/dist/{chunk-JVBO4SZL.js.map → chunk-6UBZ6I5Z.js.map} +0 -0
- /package/dist/{chunk-UFEVO57O.js.map → chunk-ABS7FMMD.js.map} +0 -0
- /package/dist/{chunk-M75ZZHSR.js.map → chunk-BLBAQRBM.js.map} +0 -0
- /package/dist/{chunk-G6EQYAL6.js.map → chunk-CI32CAFS.js.map} +0 -0
- /package/dist/{chunk-6B4DYGWO.js.map → chunk-DABACXEF.js.map} +0 -0
- /package/dist/{chunk-F6T6L2UK.js.map → chunk-DMH3VZU5.js.map} +0 -0
- /package/dist/{chunk-MQ6WOPSY.js.map → chunk-DZ2JXHWK.js.map} +0 -0
- /package/dist/{chunk-UBNCR3YK.js.map → chunk-EE7UHLKH.js.map} +0 -0
- /package/dist/{chunk-EGCRBCNA.js.map → chunk-EKRSCRNM.js.map} +0 -0
- /package/dist/{chunk-IRQY7IR5.js.map → chunk-FRMPM7WG.js.map} +0 -0
- /package/dist/{as/index.js.map → chunk-GJW4HX2O.js.map} +0 -0
- /package/dist/{chunk-T6GU5PPZ.js.map → chunk-GU3IP74F.js.map} +0 -0
- /package/dist/{chunk-SMIRGMS7.js.map → chunk-IXNCZTWA.js.map} +0 -0
- /package/dist/{chunk-RM2XV574.js.map → chunk-IXTSNGYY.js.map} +0 -0
- /package/dist/{chunk-PK6QCH2C.js.map → chunk-JJZGIE4U.js.map} +0 -0
- /package/dist/{chunk-DRWBEDYO.js.map → chunk-JYJFGD2H.js.map} +0 -0
- /package/dist/{chunk-KUIB4B5Q.js.map → chunk-LNZKOUJR.js.map} +0 -0
- /package/dist/{chunk-IM3QVF53.js.map → chunk-MBKVNOJS.js.map} +0 -0
- /package/dist/{chunk-N6WF5PKC.js.map → chunk-NFZGZ4AK.js.map} +0 -0
- /package/dist/{chunk-OF75ACKS.js.map → chunk-PEATRIYJ.js.map} +0 -0
- /package/dist/{chunk-N55W7KUL.js.map → chunk-QDIH2744.js.map} +0 -0
- /package/dist/{chunk-6HY2X62D.js.map → chunk-QJNCB5SK.js.map} +0 -0
- /package/dist/{chunk-2PCAVW2I.js.map → chunk-SHEEBRZ4.js.map} +0 -0
- /package/dist/{chunk-RA5VTXXG.js.map → chunk-V6UQZA2H.js.map} +0 -0
- /package/dist/{chunk-QDPZWTN2.js.map → chunk-W3G5REIU.js.map} +0 -0
- /package/dist/{chunk-WEYZTLXN.js.map → chunk-W3L64Y3J.js.map} +0 -0
- /package/dist/{chunk-CBSY2N76.js.map → chunk-WLXMTYGO.js.map} +0 -0
- /package/dist/{chunk-3U3FBR7O.js.map → chunk-X3F5YJG2.js.map} +0 -0
- /package/dist/{classified-marker-WQVIJAW2.js.map → classified-marker-7PKJBFCG.js.map} +0 -0
- /package/dist/{at/index.js.map → collection-facade-6I5K5IOD.js.map} +0 -0
- /package/dist/{bundle/index.js.map → computed-AB45PS4V.js.map} +0 -0
- /package/dist/{by → cover}/index.js.map +0 -0
- /package/dist/{in → custody}/index.js.map +0 -0
- /package/dist/{dead-filter-2A7IAJ3D.js.map → dead-filter-FL3WIUZS.js.map} +0 -0
- /package/dist/{chunk-CHFZDSE4.js.map → derive-VGS7FKE3.js.map} +0 -0
- /package/dist/{on → directory}/index.js.map +0 -0
- /package/dist/{collection-facade-MJXSC3Q6.js.map → enclave-SPEEDYZ4.js.map} +0 -0
- /package/dist/{computed-DSMPXG2Y.js.map → executor-IEBLTBCW.js.map} +0 -0
- /package/dist/{cover-B5SJDHR3.js.map → executor-OTF556PL.js.map} +0 -0
- /package/dist/{delegation-A5A75KQO.js.map → executor-RM7XWQDU.js.map} +0 -0
- /package/dist/{derive-HJSMH7RA.js.map → export-accessible-P42BQ4PB.js.map} +0 -0
- /package/dist/{enclave-7MW6ZGXW.js.map → extract-partition-TCYXRWBN.js.map} +0 -0
- /package/dist/{fanout-sidecar-WDYQ77VB.js.map → fanout-sidecar-SSMRSC2L.js.map} +0 -0
- /package/dist/{executor-AXE4XMLT.js.map → find-OZMTRIK3.js.map} +0 -0
- /package/dist/{ui → introspection}/index.js.map +0 -0
- /package/dist/{executor-KGNLJD6M.js.map → issue-FCKN5O6C.js.map} +0 -0
- /package/dist/{executor-XGIS6CR7.js.map → ledger-6XBEVWYP.js.map} +0 -0
- /package/dist/{export-accessible-VVBJM3MM.js.map → liberate-CYUOS4NC.js.map} +0 -0
- /package/dist/{extract-partition-LGGBTQET.js.map → link-set-NK6L2A5S.js.map} +0 -0
- /package/dist/{with → money}/index.js.map +0 -0
- /package/dist/{find-YS6XWB46.js.map → noydb-FZZIMLCI.js.map} +0 -0
- /package/dist/{issue-LSR5PS6H.js.map → policy/index.js.map} +0 -0
- /package/dist/{post-register-JVTMJ6OW.js.map → post-register-VFIYISUK.js.map} +0 -0
- /package/dist/{ledger-33BC4TR7.js.map → register-UKU2J6MN.js.map} +0 -0
- /package/dist/{liberate-GBSAYLPJ.js.map → registry-2VCWVHZS.js.map} +0 -0
- /package/dist/{link-set-QK22TDEX.js.map → registry-7Y5WF7GS.js.map} +0 -0
- /package/dist/{noydb-MTUBMWFE.js.map → registry-BWD2ED52.js.map} +0 -0
- /package/dist/{policy-NAHB2TUT.js.map → registry-DTWRJLSF.js.map} +0 -0
- /package/dist/{register-X462DNDM.js.map → request-withdrawal-JYDZ5XY2.js.map} +0 -0
- /package/dist/{reveal-LGBOM2BO.js.map → reveal-CL2BCW6A.js.map} +0 -0
- /package/dist/{registry-3XMCT7MQ.js.map → revoke-2TSAD2T4.js.map} +0 -0
- /package/dist/{registry-KHD2I3M2.js.map → schema-update/index.js.map} +0 -0
- /package/dist/{registry-NPOWJHC4.js.map → search/index.js.map} +0 -0
- /package/dist/{registry-VW6P6A3J.js.map → sequence/index.js.map} +0 -0
- /package/dist/{request-withdrawal-U7K3JTMY.js.map → signer-K7Z2EOMG.js.map} +0 -0
- /package/dist/{revoke-OCWDSWRQ.js.map → stale-A4J26OIC.js.map} +0 -0
- /package/dist/{signer-3YFBLZRN.js.map → storage-YVHZUISN.js.map} +0 -0
- /package/dist/{stale-FJED42DM.js.map → store/index.js.map} +0 -0
- /package/dist/{store-coordination-provider-APODTV4W.js.map → store-coordination-provider-EWRZL7XF.js.map} +0 -0
- /package/dist/{verify-26Y5GNMK.js.map → verify-7XWHJWUN.js.map} +0 -0
- /package/dist/{storage-W3GURVFU.js.map → walk-P5D6CF4V.js.map} +0 -0
- /package/dist/with-lookup/{aggregate → reduce}/canonical-key.d.ts +0 -0
- /package/dist/{with-party → with-sync}/tab-coordination.d.ts +0 -0
- /package/dist/{with-party → with-sync}/tab-write-relay.d.ts +0 -0
- /package/dist/{withdraw-accessible-Y3B2535F.js.map → withdraw-accessible-A2PWF5MZ.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/port/with/team-strategy.ts","../src/with-party/team/wrapped-deks.ts","../src/with-party/team/recovery.ts","../src/with-party/team/magic-link-grant.ts","../src/with-party/team/rotate-recover.ts","../src/with-party/team/peer-recover.ts","../src/with-party/auth-introspection/index.ts","../src/with-party/team/authenticators.ts"],"sourcesContent":["/**\n * Multi-user team capability contract (#267 Track A tail — the\n * keyring-grant → team split). Lives on the `/with` port (the one seam the\n * kernel spine may import statically) so `Noydb` can hold the `NO_TEAM`\n * floor default without a spine→service static import.\n *\n * The active engine (`withTeam()` in `with-party/team/active.ts`)\n * statically links the grant/revoke/rotate keyring engines from\n * `keyring.ts` — bundle-charged to consumers of the `@noy-db/hub/team`\n * subpath, never to the single-user floor. {@link NO_TEAM} (the floor\n * default) throws {@link TeamNotEnabledError}.\n *\n * `Noydb.grant` / `Noydb.revoke` / `Noydb.rotate` delegate here, passing\n * their `TeamFacade` — the facade owns the policy-gate + keyring plumbing\n * (`runGrant` / `runRevoke` / `runRotate`) and receives the engine as an\n * argument from the strategy.\n *\n * Deliberately NOT gated (single-user primitives): owner keyring creation,\n * unlock, `listUsers`, `updateUser`, secret rotate/recover, and the\n * `createDeedOwner` free function (no createNoydb instance to gate against —\n * the same carve-out as `liberateVault`).\n * @internal\n */\nimport type { GrantOptions, RevokeOptions, FactorProofBundle } from '../../kernel/types.js'\nimport type { RotateResult } from '../../with-party/team/keyring.js'\nimport { TeamNotEnabledError } from '../../kernel/errors.js'\nimport type { TeamFacade } from '../../with-party/team/noydb-facade.js'\n\nexport interface TeamStrategy {\n grant(team: TeamFacade, vault: string, options: GrantOptions, factors?: FactorProofBundle): Promise<void>\n revoke(team: TeamFacade, vault: string, options: RevokeOptions, factors?: FactorProofBundle): Promise<void>\n rotate(team: TeamFacade, vault: string, collections: string[]): Promise<RotateResult>\n}\n\n/**\n * No-op stub — the floor default. Every multi-user team operation throws\n * {@link TeamNotEnabledError}; opt in with `teamStrategy: withTeam()` in\n * createNoydb. @internal\n */\nexport const NO_TEAM: TeamStrategy = {\n async grant() { throw new TeamNotEnabledError() },\n async revoke() { throw new TeamNotEnabledError() },\n async rotate() { throw new TeamNotEnabledError() },\n}\n","/**\n * **Wrap-DEKs primitive** — a single canonical shape for the\n * pattern of \"serialize a DEK set, encrypt it under a credential-derived\n * AES-GCM key.\" Used by:\n *\n * - **tier-0** — paper recovery entries (`_meta/recovery-paper`),\n * credential = the printed code.\n * - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,\n * `wrapKind: 'deks'`), credential = the user's password.\n *\n * **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under\n * the same conceptual pattern but at **100,000 PBKDF2 iterations**\n * (vs the 600,000 here), because the protection window for a PIN\n * slot is short (idle-timeout-bounded, typically 15 min) and 600k\n * iterations would make every PIN-resume noticeably slow. The wire\n * formats are deliberately incompatible. See `@noy-db/on-pin`'s\n * `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its\n * module docstring.\n *\n * Previously, the same crypto lived in two places: `mintPaperRecoveryEntry`\n * (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in\n * `@noy-db/on-password`). Both functions did identical work — PBKDF2\n * the credential, AES-GCM-encrypt the JSON-serialized DEK set — but\n * their implementations had drifted apart enough that fixing a bug\n * in one wouldn't fix the other.\n *\n * This module owns the canonical implementation. Consumers compose:\n *\n * - `mintPaperRecoveryEntry` is now a thin wrapper that calls\n * `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.\n * - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and\n * wraps the result in the slot envelope.\n *\n * @module\n */\n\nimport { deriveSecretKey, type EnclaveKey } from '../../kernel/enclave/index.js'\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Type ──────────────────────────────────────────────────────────────\n\n/**\n * The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set\n * keyed under a credential-derived key.\n *\n * All three fields are base64-encoded so the blob is JSON-safe and\n * round-trips through `_meta/*` envelopes (which carry plaintext\n * JSON in `_data`).\n *\n * Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus\n * `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`\n * carries the same three fields with `salt` stored in `meta` for\n * slot-format back-compat (defers moving it to top-level).\n */\nexport interface WrappedDeksBlob {\n /** Base64 PBKDF2 salt for the credential-derived wrapping key. */\n readonly salt: string\n /** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */\n readonly iv: string\n /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */\n readonly wrappedDeks: string\n}\n\n// ─── Mint ──────────────────────────────────────────────────────────────\n\n/**\n * Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.\n *\n * Generates a random salt + IV, derives a 256-bit AES-GCM key via\n * PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as\n * `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.\n *\n * The `credential` is the user-typed string (recovery code, password,\n * PIN). Caller normalization rules apply (e.g. paper\n * recovery uppercase-strips the code before reaching this function).\n *\n * @param deks - DEK set to wrap. Each DEK must be exportable via\n * `subtle.exportKey('raw', dek)` (the hub mints DEKs\n * this way; consumers feeding non-extractable keys\n * will get `InvalidAccessError` from WebCrypto).\n * @param credential - String input the consumer minted (paper code,\n * password, PIN). Treated as opaque bytes by PBKDF2.\n */\nexport async function mintWrappedDeksBlob(\n deks: Map<string, EnclaveKey>,\n credential: string,\n): Promise<WrappedDeksBlob> {\n const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES))\n const wrappingKey = await deriveWrappingKey(credential, salt)\n\n // Serialize the DEK set as JSON `{ deks: { collection: base64 } }`.\n const exported: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n const raw = await subtle.exportKey('raw', dek)\n exported[coll] = bytesToBase64(new Uint8Array(raw))\n }\n const plaintext = new TextEncoder().encode(JSON.stringify({ deks: exported }))\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n wrappingKey,\n plaintext as BufferSource,\n )\n\n return {\n salt: bytesToBase64(salt),\n iv: bytesToBase64(iv),\n wrappedDeks: bytesToBase64(new Uint8Array(ciphertext)),\n }\n}\n\n// ─── Unwrap ────────────────────────────────────────────────────────────\n\n/**\n * Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key\n * from the credential + stored salt, AES-GCM-decrypts the wrapped DEK\n * set, and re-imports each DEK as an extractable AES-GCM CryptoKey.\n *\n * Throws (AES-GCM auth tag failure) when the credential doesn't\n * match the blob. Callers iterating over multiple blobs (e.g. paper\n * recovery's \"try every entry until one matches\") should catch.\n */\nexport async function unwrapDeksFromBlob(\n blob: WrappedDeksBlob,\n credential: string,\n): Promise<Map<string, EnclaveKey>> {\n const wrappingKey = await deriveWrappingKey(credential, base64ToBytes(blob.salt))\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: base64ToBytes(blob.iv) as BufferSource },\n wrappingKey,\n base64ToBytes(blob.wrappedDeks) as BufferSource,\n )\n const parsed = JSON.parse(new TextDecoder().decode(plaintext)) as { deks: Record<string, string> }\n const deks = new Map<string, EnclaveKey>()\n for (const [coll, b64] of Object.entries(parsed.deks)) {\n const raw = base64ToBytes(b64)\n const key = await subtle.importKey(\n 'raw',\n raw as BufferSource,\n { name: 'AES-GCM', length: 256 },\n true,\n ['encrypt', 'decrypt'],\n )\n deks.set(coll, key)\n }\n return deks\n}\n\n// ─── Internals ─────────────────────────────────────────────────────────\n\nasync function deriveWrappingKey(credential: string, salt: Uint8Array): Promise<CryptoKey> {\n return deriveSecretKey(credential, salt, { iterations: PBKDF2_ITERATIONS, keyUsage: 'aes-gcm' })\n}\n\nfunction bytesToBase64(b: Uint8Array): string {\n let s = ''\n for (const x of b) s += String.fromCharCode(x)\n return btoa(s)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const s = atob(b64)\n const out = new Uint8Array(s.length)\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i)\n return out\n}\n","/**\n * Recovery profile persistence + dispatch.\n *\n * Wires the **paper** profile end-to-end through\n * `@noy-db/on-recovery`. The other three profiles (Shamir,\n * multi-channel, admin-mediated) ship the API surface and throw\n * {@link RecoveryProfileNotImplementedError} during use; per-profile\n * dispatch lands in follow-up issues.\n *\n * Storage layout:\n *\n * ```\n * _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.\n * _meta/recovery-shamir — reserved\n * _meta/recovery-multi — reserved\n * _meta/recovery-admin — reserved\n * ```\n *\n * Like `_meta/policy` and `_meta/handle`, the documents are plain JSON\n * with empty `_iv` — the recovery-code wrapping is what protects the\n * KEK; the entries themselves are inert without the user's code.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport {\n mintWrappedDeksBlob,\n unwrapDeksFromBlob,\n type WrappedDeksBlob,\n} from './wrapped-deks.js'\nimport type { ShamirRecoveryProvider } from './shamir-recovery-provider.js'\n\n/**\n * One paper recovery code as persisted in `_meta/recovery-paper`.\n *\n * The hub's KEK is intentionally non-extractable (see `crypto.ts`),\n * so the recovery entry can't AES-KW-wrap the KEK directly. Instead\n * we wrap a serialized DEK set: the entry holds the AES-GCM\n * ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery\n * deserializes the DEK set, then mints a fresh KEK from the new\n * secret and rewraps the DEKs under it.\n *\n * This is the same pattern `@noy-db/on-pin` uses for tier-3 quick\n * resume — the cryptographic guarantee is identical (AES-GCM with a\n * PBKDF2-derived key), and it sidesteps the non-extractable-KEK\n * constraint cleanly.\n *\n * Type-level composition: `PaperRecoveryEntry extends\n * WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,\n * `wrappedDeks`) come from the shared primitive; `codeId` and\n * `enrolledAt` are paper-recovery's own metadata. Wire format\n * unchanged.\n */\nexport interface PaperRecoveryEntry extends WrappedDeksBlob {\n readonly codeId: string\n readonly enrolledAt: string\n}\n\nexport interface PaperRecoveryDoc {\n readonly _noydb_recovery: 1\n readonly profile: 'paper'\n readonly entries: ReadonlyArray<PaperRecoveryEntry>\n}\n\nconst PAPER_DOC_ID = 'recovery-paper'\n\n/** Read the paper-recovery entries. Returns empty array when absent. */\nexport async function loadPaperRecoveryEntries(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<PaperRecoveryEntry>> {\n const env = await store.get(vault, '_meta', PAPER_DOC_ID)\n if (!env) return []\n try {\n const doc = JSON.parse(env._data) as PaperRecoveryDoc\n if (doc.profile !== 'paper' || !Array.isArray(doc.entries)) return []\n return doc.entries\n } catch {\n return []\n }\n}\n\n/** Replace the paper-recovery entries (used after burn-on-recovery). */\nexport async function savePaperRecoveryEntries(\n store: NoydbStore,\n vault: string,\n entries: ReadonlyArray<PaperRecoveryEntry>,\n): Promise<void> {\n const doc: PaperRecoveryDoc = {\n _noydb_recovery: 1,\n profile: 'paper',\n entries,\n }\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(doc),\n }\n await store.put(vault, '_meta', PAPER_DOC_ID, envelope)\n}\n\n/** Drop a single paper-recovery entry (burn-on-use). */\nexport async function burnPaperRecoveryEntry(\n store: NoydbStore,\n vault: string,\n codeId: string,\n): Promise<void> {\n const entries = await loadPaperRecoveryEntries(store, vault)\n const remaining = entries.filter((e) => e.codeId !== codeId)\n await savePaperRecoveryEntries(store, vault, remaining)\n}\n\n/** Whether at least one recovery profile has any enrolled entries. */\nexport async function hasRecoveryEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<boolean> {\n const paper = await loadPaperRecoveryEntries(store, vault)\n if (paper.length > 0) return true\n const shamir = await loadShamirRecoveryEntries(store, vault)\n return shamir.length > 0\n}\n\n/**\n * Whether at least one **strong** recovery profile is enrolled.\n *\n * \"Strong\" excludes paper-alone — under managed-secret mode the\n * user has no memorized secret, so a stolen/lost paper sheet\n * would be a single point of total loss. Strong profiles today:\n *\n * - `shamir` (k-of-n threshold; survives loss of up to n-k shares)\n * - `multi-channel` (when shipped — follow-up slice)\n * - `admin-mediated` (when shipped — follow-up slice)\n *\n * Managed mode requires this check to pass before `openVault` returns.\n */\nexport async function hasStrongRecoveryEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<boolean> {\n const shamir = await loadShamirRecoveryEntries(store, vault)\n return shamir.length > 0\n // When multi-channel / admin-mediated land, extend this check.\n}\n\n// ─── Shamir recovery ─────────────────────────────────────────────────────\n\n/**\n * One Shamir-recovery entry as persisted in `_meta/recovery-shamir`.\n *\n * Like {@link PaperRecoveryEntry}, the entry composes\n * {@link WrappedDeksBlob} (DEKs wrapped under a fresh ephemeral\n * recovery secret) with profile-specific metadata. Unlike paper, the\n * \"credential\" was never visible to the user — it was 32 random\n * bytes split into N Shamir shares at enrollment. The shares ARE\n * the credential; the user holds them, the hub never sees them\n * again after `enrollRecovery` returns.\n *\n * The recovery secret is base64-encoded and\n * passed as the `credential` arg to\n * {@link mintWrappedDeksBlob} / {@link unwrapDeksFromBlob}. The\n * PBKDF2 round over high-entropy input is harmless overhead — it\n * keeps the shared primitive unchanged while letting Shamir reuse\n * the same wrapping pipeline as paper.\n */\nexport interface ShamirRecoveryEntry extends WrappedDeksBlob {\n /** Stable id for this entry. Allows multiple Shamir splits to coexist. */\n readonly entryId: string\n /** Threshold — minimum shares to reconstruct. */\n readonly k: number\n /** Total shares minted at enrollment. */\n readonly n: number\n /** x-coordinates of the n minted shares. Informational. Omitted as of 0.2\n * (string-level provider doesn't expose share x-coords); kept optional so\n * pre-0.2 entries still read. */\n readonly xCoords?: ReadonlyArray<number>\n /** ISO timestamp. */\n readonly enrolledAt: string\n /** Optional caller-supplied label (e.g., \"2-of-3 board escrow\"). */\n readonly label?: string\n}\n\nexport interface ShamirRecoveryDoc {\n readonly _noydb_recovery: 1\n readonly profile: 'shamir'\n readonly entries: ReadonlyArray<ShamirRecoveryEntry>\n}\n\nconst SHAMIR_DOC_ID = 'recovery-shamir'\n\n/** Read the Shamir-recovery entries. Returns empty array when absent. */\nexport async function loadShamirRecoveryEntries(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<ShamirRecoveryEntry>> {\n const env = await store.get(vault, '_meta', SHAMIR_DOC_ID)\n if (!env) return []\n try {\n const doc = JSON.parse(env._data) as ShamirRecoveryDoc\n if (doc.profile !== 'shamir' || !Array.isArray(doc.entries)) return []\n return doc.entries\n } catch {\n return []\n }\n}\n\n/** Replace the Shamir-recovery entries (used by enrollment and rotation). */\nexport async function saveShamirRecoveryEntries(\n store: NoydbStore,\n vault: string,\n entries: ReadonlyArray<ShamirRecoveryEntry>,\n): Promise<void> {\n const doc: ShamirRecoveryDoc = {\n _noydb_recovery: 1,\n profile: 'shamir',\n entries,\n }\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(doc),\n }\n await store.put(vault, '_meta', SHAMIR_DOC_ID, envelope)\n}\n\n/**\n * Mint a fresh Shamir recovery entry from a DEK set.\n *\n * 1. Generates a 32-byte recovery secret.\n * 2. Wraps the DEK set under that secret via\n * {@link mintWrappedDeksBlob} (the recovery secret is base64-\n * encoded as the credential string — PBKDF2 over high-entropy\n * input is harmless overhead).\n * 3. Splits the recovery secret via Shamir into `n` shares with\n * threshold `k`.\n * 4. Zeros the in-memory recovery secret after wrapping + splitting.\n *\n * Returns:\n * - `entry` — the {@link ShamirRecoveryEntry} to persist.\n * - `shareStrings` — the `n` Base32-encoded share strings to\n * return to the caller. The HUB MUST NOT PERSIST THESE; once\n * returned they are the user's responsibility.\n *\n * @param deks - DEK set to wrap.\n * @param entryId - Stable id for this entry (caller-supplied or\n * hub-generated).\n * @param k - Threshold (>= 2).\n * @param n - Total shares (k <= n <= 255).\n * @param label - Optional caller label.\n */\nexport async function mintShamirRecoveryEntry(\n provider: ShamirRecoveryProvider,\n deks: Map<string, EnclaveKey>,\n entryId: string,\n k: number,\n n: number,\n label?: string,\n): Promise<{ entry: ShamirRecoveryEntry; shareStrings: string[] }> {\n const recoverySecret = crypto.getRandomValues(new Uint8Array(32))\n try {\n const credential = bytesToBase64(recoverySecret)\n const blob = await mintWrappedDeksBlob(deks, credential)\n const shareStrings = provider.splitToShares(recoverySecret, k, n)\n const entry: ShamirRecoveryEntry = {\n ...blob, entryId, k, n,\n enrolledAt: new Date().toISOString(),\n ...(label !== undefined && { label }),\n }\n return { entry, shareStrings }\n } finally {\n recoverySecret.fill(0)\n }\n}\n\n/**\n * Decrypt a Shamir recovery entry to recover the raw DEK set.\n *\n * Combines K or more `shares`, reconstructs the recovery secret,\n * unwraps the DEKs via {@link unwrapDeksFromBlob}.\n *\n * Throws (AES-GCM auth-tag mismatch) when the shares don't combine\n * to the secret originally used to mint the entry — typically\n * because they came from a different enrollment or were tampered\n * with. Callers iterating multiple entries should catch.\n */\nexport async function unwrapDeksFromShamirEntry(\n provider: ShamirRecoveryProvider,\n entry: ShamirRecoveryEntry,\n shareStrings: readonly string[],\n): Promise<Map<string, EnclaveKey>> {\n if (shareStrings.length < entry.k) {\n throw new Error(\n `Insufficient shares: this Shamir entry needs ${entry.k} of ${entry.n}, `\n + `but ${shareStrings.length} were provided.`,\n )\n }\n const secret = provider.combineShares(shareStrings)\n try {\n return await unwrapDeksFromBlob(entry, bytesToBase64(secret))\n } finally {\n secret.fill(0)\n }\n}\n\nfunction bytesToBase64(b: Uint8Array): string {\n let s = ''\n for (const x of b) s += String.fromCharCode(x)\n return btoa(s)\n}\n\n/**\n * Generate one paper-recovery entry from an unlocked DEK set.\n *\n * Returns the serializable entry (persisted via\n * {@link savePaperRecoveryEntries}). The recovery flow unwraps the\n * DEK set, then mints a fresh KEK from the user's new secret.\n *\n * Thin wrapper over {@link mintWrappedDeksBlob} — the crypto\n * lives in the shared primitive; this function just adds paper-\n * recovery's own metadata (`codeId`, `enrolledAt`).\n *\n * @param deks Map of collection-name → DEK (extractable).\n * @param code The plaintext recovery code (caller-supplied;\n * pair this with `@noy-db/on-recovery`'s code\n * generator/parser if available).\n * @param codeId Stable id used by `burnPaperRecoveryEntry`.\n */\nexport async function mintPaperRecoveryEntry(\n deks: Map<string, EnclaveKey>,\n code: string,\n codeId: string,\n): Promise<PaperRecoveryEntry> {\n const blob = await mintWrappedDeksBlob(deks, code)\n return {\n ...blob,\n codeId,\n enrolledAt: new Date().toISOString(),\n }\n}\n\n/**\n * Decrypt a recovery entry to recover the raw DEK set. Used by the\n * `recoverSecret` flow after the user's code has been parsed.\n *\n * Thin wrapper over {@link unwrapDeksFromBlob}.\n *\n * @throws when the code does not match the entry (AES-GCM auth tag fail).\n */\nexport async function unwrapDeksFromPaperEntry(\n entry: PaperRecoveryEntry,\n code: string,\n): Promise<Map<string, EnclaveKey>> {\n return unwrapDeksFromBlob(entry, code)\n}\n\n// The crypto helpers (deriveRecoveryWrappingKey, bytesToBase64,\n// base64ToBytes) live in the canonical wrap-DEKs primitive at\n// `./wrapped-deks.ts` and are reached via\n// `mintWrappedDeksBlob` / `unwrapDeksFromBlob`.\n","/**\n * Magic-link-bound cross-user delegation grants.\n *\n * This module is the **core storage + encryption layer** that lets a\n * grantor issue a tier-DEK to a user whose KEK they do not know. The\n * trust bridge is provided by the `@noy-db/on-magic-link` package:\n *\n * 1. Grantor picks a grantee identity (user id + email handle).\n * 2. Grantor mints a magic-link token (ULID) via `createMagicLinkToken`.\n * 3. Grantor derives a **content key** + a **KEK** from\n * `(serverSecret, token, vault)` using HKDF-SHA256 with separate\n * `info` tags — both callers (grantor and grantee) can derive the\n * same keys given the same inputs.\n * 4. Grantor persists a record in `_magic_link_grants/<token>`:\n * - envelope `_data` is AES-GCM encrypted under the content key\n * - the inner `wrappedDek` is AES-KW wrapped under the KEK\n * 5. Grantee receives the URL, derives the same content key + KEK,\n * loads the grant, decrypts the envelope, unwraps the tier DEK.\n *\n * ## Why a separate collection from `_delegations`\n *\n * `_delegations` envelopes are encrypted under a DEK shared across\n * every vault user (audit-visibility). External auditors / client\n * portal users have NO pre-existing keyring, so they cannot read that\n * DEK. Magic-link grants live in their own collection whose envelope\n * encryption is derived purely from the magic-link URL + server secret\n * — nothing else is required to decrypt.\n *\n * ## Batch grants\n *\n * One magic-link token may point to MULTIPLE grants (e.g. the client\n * portal case: invoices + payments + etax all share one link). Each\n * grant is persisted under a distinct record id:\n *\n * `<token>` for the single-grant / primary entry\n * `<token>:<index>` for subsequent entries\n *\n * `listMagicLinkGrants(store, vault, token)` enumerates every record\n * whose id begins with `<token>` so the claimant can materialize all\n * DEKs in one pass.\n *\n * ## Revocation\n *\n * `store.delete(vault, _magic_link_grants, <token>)` immediately\n * invalidates the link — even if the URL was captured and the server\n * secret leaked, no payload remains to decrypt.\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { encrypt, openEnvelopeJson, wrapKey, unwrapKey, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { dekKey } from './tiers.js'\nimport { DelegationTargetMissingError } from '../../kernel/errors.js'\n\n/** Reserved collection holding magic-link grant envelopes. */\nexport const MAGIC_LINK_GRANTS_COLLECTION = '_magic_link_grants'\n\n/** HKDF `info` for the AES-GCM content key. Version-namespaced. */\nexport const MAGIC_LINK_CONTENT_INFO_PREFIX = 'noydb-magic-link-content-v1:'\n\n/** HKDF `info` for the AES-KW KEK. Matches `@noy-db/on-magic-link`. */\nexport const MAGIC_LINK_KEK_INFO_PREFIX = 'noydb-magic-link-v1:'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\n/**\n * Decrypted payload of a magic-link grant record. Mirrors\n * `DelegationToken` in `team/delegation.ts` but tracked separately\n * because the two flows persist under different collections + envelope\n * encryption schemes.\n */\nexport interface MagicLinkGrantPayload {\n readonly id: string\n readonly toUser: string\n readonly fromUser: string\n readonly tier: number\n /** Collection name or `null` for the vault-wide tier DEK. */\n readonly collection: string | null\n /** Optional specific record id scope. */\n readonly record?: string\n /** ISO timestamp — grant expires at this instant. */\n readonly until: string\n /** AES-KW-wrapped tier DEK, unwrap with the magic-link KEK. */\n readonly wrappedDek: string\n /** ISO timestamp the grant was issued. */\n readonly createdAt: string\n /** Optional caller-provided label (surfaced in audit UIs). */\n readonly note?: string\n}\n\nexport interface IssueMagicLinkGrantOptions {\n readonly toUser: string\n readonly tier: number\n readonly collection?: string\n readonly record?: string\n readonly until: Date | string\n readonly note?: string\n}\n\nexport interface MagicLinkGrantRecord {\n /** Store record id — `<token>` or `<token>:<index>` for batch entries. */\n readonly recordId: string\n readonly payload: MagicLinkGrantPayload\n}\n\n// ─── Key derivation ─────────────────────────────────────────────────────\n\n/**\n * Derive the AES-GCM content key from the same HKDF inputs used for\n * the magic-link KEK. Different `info` suffix → domain-separated key.\n *\n * Exported so the `@noy-db/on-magic-link` package can share the exact\n * derivation path without cross-dependency between the two modules.\n */\nexport async function deriveMagicLinkContentKey(\n serverSecret: string | Uint8Array<ArrayBuffer>,\n token: string,\n vault: string,\n): Promise<EnclaveKey> {\n const subtle = globalThis.crypto.subtle\n const ikmBytes =\n serverSecret instanceof Uint8Array\n ? serverSecret\n : new TextEncoder().encode(serverSecret)\n const tokenBytes = new TextEncoder().encode(token)\n const saltBuffer = await subtle.digest('SHA-256', tokenBytes)\n const info = new TextEncoder().encode(MAGIC_LINK_CONTENT_INFO_PREFIX + vault)\n const ikm = await subtle.importKey('raw', ikmBytes, 'HKDF', false, ['deriveKey'])\n return subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: saltBuffer, info },\n ikm,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Issue ──────────────────────────────────────────────────────────────\n\n/**\n * Persist a magic-link grant record. Caller derives + provides both\n * the content key and the KEK; this function performs the wrap/encrypt\n * and writes the envelope.\n *\n * `recordId` lets the caller use either the bare token (primary grant)\n * or a suffixed id (batch entry). The writer is responsible for\n * collision-avoidance across batch entries.\n */\nexport async function writeMagicLinkGrant(\n store: NoydbStore,\n vault: string,\n grantor: UnlockedKeyring,\n contentKey: EnclaveKey,\n grantKek: EnclaveKey,\n recordId: string,\n opts: IssueMagicLinkGrantOptions,\n): Promise<MagicLinkGrantRecord> {\n const collectionName = opts.collection ?? null\n const sourceKey = collectionName\n ? dekKey(collectionName, opts.tier)\n : `__any#${opts.tier}`\n const sourceDek = grantor.deks.get(sourceKey)\n if (!sourceDek) {\n throw new DelegationTargetMissingError(\n `grantor cannot find tier ${opts.tier} DEK for ${collectionName ?? '(any)'}`,\n )\n }\n const wrappedDek = await wrapKey(sourceDek, grantKek)\n\n const until = typeof opts.until === 'string' ? opts.until : opts.until.toISOString()\n const createdAt = new Date().toISOString()\n const payload: MagicLinkGrantPayload = {\n id: recordId,\n toUser: opts.toUser,\n fromUser: grantor.userId,\n tier: opts.tier,\n collection: collectionName,\n ...(opts.record && { record: opts.record }),\n until,\n wrappedDek,\n createdAt,\n ...(opts.note && { note: opts.note }),\n }\n\n const { iv, data } = await encrypt(JSON.stringify(payload), contentKey)\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: createdAt,\n _iv: iv,\n _data: data,\n _by: grantor.userId,\n }\n await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId, envelope)\n return { recordId, payload }\n}\n\n// ─── Claim ──────────────────────────────────────────────────────────────\n\n/**\n * Fetch + decrypt a single magic-link grant record by id. Returns null\n * when the record is absent OR when decryption fails (wrong server\n * secret, wrong vault, tampered envelope) — callers treat a null as\n * \"this URL is not valid for this server\".\n *\n * The returned payload's `wrappedDek` is still AES-KW-wrapped; the\n * caller unwraps it with the magic-link KEK to obtain the tier DEK.\n */\nexport async function readMagicLinkGrantRecord(\n store: NoydbStore,\n vault: string,\n contentKey: EnclaveKey,\n recordId: string,\n): Promise<MagicLinkGrantPayload | null> {\n const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId)\n if (!env) return null\n try {\n const json = await openEnvelopeJson(env, contentKey)\n return JSON.parse(json) as MagicLinkGrantPayload\n } catch {\n return null\n }\n}\n\n/**\n * Enumerate every grant record sharing the magic-link `token` prefix\n * (i.e. the primary `<token>` entry plus any `<token>:*` batch entries).\n * Expired grants are still returned — the caller filters on `until`.\n */\nexport async function listMagicLinkGrants(\n store: NoydbStore,\n vault: string,\n contentKey: EnclaveKey,\n token: string,\n): Promise<MagicLinkGrantPayload[]> {\n const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION)\n const matching = ids.filter(id => id === token || id.startsWith(`${token}:`))\n const out: MagicLinkGrantPayload[] = []\n for (const id of matching) {\n const payload = await readMagicLinkGrantRecord(store, vault, contentKey, id)\n if (payload) out.push(payload)\n }\n return out\n}\n\n/**\n * Unwrap the tier DEK from a grant payload using the magic-link KEK.\n * Thin wrapper around `unwrapKey` — provided so the claimant can avoid\n * importing `crypto.js` directly.\n */\nexport async function unwrapMagicLinkGrant(\n payload: MagicLinkGrantPayload,\n grantKek: EnclaveKey,\n): Promise<EnclaveKey> {\n return unwrapKey(payload.wrappedDek, grantKek)\n}\n\n/**\n * Delete a magic-link grant (primary + every batch entry sharing the\n * token). Safe to call when nothing exists.\n */\nexport async function revokeMagicLinkGrant(\n store: NoydbStore,\n vault: string,\n token: string,\n): Promise<number> {\n const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION)\n const matching = ids.filter(id => id === token || id.startsWith(`${token}:`))\n for (const id of matching) {\n await store.delete(vault, MAGIC_LINK_GRANTS_COLLECTION, id)\n }\n return matching.length\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Compose the batch-entry record id. `index === 0` → bare token.\n * Subsequent entries use `<token>:<index>` so `store.list()` can\n * enumerate them all by common prefix.\n */\nexport function magicLinkGrantRecordId(token: string, index: number): string {\n return index === 0 ? token : `${token}:${index}`\n}\n\n/**\n * True when the payload's `until` is in the past relative to `now`.\n * Kept here (rather than inlined) so the semantics stay aligned with\n * the canonical `DelegationToken` expiry check.\n */\nexport function isMagicLinkGrantExpired(\n payload: MagicLinkGrantPayload,\n now: Date = new Date(),\n): boolean {\n return payload.until <= now.toISOString()\n}\n","/**\n * Tier-1 change flows — `rotateSecret` (user remembers old) and\n * `recoverSecret` (user supplies a recovery proof).\n *\n * The two flows share the post-verification half — fresh salt, fresh\n * KEK, rewrap every DEK — and differ only in how they re-derive the\n * old KEK:\n *\n * - **Rotate**: derive from the supplied `oldSecret`.\n * - **Recover (paper)**: unwrap from a `RecoveryCodeEntry` using a\n * user-supplied recovery code. The entry is burned on success.\n *\n * The non-paper recovery profiles (Shamir, multi-channel,\n * admin-mediated) are not yet wired — calling them throws\n * {@link RecoveryProfileNotImplementedError} with a tracking link.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile } from '../../kernel/types.js'\nimport { NOYDB_KEYRING_VERSION } from '../../kernel/types.js'\nimport {\n deriveKey,\n generateSalt,\n wrapKey,\n unwrapKey,\n bufferToBase64,\n base64ToBuffer,\n type EnclaveKey,\n} from '../../kernel/enclave/index.js'\nimport { InvalidKeyError, NoAccessError, RecoveryProfileNotImplementedError } from '../../kernel/errors.js'\nimport {\n loadPaperRecoveryEntries,\n burnPaperRecoveryEntry,\n unwrapDeksFromPaperEntry,\n loadShamirRecoveryEntries,\n unwrapDeksFromShamirEntry,\n type PaperRecoveryEntry,\n type ShamirRecoveryEntry,\n} from './recovery.js'\nimport type { ShamirRecoveryProvider } from './shamir-recovery-provider.js'\nimport { assertStrongSecret, type SecretPolicy } from '../../kernel/validation.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { mintKeyringCanary } from './keyring.js'\nimport type { KeyringAuthenticator } from '../../kernel/types.js'\nimport type { EnrollAuthenticatorOptions } from './authenticators.js'\nimport { ValidationError } from '../../kernel/errors.js'\n\n/**\n * Context handed to a {@link SlotRewrapCeremony} when `rotateSecret`\n * preserves a tier-2 slot. The ceremony's job is to re-derive its\n * method-specific wrapping material (PRF assertion, PBKDF2 of the\n * password, etc.) and wrap the freshly rewrapped DEK set under\n * the new wrapping key.\n *\n * Two surfaces are exposed:\n *\n * - `newDeks` — the rewrapped (extractable) DEK set the slot will\n * wrap. This is what `mintPaperRecoveryEntry` / `enrollPassword-\n * Authenticator` / `wrapKeyringSummary` (in `@noy-db/on-webauthn`)\n * all consume; effectively the canonical input for every\n * post-Path C tier-2 ceremony.\n *\n * - `newKek` — the freshly-derived KEK (extractable for the\n * ceremony scope only). Only relevant for forward-compatibility\n * with a hypothetical future on-* package that wants to wrap the\n * KEK itself under a method-derived key. None of the shipped\n * on-* packages need this; they all operate on `newDeks`.\n *\n * The ceremony MUST preserve `oldSlot.id` and `oldSlot.method` in the\n * returned `EnrollAuthenticatorOptions`. Hub validates these — a\n * mismatch throws `ValidationError` (prevents slot-type swap mid-\n * rotation, e.g. converting a webauthn slot to a password slot under\n * cover of preservation).\n */\nexport interface SlotRewrapContext {\n readonly newKek: EnclaveKey\n readonly newDeks: Map<string, EnclaveKey>\n readonly oldSlot: KeyringAuthenticator\n}\n\n/**\n * Callback that re-enrolls one tier-2 slot during `rotateSecret`.\n * Returns the new slot's `EnrollAuthenticatorOptions` — same shape\n * the consumer would pass to `db.enrollAuthenticator` for a fresh\n * enrollment. Hub persists the result atomically with the rotation.\n */\nexport type SlotRewrapCeremony = (\n ctx: SlotRewrapContext,\n) => Promise<EnrollAuthenticatorOptions>\n\n/** Caller payload for {@link rotateSecret}. */\nexport interface RotateSecretInput {\n readonly oldSecret: string\n readonly newSecret: string\n readonly secretPolicy?: SecretPolicy\n readonly allowWeakSecret?: boolean\n /**\n * Map of slot id → re-enrolment ceremony. Slots whose id appears\n * here are PRESERVED across rotation (the ceremony re-derives the\n * method-specific wrapping under the new keyring); slots whose id\n * is absent are DROPPED (the pre-slot-ceremony behavior).\n *\n * Without this map, `rotateSecret` wipes every tier-2 slot. Consumers building a\n * \"rotate without losing my biometric\" flow supply ceremonies for\n * each slot they want to keep.\n *\n * If a ceremony throws, the entire rotation throws — no partial\n * state. Callers wrap individual ceremonies in try/catch + return\n * a sentinel if they want graceful degradation per slot.\n *\n * Added when slot-ceremony rewrapping landed.\n */\n readonly slotCeremonies?: { readonly [slotId: string]: SlotRewrapCeremony }\n}\n\n/**\n * Re-derive the user's KEK from `oldSecret`, rewrap every DEK\n * under a freshly-derived KEK from `newSecret`, and persist.\n *\n * Tier-2 authenticator slots are dropped UNLESS the caller supplies\n * a `slotCeremonies` map — each ceremony re-derives its\n * method-specific wrapping under the new keyring, and hub persists\n * the rewrapped slots atomically with the rotation. Slots whose id\n * isn't in the map are still dropped.\n *\n * @throws `InvalidKeyError` if `oldSecret` does not unwrap the keyring.\n * @throws `WeakSecretError` if `newSecret` fails the strength rule.\n * @throws `ValidationError` if a ceremony's result mismatches the\n * slot's id or method (anti-slot-swap guard).\n */\nexport async function rotateSecret(\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RotateSecretInput,\n): Promise<UnlockedKeyring> {\n if (!input.allowWeakSecret) {\n assertStrongSecret(input.newSecret, input.secretPolicy)\n }\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n const oldSalt = base64ToBuffer(file.salt)\n const oldKek = await deriveKey(input.oldSecret, oldSalt)\n\n // Unwrap every DEK with the OLD KEK first — this also validates the\n // secret (a bad KEK throws InvalidKeyError on the first unwrap).\n const deks = new Map<string, EnclaveKey>()\n for (const [coll, wrapped] of Object.entries(file.deks)) {\n deks.set(coll, await unwrapKey(wrapped, oldKek))\n }\n\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newSecret, newSalt)\n\n // Rewrap with the new KEK.\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n // Slot rewrap. Without slotCeremonies, we drop every existing\n // slot. With a ceremony map, slots whose id appears in the map\n // are preserved; the rest are dropped.\n const oldSlots = file.authenticators ?? []\n const newSlots: KeyringAuthenticator[] = []\n if (input.slotCeremonies && oldSlots.length > 0) {\n for (const oldSlot of oldSlots) {\n const ceremony = input.slotCeremonies[oldSlot.id]\n if (!ceremony) continue // drop — not in slotCeremonies map\n\n const result = await ceremony({ newKek, newDeks: deks, oldSlot })\n\n // Anti-slot-swap guard. The ceremony MUST preserve identity —\n // a mismatch would let the consumer convert a webauthn slot to\n // a password slot mid-rotation, which would silently change\n // the security profile of the slot under cover of \"rotation.\"\n if (result.id !== oldSlot.id) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned id=\"${result.id}\". ` +\n 'The id must match the rotated slot — a ceremony cannot ' +\n 'change a slot\\'s identity.',\n )\n }\n if (result.method !== oldSlot.method) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned method=\"${result.method}\", ` +\n `expected \"${oldSlot.method}\". The method must match the rotated ` +\n 'slot — a ceremony cannot change the auth method (e.g. webauthn ' +\n '→ password) under cover of rotation.',\n )\n }\n // wrapKind absent on legacy slots / wrap-KEK enroll inputs; treat as 'kek'.\n const oldWrapKind = oldSlot.wrapKind ?? 'kek'\n const newWrapKind = result.wrapKind ?? 'kek'\n if (oldWrapKind !== newWrapKind) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned wrapKind=\"${newWrapKind}\", ` +\n `expected \"${oldWrapKind}\". The wrap format must match the rotated ` +\n 'slot — a ceremony cannot change the wrap shape (e.g. wrap-KEK → ' +\n 'wrap-DEKs) under cover of rotation, since that would silently ' +\n 'change the session tier produced at unlock.',\n )\n }\n\n // Build the persisted slot from the ceremony result. Mirrors\n // the same construction `enrollAuthenticator` does — wrap-DEKs\n // variants carry { wrapped_deks, iv }; wrap-KEK variants\n // carry { wrapped_kek }.\n const baseFields = {\n id: result.id,\n method: result.method,\n // Preserve original enrolled_at — rotation is rewrapping, not\n // re-enrollment. The slot's enrolment timestamp tracks when\n // the user originally added the slot, not when it was last\n // rewrapped. Forensics consumers reading enrolled_at are\n // tracking the slot's ORIGIN, not its CURRENT wrapping.\n enrolled_at: oldSlot.enrolled_at,\n enrolled_via_tier: result.enrolled_via_tier ?? oldSlot.enrolled_via_tier,\n meta: result.meta,\n } as const\n const newSlot: KeyringAuthenticator = result.wrapKind === 'deks'\n ? {\n ...baseFields,\n wrapKind: 'deks',\n wrapped_deks: result.wrapped_deks,\n iv: result.iv,\n }\n : {\n ...baseFields,\n wrapped_kek: result.wrapped_kek,\n }\n newSlots.push(newSlot)\n }\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: newSlots,\n canary,\n }\n\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks,\n kek: newKek,\n salt: newSalt,\n authenticators: newSlots,\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\n/**\n * Caller payload for {@link recoverSecret}.\n *\n * `paper` and `shamir` are wired end-to-end.\n * The remaining two profiles (`multi-channel`, `admin-mediated`)\n * stay outside the union and throw\n * {@link RecoveryProfileNotImplementedError} at the runtime guard\n * when bypassed via `as unknown as RecoveryProof`.\n */\nexport type RecoveryProof =\n | { readonly profile: 'paper'; readonly payload: { readonly code: string } }\n | { readonly profile: 'shamir'; readonly payload: {\n /** Optional disambiguator when multiple Shamir entries are enrolled.\n * When omitted, hub tries each entry until one combines. */\n readonly entryId?: string\n /** K or more opaque share strings, as returned by `ShamirRecoveryProvider.splitToShares`. */\n readonly shares: ReadonlyArray<string>\n } }\n\nexport interface RecoverSecretInput {\n readonly newSecret: string\n readonly recoveryProof: RecoveryProof\n readonly secretPolicy?: SecretPolicy\n readonly allowWeakSecret?: boolean\n /**\n * After a successful paper-recovery, replace ALL remaining recovery\n * entries with freshly-minted ones. Defaults to `true` (defensive).\n *\n * Rationale: the user just demonstrated they had access\n * to AT LEAST one code. The remaining codes from the same printed\n * sheet may also be compromised — photographed, leaked via a\n * screen-share slip, or in the hands of whoever stole the sheet.\n * Auto-rotation closes the window without requiring consumer action.\n *\n * Set to `false` to preserve the original behavior (only the matched\n * code is burned; the rest stay valid).\n *\n * Hub-side orchestration is non-atomic with the recovery itself:\n * if the rotation step fails after a successful burn, the user\n * falls back to the pre-rotation state (remaining codes still\n * valid). Strictly safer than the previous default — a failed\n * rotation degrades gracefully rather than leaving the vault\n * locked or codes dual-existing.\n */\n readonly rotateRemainingCodes?: boolean\n /**\n * Number of fresh codes to mint when `rotateRemainingCodes` is on.\n * Defaults to the count of remaining entries POST-burn (e.g. if\n * the user enrolled 8 originally and just consumed 1, defaults to\n * 7). Pass an explicit number to mint a different count — useful\n * when the consumer wants to refresh to a target N regardless of\n * how many were left.\n */\n readonly newCodeCount?: number\n /**\n * Override the default raw-code generator. The default is hub's\n * {@link generateULID} — uppercase Crockford-Base32, 26 chars,\n * passes through `normalizePaperCode` untouched.\n *\n * Pass `() => generateRawCode()` from `@noy-db/on-recovery` when\n * the consumer prefers the Base32 + checksum format with hyphenated\n * display. The `mintPaperRecoveryEntry` helper accepts any string —\n * the generator just needs to produce a high-entropy unique value.\n */\n readonly codeGenerator?: () => string\n}\n\n/**\n * Return shape of `db.recoverSecret`. `newCodes` is populated when\n * `rotateRemainingCodes` was enabled and at least one entry was\n * rotated; an empty array means no rotation happened (rotation\n * disabled, or no remaining codes after burn). Show the codes to the\n * user once — they are the canonical credential for future recovery\n * and CANNOT be retrieved again.\n */\nexport interface RecoverSecretResult {\n readonly newCodes: readonly string[]\n}\n\n/**\n * Input for {@link Noydb.rotateRecovery} — deliberate\n * recovery-credential regeneration when the user knows their\n * secret but wants a fresh sheet (paper) or fresh shares\n * (shamir). Symmetric to {@link RotateSecretInput}.\n */\nexport type RotateRecoveryOptions =\n | {\n readonly profile: 'paper'\n /** How many fresh codes to mint. Default: existing sheet size. */\n readonly count?: number\n /** Optional code generator — see {@link RecoverSecretInput.codeGenerator}. */\n readonly codeGenerator?: () => string\n }\n | {\n readonly profile: 'shamir'\n /** New threshold. */\n readonly k: number\n /** New total share count. */\n readonly n: number\n /** Disambiguator when multiple Shamir entries exist; required if there are 2+. */\n readonly entryId?: string\n /** Optional updated label. */\n readonly label?: string\n }\n\n/**\n * Result of {@link Noydb.rotateRecovery}. Shape varies by profile:\n *\n * - `paper` → `{ newCodes: string[] }` (and `entryId === 'paper-batch'`)\n * - `shamir` → `{ newShares: string[], entryId }`\n *\n * `newCodes` is populated for paper rotations; `newShares` for\n * Shamir rotations. Both are show-once — the hub does not\n * retain them.\n */\nexport interface RotateRecoveryResult {\n readonly newCodes?: readonly string[]\n readonly newShares?: readonly string[]\n readonly entryId?: string\n}\n\n/**\n * Result of {@link Noydb.enrollRecovery}. Shape varies by profile:\n *\n * - `paper` → `{ entryId: 'paper-batch' }` (caller minted the\n * entries; this is a sentinel since paper enrollments are batch-shaped).\n * - `shamir` → `{ entryId, shares: string[] }` — shares are\n * show-once; the hub does not retain them.\n */\nexport interface EnrollRecoveryResult {\n readonly entryId: string\n readonly shares?: readonly string[]\n}\n\n/**\n * Input shape for {@link Noydb.enrollRecovery} and\n * {@link Noydb.openVaultAndEnrollRecovery}. Discriminated\n * union over recovery profiles.\n *\n * - `paper`: caller pre-mints entries (typically via\n * `mintPaperRecoveryEntry` or `@noy-db/on-recovery`'s\n * `generateRecoveryCodeSet`) and passes them in. The hub stores\n * them and surfaces an opaque batch id.\n * - `shamir`: hub mints the recovery secret + the shares at\n * enrollment time. The shares are returned in\n * {@link EnrollRecoveryResult.shares} (show-once); the hub never\n * retains them.\n *\n * Multi-channel and admin-mediated will be added when the respective\n * dispatch slices ship.\n */\nexport type RecoveryEnrollmentInput =\n | { readonly profile: 'paper'; readonly entries: ReadonlyArray<PaperRecoveryEntry> }\n | {\n readonly profile: 'shamir'\n readonly k: number\n readonly n: number\n readonly label?: string\n readonly entryId?: string\n }\n\n/**\n * Reset the user's secret using a recovery proof.\n * Supports `'paper'` and `'shamir'` profiles. The other profiles throw\n * {@link RecoveryProfileNotImplementedError}.\n *\n * On success, the used recovery entry is burned (deleted from the\n * stored set).\n */\nexport async function recoverSecret(\n provider: ShamirRecoveryProvider | undefined,\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverSecretInput,\n): Promise<UnlockedKeyring> {\n if (!input.allowWeakSecret) {\n assertStrongSecret(input.newSecret, input.secretPolicy)\n }\n\n // Runtime defense-in-depth: the type narrows to 'paper' | 'shamir',\n // but a consumer bypassing TS via\n // `as unknown as RecoveryProof` should still hit a clear error\n // rather than silently fall into a handler with a malformed payload.\n const profile = (input.recoveryProof as { profile: string }).profile\n if (profile === 'paper') {\n return recoverViaPaperCode(store, vault, userId, input)\n }\n if (profile === 'shamir') {\n return recoverViaShamir(provider, store, vault, userId, input)\n }\n throw new RecoveryProfileNotImplementedError(\n profile,\n 'https://github.com/vLannaAi/noy-db/issues/196',\n )\n}\n\nasync function recoverViaPaperCode(\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverSecretInput,\n): Promise<UnlockedKeyring> {\n if (input.recoveryProof.profile !== 'paper') throw new Error('unreachable')\n const { code } = input.recoveryProof.payload\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n\n const entries = await loadPaperRecoveryEntries(store, vault)\n if (entries.length === 0) {\n throw new NoAccessError(\n `No paper-recovery entries enrolled for vault \"${vault}\". ` +\n 'Enroll via `db.team.enrollRecovery({ profile: \"paper\", entries })` before relying on recovery.',\n )\n }\n\n const normalized = normalizePaperCode(code)\n let recovered: { deks: Map<string, EnclaveKey>; entry: PaperRecoveryEntry } | undefined\n for (const entry of entries) {\n try {\n const deks = await unwrapDeksFromPaperEntry(entry, normalized)\n recovered = { deks, entry }\n break\n } catch {\n // wrong code for this entry — try the next one\n }\n }\n if (!recovered) {\n throw new InvalidKeyError(\n 'Recovery code does not match any enrolled paper entry. The code may have been ' +\n 'previously used (single-use) or typed incorrectly.',\n )\n }\n\n const deks = recovered.deks\n\n // Fresh salt + KEK from the new secret, rewrap.\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newSecret, newSalt)\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: [], // tier-2 slots wrap old KEK, drop them\n canary,\n }\n\n // Burn first, then rewrite the keyring. The two writes are not\n // atomic — if the second fails, the safer ordering is:\n //\n // 1. Code burned, keyring untouched: user keeps their old secret\n // and loses one recovery code (recoverable: contact admin / use\n // another code).\n //\n // 2. Keyring rewritten, code unburned: user has rotated, but the\n // consumed code REMAINS VALID. Anyone with access to the paper\n // sheet can use it again. Security regression.\n //\n // Burning first picks (1) over (2).\n await burnPaperRecoveryEntry(store, vault, recovered.entry.codeId)\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks,\n kek: newKek,\n salt: newSalt,\n authenticators: [],\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\n/**\n * Mirror of `@noy-db/on-recovery/parseRecoveryCode`. Inlined so the\n * hub does not gain a peer dep on on-recovery — both implementations\n * follow the same RFC 4648 Base32 + checksum format and round-trip\n * through the same KDF.\n *\n * Accepts hyphenated, lowercase, or whitespace-padded input.\n */\nfunction normalizePaperCode(input: string): string {\n return input.toUpperCase().replace(/[\\s\\-_]/g, '')\n}\n\n/**\n * Recover the user's keyring via the Shamir profile.\n *\n * 1. Decode each supplied share string into a {@link RawShare}.\n * 2. Load `_meta/recovery-shamir` entries.\n * 3. If `payload.entryId` is supplied, restrict to that entry; else\n * iterate over all entries and try each until one combines.\n * 4. For each candidate: filter shares to those whose `(k, n)`\n * match the entry's parameters, then attempt\n * `unwrapDeksFromShamirEntry`. AES-GCM auth-tag failure means\n * the combined secret doesn't match — try the next entry.\n * 5. With unwrapped DEKs: derive fresh KEK from `newSecret` +\n * fresh salt, rewrap, write the keyring.\n * 6. Shamir entries are NOT burned on recovery (shares reusable);\n * explicit {@link Noydb.rotateRecovery} is the refresh ceremony.\n */\nasync function recoverViaShamir(\n provider: ShamirRecoveryProvider | undefined,\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverSecretInput,\n): Promise<UnlockedKeyring> {\n if (input.recoveryProof.profile !== 'shamir') throw new Error('unreachable')\n const { entryId: requestedEntryId, shares: shareStrings } = input.recoveryProof.payload\n\n if (shareStrings.length === 0) {\n throw new ValidationError(\n 'Shamir recovery requires at least one share; received an empty array.',\n )\n }\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n\n const allEntries = await loadShamirRecoveryEntries(store, vault)\n if (allEntries.length === 0) {\n throw new NoAccessError(\n `No Shamir-recovery entries enrolled for vault \"${vault}\". `\n + 'Enroll via `db.team.enrollRecovery({ profile: \"shamir\", k, n })` before relying on recovery.',\n )\n }\n\n if (!provider) {\n throw new Error(\n \"shamir recovery requires a ShamirRecoveryProvider — pass \"\n + \"shamirRecovery: shamirRecoveryProvider() from '@noy-db/on-shamir' to createNoydb()\",\n )\n }\n\n // Restrict to a specific entry when entryId supplied.\n let candidates: ReadonlyArray<ShamirRecoveryEntry>\n if (requestedEntryId !== undefined) {\n candidates = allEntries.filter(e => e.entryId === requestedEntryId)\n if (candidates.length === 0) {\n throw new NoAccessError(\n `No Shamir-recovery entry with entryId=\"${requestedEntryId}\" found `\n + `in vault \"${vault}\". Available entries: `\n + allEntries.map(e => `\"${e.entryId}\"`).join(', '),\n )\n }\n } else {\n candidates = allEntries\n }\n\n // Try each candidate entry. Pass all share strings to the provider;\n // provider.combineShares validates and throws on mismatch — the\n // AES-GCM auth-tag is an additional guard.\n let recoveredDeks: Map<string, EnclaveKey> | undefined\n for (const entry of candidates) {\n if (shareStrings.length < entry.k) {\n // Not enough shares for this entry — could still match another.\n continue\n }\n try {\n const deks = await unwrapDeksFromShamirEntry(provider, entry, shareStrings)\n recoveredDeks = deks\n break\n } catch {\n // provider.combineShares threw (malformed/mismatched shares) or\n // AES-GCM auth-tag failure → try the next entry.\n }\n }\n\n if (!recoveredDeks) {\n // Distinguish \"below-threshold\" from \"no entry matches\" so the\n // error message is actionable.\n const minK = Math.min(...candidates.map(e => e.k))\n if (shareStrings.length < minK) {\n throw new InvalidKeyError(\n `Insufficient Shamir shares to combine: the smallest enrolled threshold is ${minK}, `\n + `but only ${shareStrings.length} share${shareStrings.length === 1 ? ' was' : 's were'} provided.`,\n )\n }\n throw new InvalidKeyError(\n 'Shamir shares do not match any enrolled entry. Possible causes: '\n + 'shares were tampered with, came from a different enrollment, '\n + 'or the entry was rotated after these shares were distributed.',\n )\n }\n\n // Mint fresh KEK from new secret, rewrap DEKs (mirrors paper).\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newSecret, newSalt)\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of recoveredDeks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: [], // tier-2 slots wrap old KEK, drop them on recovery\n canary,\n }\n\n // No burn: Shamir entries persist across recoveries. Explicit\n // rotateRecovery is the refresh ceremony.\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks: recoveredDeks,\n kek: newKek,\n salt: newSalt,\n authenticators: [],\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\nasync function writeKeyringFile(\n store: NoydbStore,\n vault: string,\n userId: string,\n file: KeyringFile,\n): Promise<void> {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(file),\n }\n await store.put(vault, '_keyring', userId, envelope)\n}\n","/**\n * Atomic peer-recovery primitive.\n *\n * `recoverUser` is a SEPARATE operation from `revoke + grant`. It\n * exists because peer-recovery has different semantics than account\n * removal-then-reissue:\n *\n * 1. **Same identity preserved.** `userId`, `role`, `permissions`,\n * capability bits, user envelope (if any), policy override (if\n * any) all survive. Only the wrapping changes.\n * 2. **No key rotation.** The existing DEKs stay valid — every\n * OTHER principal in the vault keeps their access. Rotating\n * keys would invalidate every co-user's wrapping.\n * 3. **Atomic by construction.** A single `store.put` overwrites\n * `_keyring/<userId>` with the recovered file. No revoke step\n * means no partial-failure window.\n * 4. **Owner→owner natively allowed.** Two co-owners recovering\n * each other is the explicitly-intentional case (a partner\n * forgot the master phrase). The existing `canRevoke` rule that\n * blocks owner→owner is correct for `revoke` (which is account\n * *removal*) and intentionally NOT replicated here. The policy\n * gate `peer-recover-user` carries the freshness requirement.\n * 5. **Tier-2 slots dropped.** The slots wrap the OLD KEK under\n * method-derived keys; after recovery the KEK is re-derived\n * from the new temp secret. Match `rotateSecret`'s\n * precedent — the recovered user re-enrols slots after picking\n * their own phrase.\n *\n * Caller must be at least as privileged as the target. The hub\n * `db.recoverUser` method gates this with the `peer-recover-user`\n * policy gate (the `peer-recover-user` factor-proof requirement); the function below\n * enforces only the role + anti-privilege-escalation invariants.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile, Role } from '../../kernel/types.js'\nimport { NOYDB_KEYRING_VERSION } from '../../kernel/types.js'\nimport { deriveKey, generateSalt, wrapKey, bufferToBase64 } from '../../kernel/enclave/index.js'\nimport { NoAccessError, PermissionDeniedError, PrivilegeEscalationError } from '../../kernel/errors.js'\nimport { assertStrongSecret, type SecretPolicy } from '../../kernel/validation.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { mintKeyringCanary } from './keyring.js'\n\n// FR-6: 'custodian' is deliberately ABSENT — an admin cannot peer-recover a\n// custodian (mirrors ADMIN_GRANTABLE_TARGETS: custodians are owner-managed\n// only). The owner CAN recover one (canRecover returns true for owner). As a\n// CALLER, a custodian recovers nobody (it is neither owner nor admin → false).\nconst ADMIN_RECOVERABLE_TARGETS: readonly Role[] = ['operator', 'viewer', 'client', 'admin']\n\n/**\n * Whether `callerRole` may recover `targetRole`.\n *\n * Differs from `canRevoke` (in `keyring.ts`) in one critical place:\n * **owner→owner IS allowed**. Peer recovery is the explicitly\n * intentional case (a co-owner forgot their phrase); the freshness\n * binding lives in the `peer-recover-user` policy gate, not in the\n * permission predicate.\n *\n * Admins can recover everyone they could grant (operator / viewer /\n * client / admin) but NOT owners — that boundary stays as a hard\n * structural rule even under recovery.\n */\nfunction canRecover(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return ADMIN_RECOVERABLE_TARGETS.includes(targetRole)\n return false\n}\n\n/** Input shape for {@link recoverUser}. */\nexport interface RecoverUserOptions {\n /** Target user id whose keyring is being recovered. */\n readonly userId: string\n /**\n * Temporary secret under which the new keyring is wrapped.\n * The recipient should call `db.rotateSecret` immediately on\n * acceptance to choose their own phrase — this temp acts as a\n * single-use bridge in invite / peer-recovery flows.\n */\n readonly secret: string\n /** Override the target's role. Defaults to the existing target's role. */\n readonly role?: Role\n /** Override the target's display name. Defaults to existing. */\n readonly displayName?: string\n /** Validate phrase strength against the configured policy. */\n readonly validateSecret?: boolean\n /**\n * Skip phrase strength validation even when `validateSecret` is\n * set. The escape hatch matches `grant`'s shape — used when the\n * temp phrase is a high-entropy one-shot string that doesn't need\n * to satisfy the human-typeable rules.\n */\n readonly allowWeakSecret?: boolean\n /**\n * Optional explicit phrase policy override (passed through to\n * `assertStrongSecret`). Mirrors how `grant` accepts a custom\n * `SecretPolicy` for app-specific tightening.\n */\n readonly secretPolicy?: SecretPolicy\n}\n\n/**\n * Atomically rewrap the target user's keyring under a fresh temp\n * secret. Single store write; no revoke step; no key rotation.\n *\n * Caller's responsibilities (NOT enforced here):\n * - Run the `peer-recover-user` policy gate first via\n * `Noydb.checkGate` to enforce the freshness factor proof.\n * - Communicate the temp secret to the recipient via a secure\n * channel (URL fragment, in-person, etc.) — the hub does not\n * transport secrets.\n */\nexport async function recoverUser(\n store: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n options: RecoverUserOptions,\n): Promise<void> {\n // 1. Load the target's existing keyring file (plaintext header).\n const env = await store.get(vault, '_keyring', options.userId)\n if (!env) {\n throw new NoAccessError(\n `recoverUser: user \"${options.userId}\" has no keyring in vault \"${vault}\".`,\n )\n }\n const target = JSON.parse(env._data) as KeyringFile\n const targetRole = options.role ?? target.role\n\n // 2. Permission check — caller must be allowed to recover this role.\n // Owner→owner natively allowed; admin→admin allowed; admin→owner blocked.\n if (!canRecover(callerKeyring.role, targetRole)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot recover role \"${targetRole}\"`,\n )\n }\n // Also guard against role-uplift via the override — admin cannot\n // promote a target to owner under cover of recovery.\n if (!canRecover(callerKeyring.role, target.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot recover role \"${target.role}\"`,\n )\n }\n\n // 3. Anti-privilege-escalation. Every collection the target had\n // access to must be in the caller's DEK set — the recoverer\n // cannot give the recovered user access to a collection the\n // recoverer themselves can't read. Mirrors `grant()`'s check.\n for (const coll of Object.keys(target.deks)) {\n if (!callerKeyring.deks.has(coll)) {\n throw new PrivilegeEscalationError(coll)\n }\n }\n\n // 4. Optional phrase strength validation (mirrors `grant` opt-in).\n if (options.validateSecret && !options.allowWeakSecret) {\n assertStrongSecret(options.secret, options.secretPolicy)\n }\n\n // 5. Mint a fresh salt + KEK from the temp secret. The DEKs\n // themselves are unchanged — only the wrapping is replaced.\n const newSalt = generateSalt()\n const newKek = await deriveKey(options.secret, newSalt)\n\n const wrappedDeks: Record<string, string> = {}\n for (const coll of Object.keys(target.deks)) {\n const callerDek = callerKeyring.deks.get(coll)\n if (!callerDek) {\n // Already caught by the anti-privilege-escalation loop above.\n // This branch is defensive belt-and-braces; if it ever fires,\n // the target had a collection the caller's deks Map disagrees\n // with — fail loud rather than silently dropping access.\n throw new PrivilegeEscalationError(coll)\n }\n wrappedDeks[coll] = await wrapKey(callerDek, newKek)\n }\n\n // 6. Build the recovered keyring file. Identity preserved; wrapping\n // refreshed; tier-2 slots dropped (they wrap the OLD KEK and\n // can't survive a tier-1 phrase change — same precedent as\n // rotateSecret). Mint a fresh canary under newKek; the\n // OLD canary on the spread `...target` would fail to verify against\n // the new KEK and trip KeyringCorruptError on next load.\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...target,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n role: targetRole,\n display_name: options.displayName ?? target.display_name,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n granted_by: callerKeyring.userId,\n authenticators: [],\n canary,\n }\n\n // 7. Single atomic write — overwrites the existing envelope.\n // Backend `put` is the canonical write primitive across every\n // `to-*` store; no partial-failure window between revoke + grant.\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(next),\n }\n await store.put(vault, '_keyring', options.userId, envelope)\n}\n","/**\n * Authentication introspection.\n *\n * Three surfaces over the configured tier model and the actual\n * per-user enrollment state:\n *\n * 1. **Vault-wide English summary** — {@link describeAuthConfig}.\n * 2. **Vault-wide Mermaid diagram** — {@link diagramAuthConfig}.\n * 3. **Per-user introspection** — {@link describeUserAuth}, gated by\n * the `view-user-auth` policy gate (off by default).\n *\n * The per-user surface is held to a strict allowlist — fields not on\n * the allowlist are dropped, never rendered. The negative test in\n * `auth-introspection.test.ts` exercises the allowlist by feeding a\n * contrived keyring with fake \"secret\" fields and asserting that none\n * of them appear in the output.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile, KeyringAuthenticator, VaultPolicy, GatePolicy } from '../../kernel/types.js'\nimport { loadVaultPolicy } from '../policy/storage.js'\nimport { loadPaperRecoveryEntries } from '../team/recovery.js'\n\n/** Vault-wide English summary of the configured authentication graph. */\nexport async function describeAuthConfig(\n store: NoydbStore,\n vault: string,\n): Promise<string> {\n const policy = (await loadVaultPolicy(store, vault)) ?? defaultPolicySnapshot()\n const recoveryProfiles = await listRecoveryProfilesEnrolled(store, vault)\n\n const lines: string[] = []\n lines.push(`Vault \"${vault}\" — three-tier authentication`)\n lines.push('')\n lines.push('Tier 1 — Secret (master)')\n lines.push(` Phrase format: ${policy.secret?.minWords ?? 6}+ words, lowercase letters, ≥${policy.secret?.minWordLength ?? 3} chars/word`)\n lines.push(' Strength validator: enforced (override available for tests only)')\n lines.push('')\n lines.push('Tier 2 — Authenticate (routine login)')\n lines.push(' Allowed methods: WebAuthn (passkey), OIDC, Password')\n lines.push(' Slots per user: unlimited')\n lines.push('')\n lines.push('Tier 3 — Unlock (quick resume)')\n lines.push(' Method: PIN (per-app configurable)')\n lines.push('')\n lines.push(`Recovery profiles enrolled: ${recoveryProfiles.length === 0 ? 'none' : recoveryProfiles.join(', ')}`)\n lines.push('Managed-secret mode: off (post-1.0)')\n lines.push('')\n lines.push('Sensitive-action gates:')\n for (const [gate, gp] of Object.entries(policy.gates) as Array<[string, GatePolicy]>) {\n lines.push(` ${gate} — ${describeGatePolicy(gp)}`)\n }\n return lines.join('\\n')\n}\n\n/**\n * Render the vault's auth graph as Mermaid `flowchart TB` source. The\n * caller pipes this through Mermaid (CLI or browser) to get an SVG.\n */\nexport async function diagramAuthConfig(\n store: NoydbStore,\n vault: string,\n): Promise<string> {\n const policy = (await loadVaultPolicy(store, vault)) ?? defaultPolicySnapshot()\n const lines: string[] = []\n lines.push('flowchart TB')\n lines.push(` vault[\"Vault: ${escapeMermaid(vault)}\"]`)\n lines.push(' tier1[\"Tier 1<br/>Secret\"]')\n lines.push(' tier2[\"Tier 2<br/>Multi-slot Authenticate\"]')\n lines.push(' tier3[\"Tier 3<br/>PIN / Quick-resume\"]')\n lines.push(' vault --> tier1')\n lines.push(' tier1 --> tier2')\n lines.push(' tier2 --> tier3')\n for (const [gateName, gp] of Object.entries(policy.gates) as Array<[string, GatePolicy]>) {\n if (gp.enabled === false) continue\n const id = sanitizeId(gateName)\n const label = `${gateName}<br/>tier ≥ ${gp.minTier}`\n lines.push(` ${id}[\"${escapeMermaid(label)}\"]`)\n const tierNode = gp.minTier === 1 ? 'tier1' : gp.minTier === 2 ? 'tier2' : 'tier3'\n lines.push(` ${tierNode} --> ${id}`)\n }\n return lines.join('\\n')\n}\n\n/**\n * Render the per-user enrollment summary. Returns an empty\n * (non-throwing) string when the user has no keyring file — never\n * confirms or denies the existence of the user from the document\n * alone.\n *\n * Sanitization is strict: only the slot list, enrollment dates, and\n * recovery-profile counts are rendered. WebAuthn cred ids, OIDC\n * subject ids, password hashes, recovery codes, TOTP secrets — all\n * dropped at the allowlist boundary, not redacted.\n */\nexport async function describeUserAuth(\n store: NoydbStore,\n vault: string,\n userId: string,\n): Promise<string> {\n const env = await store.get(vault, '_keyring', userId)\n if (!env) return ''\n const file = JSON.parse(env._data) as KeyringFile\n\n const lines: string[] = []\n lines.push(\n `User: ${file.user_id} (joined ${file.created_at.slice(0, 10)}, role: ${file.role})`,\n )\n lines.push('')\n lines.push('Tier 2 enrollments:')\n if (!file.authenticators || file.authenticators.length === 0) {\n lines.push(' (none enrolled)')\n } else {\n for (const slot of file.authenticators) {\n lines.push(` - ${describeSlot(slot)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Bulk variant for owner dashboards. */\nexport async function describeAllUsersAuth(\n store: NoydbStore,\n vault: string,\n): Promise<Array<{ userId: string; description: string }>> {\n const ids = await store.list(vault, '_keyring')\n const results: Array<{ userId: string; description: string }> = []\n for (const userId of ids) {\n const description = await describeUserAuth(store, vault, userId)\n if (description !== '') results.push({ userId, description })\n }\n return results\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\nconst SLOT_FIELD_ALLOWLIST: ReadonlyArray<keyof KeyringAuthenticator> = [\n 'id',\n 'method',\n 'enrolled_at',\n 'enrolled_via_tier',\n] as const\n\nfunction describeSlot(slot: KeyringAuthenticator): string {\n // Project to the allowlist FIRST — never read meta/wrapped_kek into\n // any user-facing string. The allowlist is the only path values\n // take to the renderer; off-allowlist fields are dropped, not redacted.\n const sanitized: Partial<KeyringAuthenticator> = {}\n for (const key of SLOT_FIELD_ALLOWLIST) {\n if (key in slot) {\n // @ts-expect-error narrow assignment from allowlist iteration\n sanitized[key] = slot[key]\n }\n }\n const date = (sanitized.enrolled_at ?? '').slice(0, 10)\n return `${sanitized.method ?? '?'} (id=${sanitized.id ?? '?'}, enrolled ${date}, via tier ${sanitized.enrolled_via_tier ?? '?'})`\n}\n\nfunction describeGatePolicy(gp: GatePolicy): string {\n if (gp.enabled === false) return 'disabled'\n const parts: string[] = []\n parts.push(`tier ${gp.minTier}`)\n if (gp.factors && gp.factors.length > 0) {\n for (const f of gp.factors) {\n parts.push(`+ ${f.count ?? 1}× ${f.anyOf.join('|')}`)\n }\n }\n if (gp.warn?.sharedDevice === 'block') parts.push('block-on-shared-device')\n return parts.join(' ')\n}\n\nfunction defaultPolicySnapshot(): VaultPolicy {\n return {\n secret: { minWords: 6, minWordLength: 3, rejectRepeatedAdjacent: true },\n gates: {},\n }\n}\n\nasync function listRecoveryProfilesEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<string>> {\n const enrolled: string[] = []\n const paper = await loadPaperRecoveryEntries(store, vault)\n if (paper.length > 0) enrolled.push(`paper (${paper.length} codes)`)\n return enrolled\n}\n\nfunction escapeMermaid(s: string): string {\n return s.replace(/\"/g, '\\\\\"').replace(/\\n/g, ' ')\n}\n\nfunction sanitizeId(s: string): string {\n return s.replace(/[^a-zA-Z0-9]/g, '_')\n}\n","/**\n * Tier-2 authenticator slot management.\n *\n * Each slot independently wraps the SAME KEK under a method-specific\n * derived key (LUKS pattern). Enrolling adds a slot; removing drops\n * one. Both are constant-time keyring writes — no DEK re-keying.\n *\n * The crypto for each method lives in its `@noy-db/on-*` package\n * (`on-webauthn`, `on-oidc`, `on-password`); this module accepts the\n * package's `wrapped_kek` ciphertext + `meta` payload and persists it.\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Tier 2 — Authenticate\n *\n * @module\n */\nimport type { NoydbStore, KeyringAuthenticator } from '../../kernel/types.js'\nimport { NoAccessError, ValidationError } from '../../kernel/errors.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { persistKeyring } from './keyring.js'\n\n/** Fields shared across both wrap-KEK and wrap-DEKs enroll inputs. */\ninterface EnrollAuthenticatorBase {\n readonly id: string\n readonly method: KeyringAuthenticator['method']\n /** Method-specific metadata (cred id, salt, …). */\n readonly meta: Record<string, unknown>\n /** Tier the active session held when enrolling. Defaults to 1. */\n readonly enrolled_via_tier?: 1 | 2\n}\n\n/** Wrap-KEK enroll input (WebAuthn, OIDC). */\nexport interface EnrollAuthenticatorWrappingKEKOptions extends EnrollAuthenticatorBase {\n /** Already-wrapped KEK ciphertext (base64) — produced by the on-* package. */\n readonly wrapped_kek: string\n readonly wrapKind?: 'kek'\n}\n\n/** Wrap-DEKs enroll input (password, future on-* using the unified wrap-DEKs primitive). */\nexport interface EnrollAuthenticatorWrappingDEKsOptions extends EnrollAuthenticatorBase {\n readonly wrapKind: 'deks'\n /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */\n readonly wrapped_deks: string\n /** Base64 AES-GCM IV used for the `wrapped_deks` ciphertext. */\n readonly iv: string\n}\n\n/** Discriminated union over the two enroll input shapes. */\nexport type EnrollAuthenticatorOptions =\n | EnrollAuthenticatorWrappingKEKOptions\n | EnrollAuthenticatorWrappingDEKsOptions\n\n/**\n * Append a new authenticator slot to the keyring file. Throws\n * `ValidationError` if a slot with the same id already exists — the\n * caller decides whether to remove + re-enroll.\n *\n * Accepts either wrap-KEK (WebAuthn, OIDC) or wrap-DEKs (password)\n * input. The variant is preserved verbatim into `KeyringAuthenticator`.\n */\nexport async function enrollAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n options: EnrollAuthenticatorOptions,\n): Promise<UnlockedKeyring> {\n const existing = keyring.authenticators.find((a) => a.id === options.id)\n if (existing) {\n throw new ValidationError(\n `enrollAuthenticator: slot id \"${options.id}\" already exists in vault \"${vault}\". ` +\n 'Remove the slot first or pick a unique id.',\n )\n }\n\n const base = {\n id: options.id,\n method: options.method,\n enrolled_at: new Date().toISOString(),\n enrolled_via_tier: options.enrolled_via_tier ?? 1,\n meta: options.meta,\n } as const\n\n const slot: KeyringAuthenticator = options.wrapKind === 'deks'\n ? {\n ...base,\n wrapKind: 'deks',\n wrapped_deks: options.wrapped_deks,\n iv: options.iv,\n }\n : {\n ...base,\n wrapped_kek: options.wrapped_kek,\n }\n\n const next = appendSlot(keyring, slot)\n await persistKeyring(store, vault, next)\n return next\n}\n\n/**\n * Caller payload for {@link updateAuthenticator}. Mutates only\n * `meta` — the slot's id, method, and wrap material are immutable\n * through this primitive, preserving the anti-slot-swap guard.\n *\n * `meta` is **merged** at the top level: keys absent from the patch\n * are preserved, keys present overwrite. To clear a meta key, pass\n * `null` for that key explicitly. (Same top-level merge semantics as\n * `UserApi.updateMe`, non-recursive — meta is a flat label bag.)\n */\nexport interface UpdateAuthenticatorOptions {\n readonly meta?: Record<string, unknown>\n}\n\n/**\n * Mutate a tier-2 authenticator slot's `meta` blob (slot rename,\n * label changes). The slot's `id`, `method`, and wrap material\n * (`wrapped_kek` for wrap-KEK; `wrapped_deks` + `iv` for wrap-DEKs)\n * are immutable through this entry point — the anti-slot-swap guard\n * is structural, not gate-driven, so even if the policy gate is\n * weakened a future caller cannot use this path to swap one slot's\n * crypto for another's.\n *\n * `meta` patch semantics:\n * - Top-level merge — absent keys preserved, present keys overwrite\n * - `null` value — delete that meta key\n * - Non-object values (string, number, boolean, array) — replace verbatim\n *\n * @throws `NoAccessError` when no slot with the given id exists.\n * @throws `ValidationError` when no patch field is provided.\n *\n */\nexport async function updateAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n slotId: string,\n options: UpdateAuthenticatorOptions,\n): Promise<UnlockedKeyring> {\n if (options.meta === undefined) {\n throw new ValidationError(\n `updateAuthenticator: at least one of meta must be provided ` +\n `(slotId: \"${slotId}\").`,\n )\n }\n\n const idx = keyring.authenticators.findIndex((a) => a.id === slotId)\n if (idx === -1) {\n throw new NoAccessError(\n `updateAuthenticator: slot \"${slotId}\" not found in vault \"${vault}\".`,\n )\n }\n const existing = keyring.authenticators[idx]!\n\n // Merge at the top level. Absent keys preserved (non-recursive —\n // meta is a flat label bag in practice, no consumer nests it).\n const mergedMeta: Record<string, unknown> = { ...existing.meta }\n for (const [k, v] of Object.entries(options.meta)) {\n if (v === undefined) continue // skip\n if (v === null) {\n delete mergedMeta[k]\n continue\n }\n mergedMeta[k] = v\n }\n\n // Reconstruct the slot preserving wrapKind discrimination. The\n // immutable fields (id, method, wrapped_kek / wrapped_deks + iv,\n // enrolled_at, enrolled_via_tier) all flow through ...existing.\n const next: KeyringAuthenticator = { ...existing, meta: mergedMeta }\n const nextSlots = [...keyring.authenticators]\n nextSlots[idx] = next\n\n const nextKeyring: UnlockedKeyring = {\n ...keyring,\n authenticators: nextSlots,\n }\n await persistKeyring(store, vault, nextKeyring)\n return nextKeyring\n}\n\n/**\n * Drop a slot by id. No-op if the slot doesn't exist (idempotent —\n * removing a non-existent slot is a recoverable retry, not an error).\n */\nexport async function removeAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n slotId: string,\n): Promise<UnlockedKeyring> {\n const filtered = keyring.authenticators.filter((a) => a.id !== slotId)\n if (filtered.length === keyring.authenticators.length) {\n return keyring // idempotent — nothing to do\n }\n const next: UnlockedKeyring = {\n ...keyring,\n authenticators: filtered,\n }\n await persistKeyring(store, vault, next)\n return next\n}\n\n/**\n * Look up a slot by id. Returns `undefined` when no slot matches.\n * Used by tier-2 unlock dispatchers to fetch the wrapped KEK + meta\n * before invoking the method-specific verifier.\n */\nexport function findAuthenticator(\n keyring: UnlockedKeyring,\n slotId: string,\n): KeyringAuthenticator | undefined {\n return keyring.authenticators.find((a) => a.id === slotId)\n}\n\nfunction appendSlot(\n keyring: UnlockedKeyring,\n slot: KeyringAuthenticator,\n): UnlockedKeyring {\n return {\n ...keyring,\n authenticators: [...keyring.authenticators, slot],\n }\n}\n"],"mappings":"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","names":["bytesToBase64","subtle","deks"]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
decrypt,
|
|
3
3
|
deriveSealedFieldKey,
|
|
4
4
|
deriveSealedFieldKeyFromCek
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-AKKRXT23.js";
|
|
6
6
|
|
|
7
7
|
// src/kernel/enclave/record-keys/sealed-slot.ts
|
|
8
8
|
function parseSealedSlot(blob) {
|
|
@@ -26,4 +26,4 @@ export {
|
|
|
26
26
|
parseSealedSlot,
|
|
27
27
|
dualReadSealedSlot
|
|
28
28
|
};
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-NFZGZ4AK.js.map
|
|
@@ -6,11 +6,28 @@ import {
|
|
|
6
6
|
ConflictError,
|
|
7
7
|
InvariantError,
|
|
8
8
|
ValidationError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QZWTRENR.js";
|
|
10
10
|
|
|
11
11
|
// src/kernel/best-effort-revert.ts
|
|
12
12
|
async function bestEffortRevert(executed, adapter, compensate) {
|
|
13
|
-
|
|
13
|
+
const legs = [...executed].reverse();
|
|
14
|
+
if (adapter.capabilities?.txAtomic === true && typeof adapter.tx === "function") {
|
|
15
|
+
const ops = legs.map(
|
|
16
|
+
(leg) => leg.prior !== null ? { type: "put", vault: leg.vaultName, collection: leg.collectionName, id: leg.id, envelope: leg.prior } : { type: "delete", vault: leg.vaultName, collection: leg.collectionName, id: leg.id }
|
|
17
|
+
);
|
|
18
|
+
try {
|
|
19
|
+
await adapter.tx(ops);
|
|
20
|
+
if (compensate) for (const leg of legs) {
|
|
21
|
+
try {
|
|
22
|
+
await compensate(leg);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const leg of legs) {
|
|
14
31
|
try {
|
|
15
32
|
if (leg.prior !== null) {
|
|
16
33
|
await adapter.put(leg.vaultName, leg.collectionName, leg.id, leg.prior);
|
|
@@ -231,7 +248,7 @@ async function runTransaction(db, fn, options, txInvariants) {
|
|
|
231
248
|
db._clearActiveTxContext(ctx);
|
|
232
249
|
}
|
|
233
250
|
if (ctx._amendment) {
|
|
234
|
-
const { GuardExecutor } = await import("./executor-
|
|
251
|
+
const { GuardExecutor } = await import("./executor-RM7XWQDU.js");
|
|
235
252
|
try {
|
|
236
253
|
for (const [vaultName, v] of ctx._amendmentVaults) {
|
|
237
254
|
const registry = v._getGuardRegistry();
|
|
@@ -368,4 +385,4 @@ export {
|
|
|
368
385
|
runTransaction,
|
|
369
386
|
revertExecuted
|
|
370
387
|
};
|
|
371
|
-
//# sourceMappingURL=chunk-
|
|
388
|
+
//# sourceMappingURL=chunk-NGSJZFGL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/best-effort-revert.ts","../src/with-commit/tx/transaction.ts"],"sourcesContent":["/**\n * Best-effort reverse-revert — the shape shared by two independent rollback\n * paths that used to duplicate it: satellite fan-out\n * (`with-shape/satellites/fanout.ts`'s `revertAndCompensate`) and\n * multi-record transactions (`with-commit/tx/transaction.ts`'s\n * `revertExecuted`). Both revert a list of already-executed writes on\n * failure by walking them in REVERSE order and, per leg, restoring the\n * captured prior envelope straight through the raw adapter — `put(prior)`\n * when one existed, `delete` when the leg was a fresh insert — bypassing\n * the Collection layer so the revert itself doesn't re-fire\n * encryption/ledger/change-event machinery. Each leg's raw-adapter revert\n * is best-effort: a throw is swallowed so a revert-path failure never masks\n * the original error that triggered the rollback, and the loop moves on to\n * the next leg.\n *\n * An optional per-leg `compensate` callback runs immediately after a\n * successful raw-adapter revert (still inside the same try, so a throwing\n * callback is swallowed the same way as a throwing raw revert). What the\n * callback does is entirely caller-defined — satellites' fan-out fires\n * `_compensateRevertedWrite` only for legs whose write actually landed\n * (#596); with-commit's transaction executor invalidates the Collection\n * cache. Neither concern belongs here: this helper only knows the generic\n * reverse/best-effort/raw-revert shape.\n *\n * Lives in `kernel/` — not a `with-*` service — so both consumers (one a\n * gated service, `with-commit`; one an always-on schema feature,\n * `with-shape/satellites`) can import it without creating a\n * `with-* → with-*` edge, which would force either family to always bundle\n * the other and defeat the opt-in tree-shaking both are built around.\n *\n * @internal — implementation-sharing only, not part of the public surface.\n */\nimport type { EncryptedEnvelope, StoreCapabilities, TxOp } from './types.js'\n\n/** One already-executed write a revert pass needs to unwind. */\nexport interface BestEffortRevertLeg {\n readonly vaultName: string\n readonly collectionName: string\n readonly id: string\n /** Envelope captured before the write, or `null` if the id didn't exist yet. */\n readonly prior: EncryptedEnvelope | null\n}\n\n/** The subset of `NoydbStore` a raw-adapter revert needs. */\nexport interface BestEffortRevertAdapter {\n put(vaultName: string, collectionName: string, id: string, envelope: EncryptedEnvelope): Promise<void>\n delete(vaultName: string, collectionName: string, id: string): Promise<void>\n /**\n * Optional storage-layer transaction (#886). When the store declares\n * `txAtomic`, the whole revert is submitted as ONE operation instead of a\n * per-leg loop, so a crash mid-revert can no longer leave the vault\n * half-unwound.\n */\n tx?(ops: readonly TxOp[]): Promise<void>\n readonly capabilities?: StoreCapabilities\n}\n\n/**\n * Revert `executed` in reverse order via the raw adapter, best-effort.\n * `compensate`, when supplied, runs once per leg immediately after that\n * leg's own raw revert succeeds.\n */\nexport async function bestEffortRevert<T extends BestEffortRevertLeg>(\n executed: readonly T[],\n adapter: BestEffortRevertAdapter,\n compensate?: (leg: T) => Promise<void> | void,\n): Promise<void> {\n const legs = [...executed].reverse()\n\n // #886 — atomic revert when the store can do it. The legs already carry RAW\n // prior envelopes (captured pre-write), which is exactly the shape `tx()`\n // wants, so this needs none of the Collection-layer machinery that makes\n // delegating the FORWARD write path a much larger job.\n //\n // Failure stays best-effort: if the store rejects the batch we fall through\n // to the per-leg loop rather than surfacing a revert-path error, because a\n // revert failure must never mask the original error that triggered it.\n if (adapter.capabilities?.txAtomic === true && typeof adapter.tx === 'function') {\n const ops: TxOp[] = legs.map(leg =>\n leg.prior !== null\n ? { type: 'put', vault: leg.vaultName, collection: leg.collectionName, id: leg.id, envelope: leg.prior }\n : { type: 'delete', vault: leg.vaultName, collection: leg.collectionName, id: leg.id },\n )\n try {\n await adapter.tx(ops)\n if (compensate) for (const leg of legs) { try { await compensate(leg) } catch { /* best-effort */ } }\n return\n } catch {\n // fall through to the per-leg path below\n }\n }\n\n for (const leg of legs) {\n try {\n if (leg.prior !== null) {\n await adapter.put(leg.vaultName, leg.collectionName, leg.id, leg.prior)\n } else {\n await adapter.delete(leg.vaultName, leg.collectionName, leg.id)\n }\n if (compensate) await compensate(leg)\n } catch {\n // best-effort — a revert-path failure must not mask the original\n // error that triggered the rollback (matches both callers' prior\n // independent implementations).\n }\n }\n}\n","/**\n * Multi-record atomic transactions.\n *\n * Lets an application stage writes across two or more collections (or\n * vaults) and commit them all-or-nothing.\n *\n * ```ts\n * await db.transaction(async (tx) => {\n * const inv = tx.vault('acme').collection<Invoice>('invoices')\n * const pay = tx.vault('acme').collection<Payment>('payments')\n * await inv.put(invoiceId, { ...invoice, status: 'paid' })\n * await pay.put(paymentId, { invoiceId, amount, paidAt })\n * })\n * // If the body throws before returning: nothing persisted.\n * // If the body returns: all puts committed; any CAS mismatch rolls\n * // the batch back and surfaces as ConflictError.\n * ```\n *\n * ## Atomicity semantics\n *\n * Ops are buffered during the body. On body-return the hub:\n *\n * 1. **Pre-flight** — re-reads every touched envelope and enforces\n * any caller-supplied `expectedVersion`. A mismatch throws\n * `ConflictError` with *no* writes performed.\n * 2. **Execute** — calls `Collection.put()` / `.delete()` for each\n * staged op in declaration order. History snapshots, ledger\n * appends, and change events fire as normal per op.\n * 3. **Unwind on failure** — if step 2 throws mid-batch, each\n * already-committed op is reverted via the raw store (restoring\n * the captured prior envelope, or deleting if none existed). The\n * ledger is NOT rewritten — audit history preserves the partial\n * commit and the revert.\n *\n * **Crash window.** Steps 2–3 are not a storage-layer transaction —\n * if the process dies between two executed ops, the on-disk state is\n * partial. True all-or-nothing atomicity requires a store that\n * implements `NoydbStore.tx()` (DynamoDB `TransactWriteItems`,\n * IndexedDB `readwrite` transaction, …). This executor declares\n * that future integration point via the `tx?()` method + the\n * `StoreCapabilities.txAtomic` bit, but does not yet delegate\n * to it — the cascade into `Fork · Stores` tracks the per-adapter\n * wire-up.\n *\n * ## Not covered\n *\n * - Cross-sync-peer atomicity. Transactions commit against the\n * primary store only; the sync engine pushes on its normal\n * schedule. For cross-peer two-phase commit use `SyncTransaction`\n * via `db.transaction(vaultName)`.\n * - Read-your-writes within the body. `tx.collection().get(id)`\n * returns the most-recently-staged value for that id when one\n * exists; if no staged op has touched the id, it reads the current\n * committed state. Version numbers returned by `get` reflect the\n * pre-transaction state (staged puts have no version yet).\n *\n * @module\n */\n\nimport type { Noydb } from '../../kernel/noydb.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport {\n AmendmentForbiddenError,\n ConflictError,\n InvariantError,\n ValidationError,\n} from '../../kernel/errors.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport type { GuardExecutor as GuardExecutorModule } from '../../with-audit/guards/executor.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\nimport type { TransactionInvariant } from './invariants.js'\nimport type { GuardChange, GuardContext, ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport { bestEffortRevert } from '../../kernel/best-effort-revert.js'\n\n/** One op buffered inside a running `TxContext`. @internal */\nexport interface StagedOp {\n type: 'put' | 'delete'\n vaultName: string\n collectionName: string\n id: string\n record?: unknown\n expectedVersion?: number\n /**\n * Optional human-readable tag forwarded to the resulting ledger\n * entry's `reason` field. Set by callers via\n * `tx.vault(v).collection(c).put(id, record, { reason })`.\n */\n reason?: string\n}\n\n/**\n * One executed op (main staged op or recursive side-effect like a\n * derivation output) paired with the envelope captured before the write.\n * `revertExecuted` walks this array in reverse on rollback.\n * @internal\n */\nexport interface ExecutedOp {\n op: StagedOp\n priorEnvelope: EncryptedEnvelope | null\n}\n\n/**\n * Options accepted by `db.transaction({ amendment, reason }, fn)`.\n * Only the amendment variant uses these — a plain `db.transaction(fn)`\n * never sees this shape.\n */\nexport interface AmendmentTxOptions {\n /** Opt into amendment mode. Required to be `true`. */\n readonly amendment: true\n /** Human-readable rationale recorded in the ledger entry. Required. */\n readonly reason: string\n}\n\n/**\n * Transaction handle passed to the user's body. Use\n * `tx.vault(name).collection<T>(name)` to get a per-collection\n * facade; its `put`/`delete`/`get` calls stage ops against the tx.\n */\nexport class TxContext {\n /** Stable id for this transaction; shared by all writes it performs. */\n readonly txId: string = generateULID()\n /** @internal */\n readonly _ops: StagedOp[] = []\n /**\n * @internal — write log built up in Phase 2. Each entry records the\n * envelope captured BEFORE the write so a mid-batch failure can\n * restore prior state via `revertExecuted`. Side-effect writes (e.g.\n * recursive derivation outputs fired inside `Collection.put`) are\n * appended here in execution order so they roll back alongside the\n * main staged ops.\n */\n readonly _executed: ExecutedOp[] = []\n /** @internal */\n readonly _db: Noydb\n /**\n * @internal — true when this TxContext was opened in amendment\n * mode. Toggles the lazy-`beginAmendment` + role-check path on first\n * `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.\n */\n readonly _amendment: boolean\n /** @internal — vaults that have already had `beginAmendment` called. */\n readonly _amendmentVaults = new Map<string, Vault>()\n\n /** @internal */\n constructor(db: Noydb, amendment = false) {\n this._db = db\n this._amendment = amendment\n }\n\n /** Scope subsequent `collection()` calls to the named vault. */\n vault(name: string): TxVault {\n const v = this._db.vault(name)\n if (this._amendment && !this._amendmentVaults.has(name)) {\n // Role check is per-vault. The task spec (\"only admin or owner\n // can open an amendment\") is implemented lazy-on-first-touch\n // because the role lives on the vault's keyring, and `tx.vault()`\n // is the first place we know which vault we're addressing. The\n // observable effect is identical to an eager check in the single-\n // vault case the tests exercise; multi-vault amendments check\n // each touched vault as they first appear.\n const role = v.role\n // FR-6: custodian is admin-rank for operational mutations, and an\n // amendment is an operational (data-correcting) act — not an ownership\n // meta-capability — so custodian is allowed alongside owner/admin.\n if (role !== 'admin' && role !== 'owner' && role !== 'custodian') {\n throw new AmendmentForbiddenError(v.userId, role)\n }\n // Amendments require an initialised guard registry — they\n // produce a structured invariant + change-set audit. A vault\n // opened without `guardStrategies` (or via the sync fallback\n // path) has a null registry and cannot run an amendment.\n const reg = v._getGuardRegistry()\n if (reg === null) {\n throw new ValidationError(\n `Vault \"${name}\": amendment mode requires at least one ` +\n `guardStrategy registered via createNoydb({ guardStrategies }). ` +\n `Open the vault with guardStrategies before calling ` +\n `db.transaction({ amendment: true }).`,\n )\n }\n reg.beginAmendment()\n this._amendmentVaults.set(name, v)\n }\n return new TxVault(this, v)\n }\n}\n\n/** Per-vault facade inside a running transaction. */\nexport class TxVault {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault) {\n this._ctx = ctx\n this._vault = vault\n }\n\n /** Scope subsequent op calls to the named collection. */\n collection<T>(name: string): TxCollection<T> {\n const c = this._vault.collection<T>(name)\n return new TxCollection<T>(this._ctx, this._vault, c, name)\n }\n}\n\n/** Per-collection facade inside a running transaction. */\nexport class TxCollection<T> {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n /** @internal */\n readonly _coll: Collection<T>\n /** @internal */\n readonly _name: string\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault, coll: Collection<T>, name: string) {\n this._ctx = ctx\n this._vault = vault\n this._coll = coll\n this._name = name\n }\n\n /**\n * Read the current committed value, or the most-recently-staged\n * value from the same transaction if one exists.\n */\n async get(id: string): Promise<T | null> {\n for (let i = this._ctx._ops.length - 1; i >= 0; i--) {\n const op = this._ctx._ops[i]!\n if (\n op.vaultName === this._vault.name &&\n op.collectionName === this._name &&\n op.id === id\n ) {\n if (op.type === 'delete') return null\n return op.record as T\n }\n }\n return this._coll.get(id)\n }\n\n /**\n * Stage a put. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n put(id: string, record: T, options?: { expectedVersion?: number; reason?: string }): void {\n const op: StagedOp = {\n type: 'put',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n record,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n if (options?.reason !== undefined) op.reason = options.reason\n this._ctx._ops.push(op)\n }\n\n /**\n * Stage a delete. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n delete(id: string, options?: { expectedVersion?: number }): void {\n const op: StagedOp = {\n type: 'delete',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n this._ctx._ops.push(op)\n }\n}\n\n/**\n * Commit plan: pre-flight check + execution + revert plan.\n *\n * @internal — driven by `withTransactions()` (via `tx/active.ts`) for\n * user-facing `db.transaction(...)` calls and by the `amendment` path\n * in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop\n * but shares the `_activeTxContext` mechanism (and the `revertExecuted`\n * helper) so nested side-effect derivation writes get registered for\n * revert alongside the bulk-put source ops.\n */\nexport async function runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n txInvariants?: ReadonlyArray<TransactionInvariant>,\n): Promise<T> {\n // ─── Amendment-mode pre-flight ───────────────────────────────\n // `reason` is the only thing we can validate before the body runs;\n // the per-vault role check happens lazily on first `tx.vault(name)`\n // because we don't know which vaults the body will touch ahead of\n // time. Throwing here keeps the failure mode close to the call site\n // so the developer doesn't have to walk an async stack to find the\n // missing-reason mistake.\n if (options?.amendment) {\n if (typeof options.reason !== 'string' || options.reason.trim().length === 0) {\n throw new ValidationError(\n 'db.transaction({ amendment: true }) requires a non-empty `reason` string.',\n )\n }\n }\n\n const ctx = new TxContext(db, options?.amendment === true)\n const bodyResult = await fn(ctx)\n\n if (ctx._ops.length === 0) {\n // Body produced no ops. If amendment mode was active we still\n // need to close any opened windows so a subsequent (unrelated)\n // write doesn't surprise-collect into a stale change-set. Each\n // `beginAmendment` is matched by exactly one `consumeChanges`.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n // Registry is guaranteed non-null here — `tx.vault(name)`\n // threw above if it was null before adding to\n // `_amendmentVaults`.\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n return bodyResult\n }\n\n // Phase 1 — pre-flight: snapshot every touched envelope and enforce\n // any caller-supplied expectedVersion. Same (vault, coll, id) touched\n // more than once in one tx snapshots only the *initial* committed\n // state; the in-order replay in Phase 2 takes care of successor ops.\n const priorEnvelopes = new Map<string, EncryptedEnvelope | null>()\n const store = db._store\n\n // Commit-time changeset invariants need PLAINTEXT prior records\n // for `before`, but `priorEnvelopes` holds ENCRYPTED envelopes. So for\n // ops in a watched scope we additionally decrypt the prior record here,\n // in Phase 1, BEFORE Phase 2 overwrites it. Snapshots only the initial\n // committed state per (vault, coll, id), matching the envelope snapshot.\n const invariants = txInvariants ?? []\n const watchedScopes = new Set(invariants.map(i => i.scope))\n const plainBefore = new Map<string, unknown>()\n\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!priorEnvelopes.has(key)) {\n const env = await store.get(op.vaultName, op.collectionName, op.id)\n priorEnvelopes.set(key, env)\n }\n if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {\n const prior = await db\n .vault(op.vaultName)\n .collection(op.collectionName)\n .get(op.id)\n plainBefore.set(key, prior ?? null)\n }\n if (op.expectedVersion !== undefined) {\n const env = priorEnvelopes.get(key) ?? null\n const actual = env?._v ?? 0\n if (actual !== op.expectedVersion) {\n throw new ConflictError(\n actual,\n `Transaction pre-flight: ${op.vaultName}/${op.collectionName}/${op.id} ` +\n `expected v${op.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n\n // Phase 2 — execute via the Collection layer so history snapshots,\n // ledger entries, and change events fire normally. We capture each\n // successful op so a mid-batch throw can revert in Phase 3.\n //\n // `_activeTxContext` is published on the Noydb instance for the\n // duration of Phase 2 so recursive writes triggered inside\n // `Collection.put` (today: eager derivation outputs) can register\n // their own envelopes onto `ctx._executed` and roll back alongside\n // the main staged ops. The `finally` clears it before the\n // amendment commit phase runs.\n db._setActiveTxContext(ctx)\n try {\n try {\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n const key = keyOf(op)\n const prior = priorEnvelopes.get(key) ?? null\n // Record the revert plan BEFORE the call so a mid-`coll.put` throw\n // (e.g. strict-mode derivation failure firing after `store.put`\n // has already committed the envelope) still has its source write\n // reverted. `revertExecuted` is best-effort: putting prior back is\n // idempotent when the failing op never actually wrote, and\n // `_invalidateCacheEntry` is a no-op when the collection isn't\n // hydrated.\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(op.id, op.record as any, op.reason !== undefined ? { reason: op.reason } : undefined)\n } else {\n await coll.delete(op.id)\n }\n }\n } catch (err) {\n // Phase 3 — best-effort revert. See helper docstring.\n await revertExecuted(ctx._executed, store, db)\n // Drain amendment windows so the next transaction starts clean.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n throw err\n }\n } finally {\n db._clearActiveTxContext(ctx)\n }\n\n // ─── Amendment commit phase (only if amendment === true) ────\n // Body succeeded — now run each touched vault's invariants over the\n // collected change-set, then append a structured ledger entry. If\n // any invariant throws, treat it exactly like a mid-Phase-2 failure:\n // revert every executed op and re-throw the InvariantError.\n if (ctx._amendment) {\n // Lazy-load GuardExecutor at the dispatch site — keeps the floor\n // bundle free of the guards service when amendments aren't used.\n // Mirrors the deferred-load pattern from elsewhere in this module.\n const { GuardExecutor } = (await import('../../with-audit/guards/executor.js')) as {\n GuardExecutor: typeof GuardExecutorModule\n }\n try {\n for (const [vaultName, v] of ctx._amendmentVaults) {\n const registry = v._getGuardRegistry()\n // Registry is guaranteed non-null at this point — the\n // `tx.vault(name)` path that populates `_amendmentVaults`\n // throws if the registry is null. The defensive check here\n // is for TypeScript's narrowing.\n if (registry === null) continue\n const changesByCollection = registry.consumeChanges()\n const meta = registry.consumeMeta()\n if (changesByCollection.size === 0) continue\n\n const readOnlyVault = v._getReadOnlyFacade()\n if (readOnlyVault === null) continue\n\n // Build the invariant ctx once per vault — it's the same shape\n // every guard sees on the normal `check` path, just with a\n // synthetic `existing: null` (invariants get the full change\n // set in their first parameter; `existing` is a per-record\n // concept that doesn't apply here).\n const invariantsPassed: string[] = []\n for (const [collection, changes] of changesByCollection) {\n const guards = registry.guardsFor(collection).filter(g => g.amendment !== undefined)\n for (const guard of guards) {\n await GuardExecutor.runInvariant(guard, changes, {\n existing: null,\n vault: readOnlyVault,\n userId: v.userId,\n role: v.role,\n })\n }\n if (guards.length > 0) invariantsPassed.push(collection)\n }\n\n // Append the audit ledger entry. Silent no-op when the\n // history strategy isn't configured — the records still\n // committed, only the multi-record summary is unavailable.\n const ledger = v._getLedgerOrNull()\n if (ledger) {\n const role = v.role as 'admin' | 'owner'\n const amendment: NonNullable<LedgerEntry['amendment']> = {\n reason: options!.reason,\n role,\n changes: meta,\n invariantsPassed,\n }\n await ledger.append({\n op: 'amendment',\n collection: '',\n id: '',\n version: 0,\n actor: v.userId,\n // No payload to hash — the per-record entries already\n // captured `payloadHash` at their own append time. We use\n // a sha256 of the canonical reason string so the field is\n // populated with something deterministic and non-empty.\n payloadHash: '',\n amendment,\n })\n }\n void vaultName\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n // ─── Commit-time changeset invariant phase ───────────\n // Runs for BOTH ordinary and amendment transactions (placed after the\n // amendment phase so an amendment commit is still subject to these\n // set-level constraints). Assemble the changeset from the executed\n // staged ops, deduped to the LAST write per (vault, coll, id) while\n // preserving write order, then group `GuardChange` by collection\n // (scope) and run each matching invariant. A throw mirrors the\n // amendment-phase failure mode exactly: revert every executed op and\n // re-throw as `InvariantError`.\n if (invariants.length > 0) {\n // Dedup ctx._ops to the last write per key, preserving first-seen\n // (write) order so the changeset is stable and order-meaningful.\n const lastOp = new Map<string, StagedOp>()\n const order: string[] = []\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!lastOp.has(key)) order.push(key)\n lastOp.set(key, op)\n }\n\n // Group {before, after} pairs by collection name (the invariant\n // scope). `before` is the plaintext prior captured in Phase 1 (null\n // for inserts / unwatched — only watched scopes were captured, and\n // every grouped key belongs to a watched scope). `after` is the\n // written record, null for a delete.\n const changesByScope = new Map<string, GuardChange<unknown>[]>()\n // Parallel map: scope → the vault name of its (last-seen) op, used to\n // build the per-invariant read-only ctx (facade + userId + role).\n const scopeVault = new Map<string, string>()\n for (const key of order) {\n const op = lastOp.get(key)!\n if (!watchedScopes.has(op.collectionName)) continue\n const before = plainBefore.get(key) ?? null\n const after = op.type === 'delete' ? null : (op.record ?? null)\n const change = { before, after } as GuardChange<unknown>\n const arr = changesByScope.get(op.collectionName)\n if (arr) arr.push(change)\n else changesByScope.set(op.collectionName, [change])\n\n // Stash the vault name alongside so we can build a per-vault ctx.\n // (All ops in a scope group could span vaults; we resolve the\n // vault per change below via a parallel map keyed the same way.)\n scopeVault.set(op.collectionName, op.vaultName)\n }\n\n try {\n for (const inv of invariants) {\n const changes = changesByScope.get(inv.scope)\n if (changes === undefined || changes.length === 0) continue\n const vaultName = scopeVault.get(inv.scope)!\n const v = db.vault(vaultName)\n // Prefer the real read-only facade so the invariant can read\n // sibling collections; fall back to a minimal read-only stub.\n const facade: ReadOnlyVaultFacade =\n v._getReadOnlyFacade() ?? {\n collection<R = unknown>(name: string) {\n const c = v.collection<R>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n query: () => c.query(),\n }\n },\n }\n const ctxForInv: GuardContext<unknown> = {\n existing: null,\n vault: facade,\n userId: v.userId,\n role: v.role,\n }\n await inv.check(changes, ctxForInv)\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n return bodyResult\n}\n\n/**\n * Phase 3 helper — restore captured prior envelopes via the raw store\n * to avoid re-firing Collection-level side effects (we don't want a\n * cascade of change events undoing themselves). The ledger is left\n * as-is: each committed op appended an entry; the revert is\n * deliberately NOT recorded as a compensating entry because the\n * caller-facing contract is \"atomic or not at all,\" not \"every write\n * visible in the audit trail.\" Auditors who need the intermediate\n * state can still reconstruct it by walking the ledger through the\n * failed-tx timestamp.\n *\n * Delegates the reverse/best-effort/raw-revert shape to the shared\n * `bestEffortRevert` helper (`kernel/best-effort-revert.ts`); the cache\n * invalidation below rides along as that helper's per-leg `compensate`\n * callback, gated on `db` exactly as before.\n *\n * @internal — shared between `runTransaction` and\n * `Collection.putManyAtomic`. Both register source ops + nested\n * derivation side-effect ops onto `_executed`; this helper unwinds the\n * combined list in reverse on rollback.\n */\nexport async function revertExecuted(\n executed: ReadonlyArray<ExecutedOp>,\n store: Noydb['_store'],\n db?: Noydb,\n): Promise<void> {\n const legs = executed.map(({ op, priorEnvelope }) => ({\n vaultName: op.vaultName,\n collectionName: op.collectionName,\n id: op.id,\n prior: priorEnvelope,\n }))\n await bestEffortRevert(\n legs,\n store,\n db\n ? async (leg) => {\n // Sync the Collection-layer cache with what we just wrote at\n // the raw store. Without this, eager-mode `get` would still\n // return the rolled-back record from its in-memory map. The\n // Collection's `_invalidateCacheEntry` is a no-op when the\n // collection hasn't yet been hydrated.\n const coll = db.vault(leg.vaultName).collection(leg.collectionName)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (coll as any)._invalidateCacheEntry(leg.id)\n }\n : undefined,\n )\n}\n\nfunction keyOf(op: StagedOp): string {\n return `${op.vaultName}\\x00${op.collectionName}\\x00${op.id}`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lazy
|
|
3
|
+
} from "./chunk-5D2ALSM5.js";
|
|
4
|
+
|
|
5
|
+
// src/with-formula/derivations/stale.ts
|
|
6
|
+
var loadDerivationExecutor = lazy(() => import("./executor-IEBLTBCW.js"));
|
|
7
|
+
var _staleByRegistry = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
var keyFor = (source, sourceId) => `${source}/${sourceId}`;
|
|
9
|
+
async function markStale(registry, strategy, sourceId) {
|
|
10
|
+
let map = _staleByRegistry.get(registry);
|
|
11
|
+
if (!map) {
|
|
12
|
+
map = /* @__PURE__ */ new Map();
|
|
13
|
+
_staleByRegistry.set(registry, map);
|
|
14
|
+
}
|
|
15
|
+
const k = keyFor(strategy.source, sourceId);
|
|
16
|
+
let set = map.get(k);
|
|
17
|
+
if (!set) {
|
|
18
|
+
set = /* @__PURE__ */ new Set();
|
|
19
|
+
map.set(k, set);
|
|
20
|
+
}
|
|
21
|
+
set.add(strategy);
|
|
22
|
+
}
|
|
23
|
+
async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
24
|
+
const registry = accessor.registry();
|
|
25
|
+
const producers = registry.strategiesProducingOutput(outputCollection);
|
|
26
|
+
if (producers.length === 0) return;
|
|
27
|
+
const map = _staleByRegistry.get(registry);
|
|
28
|
+
if (!map) return;
|
|
29
|
+
let DerivationExecutor = null;
|
|
30
|
+
for (const { spec, strategyHash } of producers) {
|
|
31
|
+
const k = keyFor(spec.source, id);
|
|
32
|
+
const pending = map.get(k);
|
|
33
|
+
if (!pending || !pending.has(spec)) continue;
|
|
34
|
+
const sourceColl = accessor.getCollection(spec.source);
|
|
35
|
+
const source = await sourceColl.get(id);
|
|
36
|
+
if (!source) {
|
|
37
|
+
pending.delete(spec);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const sourceWithId = { ...source, id };
|
|
41
|
+
DerivationExecutor ??= (await loadDerivationExecutor()).DerivationExecutor;
|
|
42
|
+
const ctx = { vault: accessor.getReadOnlyFacade() };
|
|
43
|
+
const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
|
|
44
|
+
for (const key of Object.keys(spec.outputs)) {
|
|
45
|
+
const out = result.outputs[key];
|
|
46
|
+
if (!out) continue;
|
|
47
|
+
if (out.kind === "failed") {
|
|
48
|
+
const err = out.error;
|
|
49
|
+
if (spec.strict) {
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
console.warn(
|
|
53
|
+
`[derivation] lazy output "${key}" for source "${spec.source}" id="${id}" failed:`,
|
|
54
|
+
err
|
|
55
|
+
);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (out.kind === "array") {
|
|
59
|
+
console.warn(
|
|
60
|
+
`[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
|
|
61
|
+
);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const outSpec = spec.outputs[key];
|
|
65
|
+
if (!outSpec) continue;
|
|
66
|
+
const outputColl = accessor.getCollection(outSpec.collection);
|
|
67
|
+
if (out.skipped === true) {
|
|
68
|
+
await outputColl._internalDelete(id, accessor.getActiveTxContext());
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
await outputColl.put(id, out.value);
|
|
72
|
+
}
|
|
73
|
+
pending.delete(spec);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
markStale,
|
|
79
|
+
resolveStaleOnRead
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=chunk-NJ4DYYO5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-formula/derivations/stale.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { DerivationExecutor as DerivationExecutorType } from './executor.js'\nimport type { ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { DerivationRegistry } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleOnRead` only when a stale flag actually fires. Keeps\n// the executor chunk out of the floor bundle.\nimport type { DerivationSpec } from './types.js'\n\nimport { lazy } from '../../kernel/lazy.js'\n\n/** #846c — one memoized binding instead of a cast at the call site. */\nconst loadDerivationExecutor = lazy(() => import('./executor.js'))\n\n/**\n * Accessor shape passed through from the owning Vault. Provides the\n * registry (used to look up strategies and as the WeakMap key) and a\n * resolver from collection name to the live `Collection` instance.\n * Same shape as `Collection.derivationSource` so callers can pass it\n * through directly.\n */\nexport interface DerivationStaleAccessor {\n registry(): DerivationRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n /**\n * Read-only vault facade handed to `derive(source, ctx)` on the lazy\n * resolve-on-read path. Same instance/shape as the eager path uses.\n */\n getReadOnlyFacade(): ReadOnlyVaultFacade\n /**\n * Active multi-record TxContext or `null`. The lazy resolve-on-read\n * path uses this to register tombstone deletes on `_executed` so a\n * later rollback restores the prior emission. Mirrors the eager\n * path's rollback tracking; the lazy `put` was historically\n * unregistered but the tombstone delete (a NEW write path)\n * matches the eager registration for symmetry.\n */\n getActiveTxContext(): TxContext | null\n}\n\n/**\n * In-memory stale map keyed by `DerivationRegistry` instance (stable\n * per vault). Maps `${source}/${sourceId}` → set of pending strategies.\n *\n * Persistence across vault close is NOT implemented in v1 (concern\n * flagged in the dim14 spec). On vault re-open, derived records'\n * `_derivedFrom.strategyHash` will still match the registered\n * strategies' hash, so an unset stale flag is interpreted as \"fresh.\"\n * `vault.deriveAll()` (Task D13) is the explicit recompute escape\n * hatch.\n *\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _staleByRegistry = new WeakMap<DerivationRegistry, Map<string, Set<DerivationSpec<any, any>>>>()\n\nconst keyFor = (source: string, sourceId: string): string => `${source}/${sourceId}`\n\n/** Mark every output of (strategy, sourceId) as stale. */\nexport async function markStale(\n registry: DerivationRegistry,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n strategy: DerivationSpec<any, any>,\n sourceId: string,\n): Promise<void> {\n let map = _staleByRegistry.get(registry)\n if (!map) {\n map = new Map()\n _staleByRegistry.set(registry, map)\n }\n const k = keyFor(strategy.source, sourceId)\n let set = map.get(k)\n if (!set) {\n set = new Set()\n map.set(k, set)\n }\n set.add(strategy)\n}\n\n/**\n * Called from `Collection.get` on lazy-mode output collections. If the\n * id has a pending stale flag for any strategy producing this output\n * collection, re-derive before returning the record. No-op when there\n * is no pending work — keeps the read fast path negligible.\n */\nexport async function resolveStaleOnRead(\n accessor: DerivationStaleAccessor,\n outputCollection: string,\n id: string,\n): Promise<void> {\n const registry = accessor.registry()\n const producers = registry.strategiesProducingOutput(outputCollection)\n if (producers.length === 0) return\n\n const map = _staleByRegistry.get(registry)\n if (!map) return\n\n // Dynamic-import the executor only when at least one stale flag\n // actually fires. Vaults with no derivation strategies never call\n // this function (gated on `derivationSource` in `Collection.get`);\n // vaults with strategies but no pending stale ids reach the\n // `pending.has(spec)` short-circuit below without ever touching\n // the executor chunk.\n let DerivationExecutor: typeof DerivationExecutorType | null = null\n\n for (const { spec, strategyHash } of producers) {\n const k = keyFor(spec.source, id)\n const pending = map.get(k)\n if (!pending || !pending.has(spec)) continue\n\n // Read the source record from the source collection and re-derive.\n // We use the same getCollection accessor that eager dispatch uses\n // — it returns the live `Collection<any>` instance with full\n // crypto / keyring wiring.\n const sourceColl = accessor.getCollection(spec.source)\n const source = await sourceColl.get(id)\n if (!source) {\n pending.delete(spec)\n continue\n }\n const sourceWithId = { ...(source as Record<string, unknown>), id } as Record<string, unknown> & { id: string }\n // sourceVersion: not tracked in v1 stale map; pass 0 — matches the\n // forthcoming v0 semantics, `_derivedFrom.sourceVersion` is\n // informational, not load-bearing for correctness.\n DerivationExecutor ??= (await loadDerivationExecutor()).DerivationExecutor\n const ctx = { vault: accessor.getReadOnlyFacade() }\n const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) {\n // Leave the stale flag set so a future read retries.\n throw err\n }\n console.warn(\n `[derivation] lazy output \"${key}\" for source \"${spec.source}\" id=\"${id}\" failed:`,\n err,\n )\n continue\n }\n if (out.kind === 'array') {\n // Defensive — array-shape requires `lifecycle: 'eager'`\n // (validated at withDerivation registration).\n // Reaching the lazy-resolve path for array-shape would mean\n // a registration check was bypassed. Log and skip.\n console.warn(\n `[derivation] unexpected array-shape output \"${key}\" in lazy resolve path; `\n + 'array-shape derivations require lifecycle: \"eager\".',\n )\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputColl = accessor.getCollection(outSpec.collection)\n if (out.skipped === true) {\n // Optional output skipped on lazy resolve — delete any\n // prior emission so the read returns null (matches eager-path\n // tombstone semantics). Routed through `_internalDelete` so a\n // user-registered `onDelete` on the output collection\n // does NOT fire. The active TxContext (if any) is forwarded:\n // `resolveStaleOnRead` is reachable from `Collection.get()`\n // which can be called from inside a transaction, so the\n // tombstone must be observable to `revertExecuted` on\n // rollback.\n await outputColl._internalDelete(id, accessor.getActiveTxContext())\n continue\n }\n await outputColl.put(id, out.value)\n }\n pending.delete(spec)\n }\n}\n"],"mappings":";;;;;AAaA,IAAM,yBAAyB,KAAK,MAAM,OAAO,wBAAe,CAAC;AA2CjE,IAAM,mBAAmB,oBAAI,QAAwE;AAErG,IAAM,SAAS,CAAC,QAAgB,aAA6B,GAAG,MAAM,IAAI,QAAQ;AAGlF,eAAsB,UACpB,UAEA,UACA,UACe;AACf,MAAI,MAAM,iBAAiB,IAAI,QAAQ;AACvC,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,qBAAiB,IAAI,UAAU,GAAG;AAAA,EACpC;AACA,QAAM,IAAI,OAAO,SAAS,QAAQ,QAAQ;AAC1C,MAAI,MAAM,IAAI,IAAI,CAAC;AACnB,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,QAAI,IAAI,GAAG,GAAG;AAAA,EAChB;AACA,MAAI,IAAI,QAAQ;AAClB;AAQA,eAAsB,mBACpB,UACA,kBACA,IACe;AACf,QAAM,WAAW,SAAS,SAAS;AACnC,QAAM,YAAY,SAAS,0BAA0B,gBAAgB;AACrE,MAAI,UAAU,WAAW,EAAG;AAE5B,QAAM,MAAM,iBAAiB,IAAI,QAAQ;AACzC,MAAI,CAAC,IAAK;AAQV,MAAI,qBAA2D;AAE/D,aAAW,EAAE,MAAM,aAAa,KAAK,WAAW;AAC9C,UAAM,IAAI,OAAO,KAAK,QAAQ,EAAE;AAChC,UAAM,UAAU,IAAI,IAAI,CAAC;AACzB,QAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,IAAI,EAAG;AAMpC,UAAM,aAAa,SAAS,cAAc,KAAK,MAAM;AACrD,UAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AACtC,QAAI,CAAC,QAAQ;AACX,cAAQ,OAAO,IAAI;AACnB;AAAA,IACF;AACA,UAAM,eAAe,EAAE,GAAI,QAAoC,GAAG;AAIlE,4BAAwB,MAAM,uBAAuB,GAAG;AACxD,UAAM,MAAM,EAAE,OAAO,SAAS,kBAAkB,EAAE;AAClD,UAAM,SAAS,MAAM,mBAAmB,IAAI,MAAM,cAAc,GAAG,cAAc,GAAG;AACpF,eAAW,OAAO,OAAO,KAAK,KAAK,OAAO,GAAG;AAC3C,YAAM,MAAM,OAAO,QAAQ,GAAG;AAC9B,UAAI,CAAC,IAAK;AACV,UAAI,IAAI,SAAS,UAAU;AACzB,cAAM,MAAM,IAAI;AAChB,YAAI,KAAK,QAAQ;AAEf,gBAAM;AAAA,QACR;AACA,gBAAQ;AAAA,UACN,6BAA6B,GAAG,iBAAiB,KAAK,MAAM,SAAS,EAAE;AAAA,UACvE;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,IAAI,SAAS,SAAS;AAKxB,gBAAQ;AAAA,UACN,+CAA+C,GAAG;AAAA,QAEpD;AACA;AAAA,MACF;AACA,YAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,UAAI,CAAC,QAAS;AACd,YAAM,aAAa,SAAS,cAAc,QAAQ,UAAU;AAC5D,UAAI,IAAI,YAAY,MAAM;AAUxB,cAAM,WAAW,gBAAgB,IAAI,SAAS,mBAAmB,CAAC;AAClE;AAAA,MACF;AACA,YAAM,WAAW,IAAI,IAAI,IAAI,KAAK;AAAA,IACpC;AACA,YAAQ,OAAO,IAAI;AAAA,EACrB;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ValidationError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QZWTRENR.js";
|
|
4
4
|
|
|
5
5
|
// src/with-audit/periods/window.ts
|
|
6
6
|
var PERIODS_COLLECTION = "_periods";
|
|
@@ -21,4 +21,4 @@ export {
|
|
|
21
21
|
PERIOD_TARGET_PURGES_COLLECTION,
|
|
22
22
|
periodExclusiveUpperBound
|
|
23
23
|
};
|
|
24
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-NM5UYF6Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-audit/periods/window.ts"],"sourcesContent":["/**\n * Pure period-window primitives — the reserved collection names and the\n * exclusive-upper-bound computation shared by freeze (#604), archive (#613),\n * target-purge (#615) and the period-scoped sync pull (#807).\n *\n * Deliberately dependency-light (kernel errors only): the sync engine\n * (`with-sync/period-scope.ts`) imports THIS module, not\n * `periods.ts`, so a period-scoped pull never drags the ledger hash-chain\n * machinery (`sha256Hex`/`canonicalJson`) into a bundle that opted out of\n * the periods service.\n */\nimport { ValidationError } from '../../kernel/errors.js'\n\n/** The reserved collection name holding closed-period metadata. */\nexport const PERIODS_COLLECTION = '_periods'\n\n/** Sibling of {@link PERIODS_COLLECTION} holding freeze companions (#604). */\nexport const PERIOD_FREEZES_COLLECTION = '_period_freezes'\n\n/** Sibling of {@link PERIODS_COLLECTION} holding archive companions (#613). */\nexport const PERIOD_ARCHIVES_COLLECTION = '_period_archives'\n\n/** Sibling of {@link PERIODS_COLLECTION} holding target-purge companions (#615). */\nexport const PERIOD_TARGET_PURGES_COLLECTION = '_period_target_purges'\n\n/**\n * Exclusive upper bound for a period's delete-marker purge window (#604).\n * Markers carry no business date (empty body), only write-time `_ts`, so freeze\n * purges markers with `_ts < bound`, `bound` being the instant just after the\n * period's inclusive `endDate`: a date-only `endDate` seals through end-of-day\n * → next midnight; a full-timestamp `endDate` seals through that instant → +1ms.\n */\nexport function periodExclusiveUpperBound(endDate: string): string {\n const ms = Date.parse(endDate)\n if (Number.isNaN(ms)) throw new ValidationError(`freezePeriod: unparseable period endDate \"${endDate}\".`)\n const dateOnly = /^\\d{4}-\\d{2}-\\d{2}$/.test(endDate)\n return new Date(ms + (dateOnly ? 86_400_000 : 1)).toISOString()\n}\n"],"mappings":";;;;;AAcO,IAAM,qBAAqB;AAG3B,IAAM,4BAA4B;AAGlC,IAAM,6BAA6B;AAGnC,IAAM,kCAAkC;AASxC,SAAS,0BAA0B,SAAyB;AACjE,QAAM,KAAK,KAAK,MAAM,OAAO;AAC7B,MAAI,OAAO,MAAM,EAAE,EAAG,OAAM,IAAI,gBAAgB,6CAA6C,OAAO,IAAI;AACxG,QAAM,WAAW,sBAAsB,KAAK,OAAO;AACnD,SAAO,IAAI,KAAK,MAAM,WAAW,QAAa,EAAE,EAAE,YAAY;AAChE;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ValidationError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QZWTRENR.js";
|
|
4
4
|
|
|
5
5
|
// src/with-formula/derivations/with-derivation.ts
|
|
6
6
|
function withDerivation(spec) {
|
|
@@ -87,8 +87,8 @@ function withDerivation(spec) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// src/with-formula/derivations/with-rollup.ts
|
|
90
|
-
function withRollup(
|
|
91
|
-
const { from, key, into, field, compute } =
|
|
90
|
+
function withRollup(opts) {
|
|
91
|
+
const { from, key, into, field, compute } = opts;
|
|
92
92
|
if (!from || from.length === 0) {
|
|
93
93
|
throw new ValidationError("withRollup: `from` (child collection) is required");
|
|
94
94
|
}
|
|
@@ -126,4 +126,4 @@ export {
|
|
|
126
126
|
withDerivation,
|
|
127
127
|
withRollup
|
|
128
128
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-NYEFP7DY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-formula/derivations/with-derivation.ts","../src/with-formula/derivations/with-rollup.ts"],"sourcesContent":["import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/**\n * Register a deterministic derivation: one source collection → one or\n * more typed outputs, computed by the user's `derive` function on\n * plaintext after DEK unwrap. Outputs are encrypted with the same DEK\n * as the source and written via the standard `Collection.put` path.\n *\n * See docs/superpowers/specs/2026-05-01-dim14-derivation-v1-design.md.\n */\nexport function withDerivation<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown> | ReadonlyArray<Record<string, unknown>>>,\n>(spec: DerivationSpec<TSource, TOutputs>): DerivationStrategy {\n if (!spec.source || spec.source.length === 0) {\n throw new ValidationError('withDerivation: source collection name is required')\n }\n if (!spec.outputs || Object.keys(spec.outputs).length === 0) {\n throw new ValidationError('withDerivation: outputs map must declare at least one output')\n }\n if (spec.deterministic !== true) {\n throw new ValidationError('withDerivation: v1 only supports deterministic derivations')\n }\n if (typeof spec.derive !== 'function') {\n throw new ValidationError('withDerivation: derive must be a function')\n }\n\n // Validate declared sibling sources. Each must be a non-empty\n // string and must differ from the primary source — a self-reference\n // would double-register the strategy under the same `_bySource` key.\n if (spec.sources !== undefined) {\n for (const extra of spec.sources) {\n if (typeof extra !== 'string' || extra.length === 0) {\n throw new ValidationError('withDerivation: each entry in sources[] must be a non-empty string')\n }\n if (extra === spec.source) {\n throw new ValidationError(\n `withDerivation: sources[] must not contain the primary source \"${spec.source}\"`,\n )\n }\n }\n }\n\n // Validate FK triggers. Each `collection` must be a non-empty\n // string differing from the primary source, and `on` a non-empty field.\n if (spec.triggerBy !== undefined) {\n for (const t of spec.triggerBy) {\n if (typeof t?.collection !== 'string' || t.collection.length === 0) {\n throw new ValidationError('withDerivation: each triggerBy entry needs a non-empty `collection`')\n }\n if (t.collection === spec.source) {\n throw new ValidationError(\n `withDerivation: triggerBy.collection must not equal the source \"${spec.source}\" (use sources[] for same-id triggers)`,\n )\n }\n if (typeof t.on !== 'string' || t.on.length === 0) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs a non-empty \\`on\\` (the FK field on the source)`,\n )\n }\n if (t.maxFanout !== undefined && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {\n throw new ValidationError(\n `withDerivation: triggerBy maxFanout on \"${t.collection}\" must be a positive integer (got ${String(t.maxFanout)}).`,\n )\n }\n }\n }\n\n // Validate array-shape outputs.\n const lifecycleMode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {\n // Self-write output (collection === source): reverse-denorm must declare\n // `denorm` (the fields it owns) — field-level provenance.\n if (outputSpec.shape === 'record' && outputSpec.collection === spec.source) {\n if (!outputSpec.denorm || outputSpec.denorm.length === 0) {\n throw new ValidationError(\n `withDerivation: self-write output \"${outputKey}\" (collection === source \"${spec.source}\") `\n + 'must declare `denorm: [...]` naming the fields it maintains.',\n )\n }\n }\n if (outputSpec.shape === 'array') {\n if (lifecycleMode !== 'eager') {\n throw new ValidationError(\n `withDerivation: shape 'array' supports lifecycle 'eager' only in this release `\n + `Output \"${outputKey}\" declared lifecycle '${lifecycleMode}'. `\n + 'Switch to `lifecycle: \"eager\"` or use shape: \"record\".',\n )\n }\n if (typeof outputSpec.key !== 'function') {\n throw new ValidationError(\n `withDerivation: shape 'array' output \"${outputKey}\" requires \\`key: (out) => string\\`.`,\n )\n }\n if (outputSpec.maxFanout !== undefined) {\n if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {\n throw new ValidationError(\n `withDerivation: maxFanout for output \"${outputKey}\" must be a positive integer `\n + `(got ${String(outputSpec.maxFanout)}).`,\n )\n }\n }\n }\n }\n\n return {\n __noydb_strategy: 'derivation',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: spec as DerivationSpec<any, any>,\n }\n}\n","import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/** Options for {@link withRollup} (#844b — was an inline literal, so unnameable). */\nexport interface WithRollupOptions<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Child collection — the trigger. */\n readonly from: string\n /** FK field on the child pointing at the parent id. */\n readonly key: keyof TChild & string\n /** Parent collection carrying the maintained summary. */\n readonly into: string\n /** Field on the parent to maintain. */\n readonly field: keyof TParent & string\n /** Reduces the matching children to the value written at `field`. */\n readonly compute: (children: TChild[]) => unknown\n}\n\n/**\n * `withRollup` — aggregate many child records onto a single field of their\n * parent. The reverse of a join: instead of reading children\n * on demand, the parent carries a maintained summary.\n *\n * ```ts\n * withRollup<Sale, Buyer>({\n * from: 'sales', // child collection (the trigger)\n * key: 'buyerId', // FK on the child → parent id\n * into: 'buyers', // parent collection\n * field: 'revenueByYear', // field on the parent to maintain\n * compute: (sales) => groupSumByYear(sales, 'total'),\n * })\n * ```\n *\n * On every write OR delete of a `from` record, the parent at id `child[key]`\n * is recomputed: `compute(allChildren where child[key] === parentId)` is\n * patched onto `parent[field]`. A parent write also recomputes its own\n * aggregate (so a parent created after its children still fills in). Only the\n * `field` is touched — the rest of the parent record is never clobbered — and\n * a value-equality guard suppresses no-op writes. The aggregate is gap-free\n * with respect to child inserts, updates, and deletes.\n *\n * Desugars to a `withDerivation` strategy carrying a `rollup` marker; dispatch\n * handles it without invoking the executor. Eager-only in this slice.\n */\nexport function withRollup<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n>(opts: WithRollupOptions<TChild, TParent>): DerivationStrategy {\n const { from, key, into, field, compute } = opts\n if (!from || from.length === 0) {\n throw new ValidationError('withRollup: `from` (child collection) is required')\n }\n if (!into || into.length === 0) {\n throw new ValidationError('withRollup: `into` (parent collection) is required')\n }\n if (from === into) {\n throw new ValidationError('withRollup: `from` and `into` must be different collections')\n }\n if (!key || key.length === 0) {\n throw new ValidationError('withRollup: `key` (FK field on the child) is required')\n }\n if (!field || field.length === 0) {\n throw new ValidationError('withRollup: `field` (target field on the parent) is required')\n }\n if (typeof compute !== 'function') {\n throw new ValidationError('withRollup: `compute` must be a function')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const spec: DerivationSpec<any, any> = {\n source: into, // the parent record is what carries the rolled-up field\n deterministic: true,\n rollup: { from, key, field, compute: compute as (children: unknown[]) => unknown },\n // Synthetic self-write output for registry / cycle bookkeeping. Dispatch\n // patches `field` directly (value-equality guarded); the executor is not run.\n outputs: { value: { shape: 'record', collection: into, denorm: [field] } },\n derive: () => ({ value: {} }), // never invoked for a rollup strategy\n lifecycle: 'eager',\n }\n\n return { __noydb_strategy: 'derivation', spec }\n}\n"],"mappings":";;;;;AAWO,SAAS,eAGd,MAA6D;AAC7D,MAAI,CAAC,KAAK,UAAU,KAAK,OAAO,WAAW,GAAG;AAC5C,UAAM,IAAI,gBAAgB,oDAAoD;AAAA,EAChF;AACA,MAAI,CAAC,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,EAAE,WAAW,GAAG;AAC3D,UAAM,IAAI,gBAAgB,8DAA8D;AAAA,EAC1F;AACA,MAAI,KAAK,kBAAkB,MAAM;AAC/B,UAAM,IAAI,gBAAgB,4DAA4D;AAAA,EACxF;AACA,MAAI,OAAO,KAAK,WAAW,YAAY;AACrC,UAAM,IAAI,gBAAgB,2CAA2C;AAAA,EACvE;AAKA,MAAI,KAAK,YAAY,QAAW;AAC9B,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,cAAM,IAAI,gBAAgB,oEAAoE;AAAA,MAChG;AACA,UAAI,UAAU,KAAK,QAAQ;AACzB,cAAM,IAAI;AAAA,UACR,kEAAkE,KAAK,MAAM;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,MAAI,KAAK,cAAc,QAAW;AAChC,eAAW,KAAK,KAAK,WAAW;AAC9B,UAAI,OAAO,GAAG,eAAe,YAAY,EAAE,WAAW,WAAW,GAAG;AAClE,cAAM,IAAI,gBAAgB,qEAAqE;AAAA,MACjG;AACA,UAAI,EAAE,eAAe,KAAK,QAAQ;AAChC,cAAM,IAAI;AAAA,UACR,mEAAmE,KAAK,MAAM;AAAA,QAChF;AAAA,MACF;AACA,UAAI,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,WAAW,GAAG;AACjD,cAAM,IAAI;AAAA,UACR,iCAAiC,EAAE,UAAU;AAAA,QAC/C;AAAA,MACF;AACA,UAAI,EAAE,cAAc,WAAc,CAAC,OAAO,UAAU,EAAE,SAAS,KAAK,EAAE,YAAY,IAAI;AACpF,cAAM,IAAI;AAAA,UACR,2CAA2C,EAAE,UAAU,qCAAqC,OAAO,EAAE,SAAS,CAAC;AAAA,QACjH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY,KAAK,UAAU;AAC3F,aAAW,CAAC,WAAW,UAAU,KAAK,OAAO,QAAQ,KAAK,OAAO,GAAG;AAGlE,QAAI,WAAW,UAAU,YAAY,WAAW,eAAe,KAAK,QAAQ;AAC1E,UAAI,CAAC,WAAW,UAAU,WAAW,OAAO,WAAW,GAAG;AACxD,cAAM,IAAI;AAAA,UACR,sCAAsC,SAAS,6BAA6B,KAAK,MAAM;AAAA,QAEzF;AAAA,MACF;AAAA,IACF;AACA,QAAI,WAAW,UAAU,SAAS;AAChC,UAAI,kBAAkB,SAAS;AAC7B,cAAM,IAAI;AAAA,UACR,yFACa,SAAS,yBAAyB,aAAa;AAAA,QAE9D;AAAA,MACF;AACA,UAAI,OAAO,WAAW,QAAQ,YAAY;AACxC,cAAM,IAAI;AAAA,UACR,yCAAyC,SAAS;AAAA,QACpD;AAAA,MACF;AACA,UAAI,WAAW,cAAc,QAAW;AACtC,YAAI,CAAC,OAAO,UAAU,WAAW,SAAS,KAAK,WAAW,YAAY,GAAG;AACvE,gBAAM,IAAI;AAAA,YACR,yCAAyC,SAAS,qCACxC,OAAO,WAAW,SAAS,CAAC;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,kBAAkB;AAAA;AAAA,IAElB;AAAA,EACF;AACF;;;ACjEO,SAAS,WAGd,MAA8D;AAC9D,QAAM,EAAE,MAAM,KAAK,MAAM,OAAO,QAAQ,IAAI;AAC5C,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,UAAM,IAAI,gBAAgB,mDAAmD;AAAA,EAC/E;AACA,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,UAAM,IAAI,gBAAgB,oDAAoD;AAAA,EAChF;AACA,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,gBAAgB,6DAA6D;AAAA,EACzF;AACA,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG;AAC5B,UAAM,IAAI,gBAAgB,uDAAuD;AAAA,EACnF;AACA,MAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,UAAM,IAAI,gBAAgB,8DAA8D;AAAA,EAC1F;AACA,MAAI,OAAO,YAAY,YAAY;AACjC,UAAM,IAAI,gBAAgB,0CAA0C;AAAA,EACtE;AAGA,QAAM,OAAiC;AAAA,IACrC,QAAQ;AAAA;AAAA,IACR,eAAe;AAAA,IACf,QAAQ,EAAE,MAAM,KAAK,OAAO,QAAqD;AAAA;AAAA;AAAA,IAGjF,SAAS,EAAE,OAAO,EAAE,OAAO,UAAU,YAAY,MAAM,QAAQ,CAAC,KAAK,EAAE,EAAE;AAAA,IACzE,QAAQ,OAAO,EAAE,OAAO,CAAC,EAAE;AAAA;AAAA,IAC3B,WAAW;AAAA,EACb;AAEA,SAAO,EAAE,kBAAkB,cAAc,KAAK;AAChD;","names":[]}
|