@nicia-ai/typegraph 0.45.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/drizzle/indexes.cjs +11 -11
- package/dist/backend/drizzle/indexes.d.cts +1 -1
- package/dist/backend/drizzle/indexes.d.ts +1 -1
- package/dist/backend/drizzle/indexes.js +7 -7
- package/dist/backend/index.cjs +53 -54
- package/dist/backend/index.d.cts +3 -3
- package/dist/backend/index.d.ts +3 -3
- package/dist/backend/index.js +13 -14
- package/dist/backend/postgres/index.cjs +29 -30
- package/dist/backend/postgres/index.d.cts +7 -7
- package/dist/backend/postgres/index.d.ts +7 -7
- package/dist/backend/postgres/index.js +16 -17
- package/dist/backend/postgres/pglite-store.cjs +48 -42
- package/dist/backend/postgres/pglite-store.cjs.map +1 -1
- package/dist/backend/postgres/pglite-store.d.cts +15 -9
- package/dist/backend/postgres/pglite-store.d.ts +15 -9
- package/dist/backend/postgres/pglite-store.js +44 -38
- package/dist/backend/postgres/pglite-store.js.map +1 -1
- package/dist/backend/postgres/pglite.cjs +18 -19
- package/dist/backend/postgres/pglite.d.cts +4 -4
- package/dist/backend/postgres/pglite.d.ts +4 -4
- package/dist/backend/postgres/pglite.js +17 -18
- package/dist/backend/sqlite/index.cjs +26 -27
- package/dist/backend/sqlite/index.d.cts +6 -6
- package/dist/backend/sqlite/index.d.ts +6 -6
- package/dist/backend/sqlite/index.js +14 -15
- package/dist/backend/sqlite/libsql.cjs +21 -22
- package/dist/backend/sqlite/libsql.cjs.map +1 -1
- package/dist/backend/sqlite/libsql.d.cts +15 -8
- package/dist/backend/sqlite/libsql.d.ts +15 -8
- package/dist/backend/sqlite/libsql.js +17 -18
- package/dist/backend/sqlite/libsql.js.map +1 -1
- package/dist/backend/sqlite/local-store.cjs +49 -43
- package/dist/backend/sqlite/local-store.cjs.map +1 -1
- package/dist/backend/sqlite/local-store.d.cts +15 -10
- package/dist/backend/sqlite/local-store.d.ts +15 -10
- package/dist/backend/sqlite/local-store.js +45 -39
- package/dist/backend/sqlite/local-store.js.map +1 -1
- package/dist/backend/sqlite/local.cjs +19 -20
- package/dist/backend/sqlite/local.d.cts +3 -3
- package/dist/backend/sqlite/local.d.ts +3 -3
- package/dist/backend/sqlite/local.js +17 -18
- package/dist/{chunk-S3ZRKJHY.js → chunk-2LG2XB2M.js} +3 -3
- package/dist/{chunk-S3ZRKJHY.js.map → chunk-2LG2XB2M.js.map} +1 -1
- package/dist/{chunk-7PF2L3H6.cjs → chunk-2NA5DYLO.cjs} +4 -4
- package/dist/{chunk-7PF2L3H6.cjs.map → chunk-2NA5DYLO.cjs.map} +1 -1
- package/dist/chunk-2Z2Z3ZD6.js +58 -0
- package/dist/chunk-2Z2Z3ZD6.js.map +1 -0
- package/dist/{chunk-VVQM4CZD.cjs → chunk-3DHZCOT7.cjs} +20 -20
- package/dist/{chunk-VVQM4CZD.cjs.map → chunk-3DHZCOT7.cjs.map} +1 -1
- package/dist/{chunk-JXUTCSXX.cjs → chunk-4UGOKMI5.cjs} +250 -158
- package/dist/chunk-4UGOKMI5.cjs.map +1 -0
- package/dist/{chunk-RFRM3Q5K.js → chunk-5262ZMTR.js} +14 -5
- package/dist/chunk-5262ZMTR.js.map +1 -0
- package/dist/{chunk-QRNYDV5Z.js → chunk-5763DULU.js} +214 -69
- package/dist/chunk-5763DULU.js.map +1 -0
- package/dist/{chunk-BGYJW37L.js → chunk-5UVRGXD3.js} +277 -7
- package/dist/chunk-5UVRGXD3.js.map +1 -0
- package/dist/{chunk-QR24DJDF.cjs → chunk-62ZP23AW.cjs} +15 -15
- package/dist/{chunk-QR24DJDF.cjs.map → chunk-62ZP23AW.cjs.map} +1 -1
- package/dist/{chunk-ZI6VQSZA.cjs → chunk-65E3XWGF.cjs} +724 -577
- package/dist/chunk-65E3XWGF.cjs.map +1 -0
- package/dist/{chunk-S4KQ5LV4.js → chunk-775A4ZMN.js} +40 -6
- package/dist/chunk-775A4ZMN.js.map +1 -0
- package/dist/{chunk-J2ICGNHO.cjs → chunk-7BQXPCGN.cjs} +830 -169
- package/dist/chunk-7BQXPCGN.cjs.map +1 -0
- package/dist/{chunk-FJMPR3OH.cjs → chunk-7EO3TCQB.cjs} +33 -7
- package/dist/chunk-7EO3TCQB.cjs.map +1 -0
- package/dist/{chunk-DXEEHSGN.js → chunk-7VQXLT5O.js} +436 -43
- package/dist/chunk-7VQXLT5O.js.map +1 -0
- package/dist/chunk-A4OC4DVN.cjs +12 -0
- package/dist/{chunk-4SZ6XPTG.cjs.map → chunk-A4OC4DVN.cjs.map} +1 -1
- package/dist/{chunk-TDXIBCGY.cjs → chunk-A7J2YDDI.cjs} +5 -3
- package/dist/chunk-A7J2YDDI.cjs.map +1 -0
- package/dist/{chunk-55ITBQJI.js → chunk-AKLS3BYZ.js} +56 -5
- package/dist/chunk-AKLS3BYZ.js.map +1 -0
- package/dist/{chunk-D7PG5D5K.js → chunk-AMKPPFUO.js} +32 -6
- package/dist/chunk-AMKPPFUO.js.map +1 -0
- package/dist/{chunk-WUFASQUO.js → chunk-ASTI57H3.js} +3 -3
- package/dist/{chunk-WUFASQUO.js.map → chunk-ASTI57H3.js.map} +1 -1
- package/dist/{chunk-4QIKLIGG.js → chunk-BKCUOV7A.js} +15 -5
- package/dist/chunk-BKCUOV7A.js.map +1 -0
- package/dist/{chunk-JQXSZHF2.cjs → chunk-BSFJMMV3.cjs} +134 -7
- package/dist/chunk-BSFJMMV3.cjs.map +1 -0
- package/dist/{chunk-4GUTQPVO.js → chunk-C23WSN5E.js} +185 -24
- package/dist/chunk-C23WSN5E.js.map +1 -0
- package/dist/{chunk-QLUXRWKL.js → chunk-CBTIUB76.js} +3 -3
- package/dist/{chunk-QLUXRWKL.js.map → chunk-CBTIUB76.js.map} +1 -1
- package/dist/chunk-CMWHLBVN.cjs +2082 -0
- package/dist/chunk-CMWHLBVN.cjs.map +1 -0
- package/dist/{chunk-QB6VMOJ2.cjs → chunk-CXBHOOIQ.cjs} +45 -10
- package/dist/chunk-CXBHOOIQ.cjs.map +1 -0
- package/dist/{chunk-FL5UQRES.js → chunk-DCQVHAYQ.js} +5 -5
- package/dist/{chunk-FL5UQRES.js.map → chunk-DCQVHAYQ.js.map} +1 -1
- package/dist/chunk-DTVWX77L.cjs +3432 -0
- package/dist/chunk-DTVWX77L.cjs.map +1 -0
- package/dist/chunk-DYATYRY4.js +47 -0
- package/dist/chunk-DYATYRY4.js.map +1 -0
- package/dist/chunk-E4WNQBEF.cjs +217 -0
- package/dist/chunk-E4WNQBEF.cjs.map +1 -0
- package/dist/{chunk-G6Q5UM7U.js → chunk-FWJKWH6U.js} +5 -3
- package/dist/chunk-FWJKWH6U.js.map +1 -0
- package/dist/chunk-GF3KXUTW.js +3393 -0
- package/dist/chunk-GF3KXUTW.js.map +1 -0
- package/dist/{chunk-VTN3C3CH.js → chunk-HFHTMECK.js} +7 -7
- package/dist/{chunk-VTN3C3CH.js.map → chunk-HFHTMECK.js.map} +1 -1
- package/dist/{chunk-FLEQ2HBK.js → chunk-I5MOXA4H.js} +20 -14
- package/dist/chunk-I5MOXA4H.js.map +1 -0
- package/dist/{chunk-4MB3AQAQ.js → chunk-J3WZ3FA2.js} +77 -3
- package/dist/chunk-J3WZ3FA2.js.map +1 -0
- package/dist/{chunk-UR7W5YQK.js → chunk-JBQQ7THH.js} +5 -3
- package/dist/chunk-JBQQ7THH.js.map +1 -0
- package/dist/{chunk-TKP5T34P.js → chunk-JXA2ZP2Q.js} +6 -16
- package/dist/chunk-JXA2ZP2Q.js.map +1 -0
- package/dist/{chunk-GYA4JRP5.js → chunk-JXXDZTMN.js} +2 -2
- package/dist/chunk-JXXDZTMN.js.map +1 -0
- package/dist/chunk-JZUR7ZWE.js +254 -0
- package/dist/chunk-JZUR7ZWE.js.map +1 -0
- package/dist/{chunk-42PYUGLS.js → chunk-K5ELX2GT.js} +108 -8
- package/dist/chunk-K5ELX2GT.js.map +1 -0
- package/dist/{chunk-Q6EDCUXV.js → chunk-L65OU2U3.js} +556 -204
- package/dist/chunk-L65OU2U3.js.map +1 -0
- package/dist/{chunk-D54WH24V.cjs → chunk-LBJKZXDE.cjs} +115 -13
- package/dist/chunk-LBJKZXDE.cjs.map +1 -0
- package/dist/{chunk-FQ7ZPOTZ.js → chunk-LGHCOKTT.js} +637 -493
- package/dist/chunk-LGHCOKTT.js.map +1 -0
- package/dist/chunk-M3WP7RQZ.js +736 -0
- package/dist/chunk-M3WP7RQZ.js.map +1 -0
- package/dist/{chunk-4MEWSUVM.cjs → chunk-M4BDU5RX.cjs} +12 -12
- package/dist/chunk-M4BDU5RX.cjs.map +1 -0
- package/dist/chunk-MCLYU6BN.cjs +332 -0
- package/dist/chunk-MCLYU6BN.cjs.map +1 -0
- package/dist/{chunk-MIJKEVV7.js → chunk-MSY7FSX4.js} +37 -4
- package/dist/chunk-MSY7FSX4.js.map +1 -0
- package/dist/{chunk-H7VE3FL4.cjs → chunk-MUK4BU7K.cjs} +519 -125
- package/dist/chunk-MUK4BU7K.cjs.map +1 -0
- package/dist/{chunk-D56XG4EI.cjs → chunk-MXJDTH5B.cjs} +56 -45
- package/dist/chunk-MXJDTH5B.cjs.map +1 -0
- package/dist/{chunk-5EKPX6QU.js → chunk-MZLPX2R7.js} +8 -9
- package/dist/chunk-MZLPX2R7.js.map +1 -0
- package/dist/chunk-N66HCGCY.cjs +57 -0
- package/dist/chunk-N66HCGCY.cjs.map +1 -0
- package/dist/{chunk-CPEL3YIR.cjs → chunk-NFCVHQ5P.cjs} +55 -22
- package/dist/chunk-NFCVHQ5P.cjs.map +1 -0
- package/dist/{chunk-C4F7VJ5D.cjs → chunk-NI72YOJB.cjs} +84 -2
- package/dist/chunk-NI72YOJB.cjs.map +1 -0
- package/dist/{chunk-BNS75YNF.cjs → chunk-NK5Q7UNE.cjs} +10 -20
- package/dist/chunk-NK5Q7UNE.cjs.map +1 -0
- package/dist/{chunk-TKORQCG4.js → chunk-NLI2AVEF.js} +386 -862
- package/dist/chunk-NLI2AVEF.js.map +1 -0
- package/dist/{chunk-YMTXMPV6.js → chunk-NMWDXFPH.js} +4 -4
- package/dist/{chunk-YMTXMPV6.js.map → chunk-NMWDXFPH.js.map} +1 -1
- package/dist/{chunk-J7LXTUTP.cjs → chunk-PF6F5NLJ.cjs} +604 -1094
- package/dist/chunk-PF6F5NLJ.cjs.map +1 -0
- package/dist/{chunk-NAAMUYMS.cjs → chunk-PNN7RHVR.cjs} +58 -48
- package/dist/chunk-PNN7RHVR.cjs.map +1 -0
- package/dist/{chunk-YOALGKRE.cjs → chunk-PNTCATDE.cjs} +3528 -1763
- package/dist/chunk-PNTCATDE.cjs.map +1 -0
- package/dist/{chunk-6DCB664D.js → chunk-PQBQG7IM.js} +3 -3
- package/dist/{chunk-6DCB664D.js.map → chunk-PQBQG7IM.js.map} +1 -1
- package/dist/{chunk-UCQ3BFSY.cjs → chunk-PT7RIRIZ.cjs} +684 -332
- package/dist/chunk-PT7RIRIZ.cjs.map +1 -0
- package/dist/{chunk-ACSVZPK7.cjs → chunk-PWLJ77UV.cjs} +5 -3
- package/dist/chunk-PWLJ77UV.cjs.map +1 -0
- package/dist/chunk-Q3G6A4I3.cjs +755 -0
- package/dist/chunk-Q3G6A4I3.cjs.map +1 -0
- package/dist/{chunk-Z3WO6MND.cjs → chunk-RMQLSPA2.cjs} +297 -27
- package/dist/chunk-RMQLSPA2.cjs.map +1 -0
- package/dist/{chunk-SJDMVITW.cjs → chunk-RT5ZY4XA.cjs} +6 -5
- package/dist/chunk-RT5ZY4XA.cjs.map +1 -0
- package/dist/{chunk-ID4WXTKL.js → chunk-S6PW7VV5.js} +2382 -617
- package/dist/chunk-S6PW7VV5.js.map +1 -0
- package/dist/{chunk-4GUBTVPV.js → chunk-T4XNKYXB.js} +780 -126
- package/dist/chunk-T4XNKYXB.js.map +1 -0
- package/dist/{chunk-YDWLUT5V.cjs → chunk-TQR4KBY2.cjs} +323 -178
- package/dist/chunk-TQR4KBY2.cjs.map +1 -0
- package/dist/{chunk-OOA273IJ.cjs → chunk-U5SON7R2.cjs} +27 -27
- package/dist/{chunk-OOA273IJ.cjs.map → chunk-U5SON7R2.cjs.map} +1 -1
- package/dist/{chunk-IX64T5OL.cjs → chunk-U63GDNRC.cjs} +32 -26
- package/dist/chunk-U63GDNRC.cjs.map +1 -0
- package/dist/{chunk-WDBXJOU5.cjs → chunk-UDISNXDL.cjs} +56 -47
- package/dist/chunk-UDISNXDL.cjs.map +1 -0
- package/dist/{chunk-EFEOOCJ3.js → chunk-UV3SN74I.js} +5 -5
- package/dist/{chunk-EFEOOCJ3.js.map → chunk-UV3SN74I.js.map} +1 -1
- package/dist/{chunk-U5W4LKZV.cjs → chunk-VCRRKBLN.cjs} +9 -9
- package/dist/{chunk-U5W4LKZV.cjs.map → chunk-VCRRKBLN.cjs.map} +1 -1
- package/dist/{chunk-ISLPNELU.js → chunk-VGTJPTIC.js} +16 -5
- package/dist/chunk-VGTJPTIC.js.map +1 -0
- package/dist/chunk-VLT6PEWR.cjs +60 -0
- package/dist/chunk-VLT6PEWR.cjs.map +1 -0
- package/dist/chunk-VQH4VAWD.cjs +12 -0
- package/dist/{chunk-B3F2D3SP.cjs.map → chunk-VQH4VAWD.cjs.map} +1 -1
- package/dist/{chunk-SNXVDZAJ.js → chunk-W6OG3HDG.js} +5 -4
- package/dist/chunk-W6OG3HDG.js.map +1 -0
- package/dist/chunk-WDGA7IYB.js +207 -0
- package/dist/chunk-WDGA7IYB.js.map +1 -0
- package/dist/chunk-WQZLUGVR.js +2057 -0
- package/dist/chunk-WQZLUGVR.js.map +1 -0
- package/dist/{chunk-EKFUG4G7.cjs → chunk-XBYVV3MP.cjs} +60 -5
- package/dist/chunk-XBYVV3MP.cjs.map +1 -0
- package/dist/{chunk-5GAONX5G.cjs → chunk-XCAGZAKP.cjs} +7 -7
- package/dist/{chunk-5GAONX5G.cjs.map → chunk-XCAGZAKP.cjs.map} +1 -1
- package/dist/chunk-XL7SLCXS.js +309 -0
- package/dist/chunk-XL7SLCXS.js.map +1 -0
- package/dist/{chunk-SVJ5GEXW.cjs → chunk-XUO3KQFO.cjs} +2 -2
- package/dist/chunk-XUO3KQFO.cjs.map +1 -0
- package/dist/{chunk-F4NHQHEZ.cjs → chunk-YAAM3M2S.cjs} +10 -10
- package/dist/{chunk-F4NHQHEZ.cjs.map → chunk-YAAM3M2S.cjs.map} +1 -1
- package/dist/{chunk-RUQS6M7G.js → chunk-YBFHZUXX.js} +6 -6
- package/dist/chunk-YBFHZUXX.js.map +1 -0
- package/dist/{chunk-KMXWZLQX.cjs → chunk-YBQOHKHN.cjs} +99 -100
- package/dist/chunk-YBQOHKHN.cjs.map +1 -0
- package/dist/{chunk-PVUI6KGR.js → chunk-YDFDBE6D.js} +93 -6
- package/dist/chunk-YDFDBE6D.js.map +1 -0
- package/dist/{chunk-P7GTYOLB.cjs → chunk-YVWGEQJE.cjs} +297 -135
- package/dist/chunk-YVWGEQJE.cjs.map +1 -0
- package/dist/{chunk-QT6V3YRH.js → chunk-YWGVVALJ.js} +3 -3
- package/dist/{chunk-QT6V3YRH.js.map → chunk-YWGVVALJ.js.map} +1 -1
- package/dist/core/index.cjs +40 -40
- package/dist/core/index.d.cts +4 -5
- package/dist/core/index.d.ts +4 -5
- package/dist/core/index.js +8 -8
- package/dist/{ddl-BG4J805L.d.cts → ddl-BJcOp4WZ.d.cts} +3 -3
- package/dist/{ddl-CgiS9QLj.d.ts → ddl-BXfGHbPN.d.ts} +3 -3
- package/dist/graph-extension/index.cjs +26 -22
- package/dist/graph-extension/index.d.cts +16 -6
- package/dist/graph-extension/index.d.ts +16 -6
- package/dist/graph-extension/index.js +7 -7
- package/dist/graph-merge/index.cjs +2459 -654
- package/dist/graph-merge/index.cjs.map +1 -1
- package/dist/graph-merge/index.d.cts +170 -33
- package/dist/graph-merge/index.d.ts +170 -33
- package/dist/graph-merge/index.js +2269 -467
- package/dist/graph-merge/index.js.map +1 -1
- package/dist/{index-C3y_98iN.d.cts → index-BSHvKfok.d.ts} +221 -10
- package/dist/{index-BBxZCUdu.d.ts → index-DxaYCv8Y.d.cts} +221 -10
- package/dist/index.cjs +330 -268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +590 -576
- package/dist/index.d.ts +590 -576
- package/dist/index.js +73 -49
- package/dist/index.js.map +1 -1
- package/dist/indexes/index.cjs +17 -17
- package/dist/indexes/index.d.cts +4 -4
- package/dist/indexes/index.d.ts +4 -4
- package/dist/indexes/index.js +10 -10
- package/dist/interchange/index.cjs +165 -51
- package/dist/interchange/index.cjs.map +1 -1
- package/dist/interchange/index.d.cts +261 -16
- package/dist/interchange/index.d.ts +261 -16
- package/dist/interchange/index.js +128 -25
- package/dist/interchange/index.js.map +1 -1
- package/dist/{manager-YoLEfcc1.d.ts → manager-CfZAimti.d.ts} +36 -5
- package/dist/{manager-KS7CGQQT.d.cts → manager-HGk6E_HW.d.cts} +36 -5
- package/dist/{materialize-indexes-Bhhyb1B3.d.ts → materialize-indexes-6A7dWGtb.d.cts} +10 -2
- package/dist/{materialize-indexes-OHRWypW6.d.cts → materialize-indexes-BMJP6Tms.d.ts} +10 -2
- package/dist/{postgres-DpkW3R5Y.d.cts → postgres-DFYWENdR.d.cts} +1961 -439
- package/dist/{postgres-DwqyIniy.d.ts → postgres-e7a9TdvR.d.ts} +1961 -439
- package/dist/profiler/index.cjs +15 -15
- package/dist/profiler/index.d.cts +9 -10
- package/dist/profiler/index.d.ts +9 -10
- package/dist/profiler/index.js +4 -4
- package/dist/provenance/index.cjs +46 -48
- package/dist/provenance/index.cjs.map +1 -1
- package/dist/provenance/index.d.cts +7 -7
- package/dist/provenance/index.d.ts +7 -7
- package/dist/provenance/index.js +12 -14
- package/dist/provenance/index.js.map +1 -1
- package/dist/schema/index.cjs +50 -48
- package/dist/schema/index.d.cts +9 -16
- package/dist/schema/index.d.ts +9 -16
- package/dist/schema/index.js +23 -21
- package/dist/{searchable-DLWEfC4a.d.ts → searchable-9OQoPuRs.d.ts} +1 -2
- package/dist/{searchable-CIZL8Hyw.d.cts → searchable-CIje5XYD.d.cts} +1 -2
- package/dist/{sqlite-DY0WODUK.d.cts → sqlite-DerU5tc7.d.ts} +2029 -347
- package/dist/{sqlite-L-VIRzqA.d.ts → sqlite-DitnISa9.d.cts} +2029 -347
- package/dist/{store-view-A85FFRnk.d.cts → store-B0h6x7wo.d.cts} +1289 -767
- package/dist/{store-view-66p92IPi.d.ts → store-ClQ-MZQS.d.ts} +1289 -767
- package/dist/{temporal-BNbjXqer.d.cts → temporal-Bn-Fi8FJ.d.cts} +1 -1
- package/dist/{temporal-Bob6Nfbp.d.ts → temporal-BtM4UALv.d.ts} +1 -1
- package/dist/{types-DeORZhxn.d.cts → types-CoIx8HX5.d.cts} +1 -1
- package/dist/{types-Ch9a0Dx6.d.ts → types-HkveCCx0.d.cts} +726 -5
- package/dist/{types-Ch9a0Dx6.d.cts → types-HkveCCx0.d.ts} +726 -5
- package/dist/{types-ClCzT-dT.d.ts → types-KFfk-8Ah.d.ts} +1 -1
- package/dist/{where-DPw52ksb.d.cts → where--fk7_0kv.d.cts} +1 -1
- package/dist/{where-kWUZPvEA.d.ts → where-ws5hf-Ov.d.ts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-2L54C2C2.js +0 -144
- package/dist/chunk-2L54C2C2.js.map +0 -1
- package/dist/chunk-35S6P5ZU.js +0 -27
- package/dist/chunk-35S6P5ZU.js.map +0 -1
- package/dist/chunk-42PYUGLS.js.map +0 -1
- package/dist/chunk-4GUBTVPV.js.map +0 -1
- package/dist/chunk-4GUTQPVO.js.map +0 -1
- package/dist/chunk-4MB3AQAQ.js.map +0 -1
- package/dist/chunk-4MEWSUVM.cjs.map +0 -1
- package/dist/chunk-4QIKLIGG.js.map +0 -1
- package/dist/chunk-4SZ6XPTG.cjs +0 -12
- package/dist/chunk-55ITBQJI.js.map +0 -1
- package/dist/chunk-5EKPX6QU.js.map +0 -1
- package/dist/chunk-6RYPD3NJ.cjs +0 -1282
- package/dist/chunk-6RYPD3NJ.cjs.map +0 -1
- package/dist/chunk-764JHTLT.cjs +0 -15
- package/dist/chunk-764JHTLT.cjs.map +0 -1
- package/dist/chunk-ACSVZPK7.cjs.map +0 -1
- package/dist/chunk-B3F2D3SP.cjs +0 -12
- package/dist/chunk-BGYJW37L.js.map +0 -1
- package/dist/chunk-BNS75YNF.cjs.map +0 -1
- package/dist/chunk-C4F7VJ5D.cjs.map +0 -1
- package/dist/chunk-CBKG6HEP.js +0 -1263
- package/dist/chunk-CBKG6HEP.js.map +0 -1
- package/dist/chunk-CPEL3YIR.cjs.map +0 -1
- package/dist/chunk-D54WH24V.cjs.map +0 -1
- package/dist/chunk-D56XG4EI.cjs.map +0 -1
- package/dist/chunk-D7PG5D5K.js.map +0 -1
- package/dist/chunk-DI74SMXS.cjs +0 -29
- package/dist/chunk-DI74SMXS.cjs.map +0 -1
- package/dist/chunk-DK2S42XB.js +0 -77
- package/dist/chunk-DK2S42XB.js.map +0 -1
- package/dist/chunk-DRACN6XG.js +0 -27
- package/dist/chunk-DRACN6XG.js.map +0 -1
- package/dist/chunk-DXEEHSGN.js.map +0 -1
- package/dist/chunk-EKFUG4G7.cjs.map +0 -1
- package/dist/chunk-EQ7JKLZO.cjs +0 -157
- package/dist/chunk-EQ7JKLZO.cjs.map +0 -1
- package/dist/chunk-FJMPR3OH.cjs.map +0 -1
- package/dist/chunk-FLEQ2HBK.js.map +0 -1
- package/dist/chunk-FQ7ZPOTZ.js.map +0 -1
- package/dist/chunk-G6Q5UM7U.js.map +0 -1
- package/dist/chunk-GYA4JRP5.js.map +0 -1
- package/dist/chunk-H7VE3FL4.cjs.map +0 -1
- package/dist/chunk-ID4WXTKL.js.map +0 -1
- package/dist/chunk-ISLPNELU.js.map +0 -1
- package/dist/chunk-ITE4IXAK.js +0 -25
- package/dist/chunk-ITE4IXAK.js.map +0 -1
- package/dist/chunk-IX64T5OL.cjs.map +0 -1
- package/dist/chunk-J2ICGNHO.cjs.map +0 -1
- package/dist/chunk-J7LXTUTP.cjs.map +0 -1
- package/dist/chunk-JQXSZHF2.cjs.map +0 -1
- package/dist/chunk-JXUTCSXX.cjs.map +0 -1
- package/dist/chunk-KCO5EFJU.js +0 -79
- package/dist/chunk-KCO5EFJU.js.map +0 -1
- package/dist/chunk-KKYELPZ2.cjs +0 -83
- package/dist/chunk-KKYELPZ2.cjs.map +0 -1
- package/dist/chunk-KMXWZLQX.cjs.map +0 -1
- package/dist/chunk-MIJKEVV7.js.map +0 -1
- package/dist/chunk-NAAMUYMS.cjs.map +0 -1
- package/dist/chunk-P7GTYOLB.cjs.map +0 -1
- package/dist/chunk-PVUI6KGR.js.map +0 -1
- package/dist/chunk-Q6EDCUXV.js.map +0 -1
- package/dist/chunk-QB6VMOJ2.cjs.map +0 -1
- package/dist/chunk-QC4MCOFV.cjs +0 -83
- package/dist/chunk-QC4MCOFV.cjs.map +0 -1
- package/dist/chunk-QMQLUA66.js +0 -12
- package/dist/chunk-QMQLUA66.js.map +0 -1
- package/dist/chunk-QRNYDV5Z.js.map +0 -1
- package/dist/chunk-RFRM3Q5K.js.map +0 -1
- package/dist/chunk-RUQS6M7G.js.map +0 -1
- package/dist/chunk-S4KQ5LV4.js.map +0 -1
- package/dist/chunk-SJDMVITW.cjs.map +0 -1
- package/dist/chunk-SNXVDZAJ.js.map +0 -1
- package/dist/chunk-SVJ5GEXW.cjs.map +0 -1
- package/dist/chunk-TDXIBCGY.cjs.map +0 -1
- package/dist/chunk-TKORQCG4.js.map +0 -1
- package/dist/chunk-TKP5T34P.js.map +0 -1
- package/dist/chunk-TRF4EBJT.cjs +0 -30
- package/dist/chunk-TRF4EBJT.cjs.map +0 -1
- package/dist/chunk-UCQ3BFSY.cjs.map +0 -1
- package/dist/chunk-UR7W5YQK.js.map +0 -1
- package/dist/chunk-V34A6R5V.js +0 -134
- package/dist/chunk-V34A6R5V.js.map +0 -1
- package/dist/chunk-WDAVPX46.js +0 -81
- package/dist/chunk-WDAVPX46.js.map +0 -1
- package/dist/chunk-WDBXJOU5.cjs.map +0 -1
- package/dist/chunk-YDWLUT5V.cjs.map +0 -1
- package/dist/chunk-YOALGKRE.cjs.map +0 -1
- package/dist/chunk-Z3WO6MND.cjs.map +0 -1
- package/dist/chunk-ZDH3DX3C.cjs +0 -30
- package/dist/chunk-ZDH3DX3C.cjs.map +0 -1
- package/dist/chunk-ZI6VQSZA.cjs.map +0 -1
- package/dist/chunk-ZTAJL4DT.cjs +0 -85
- package/dist/chunk-ZTAJL4DT.cjs.map +0 -1
- package/dist/define-graph-BGrJahvp.d.ts +0 -173
- package/dist/define-graph-alREY2wj.d.cts +0 -173
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
import { createStoreWithSchema } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import { importGraphStream, exportGraphStream } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import { storeBackend,
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
8
|
-
import '../chunk-
|
|
9
|
-
import '../chunk-
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import '../chunk-
|
|
14
|
-
import {
|
|
15
|
-
import '../chunk-
|
|
16
|
-
import {
|
|
17
|
-
import '../chunk-
|
|
18
|
-
import '../chunk-
|
|
19
|
-
import '../chunk-
|
|
20
|
-
import '../chunk-FLEQ2HBK.js';
|
|
1
|
+
import { createStoreWithSchema } from '../chunk-S6PW7VV5.js';
|
|
2
|
+
import '../chunk-NMWDXFPH.js';
|
|
3
|
+
import { importGraphStream, exportGraphStream, importGraph, exportGraph } from '../chunk-WQZLUGVR.js';
|
|
4
|
+
import '../chunk-L65OU2U3.js';
|
|
5
|
+
import { storeBackend, storeRuntime, transactionBackend } from '../chunk-M3WP7RQZ.js';
|
|
6
|
+
import { computeSchemaHash, serializeSchema } from '../chunk-LGHCOKTT.js';
|
|
7
|
+
import { defineNode } from '../chunk-JXA2ZP2Q.js';
|
|
8
|
+
import { encodeTupleKey, normalizeIdentityPair, identityReferenceKey, identityAssertionSemanticKey } from '../chunk-GF3KXUTW.js';
|
|
9
|
+
import { generateId, compareStrings, compareCodePoints, lockRecordedGraphWrite, readRevisionOrigin, readRecordedClock } from '../chunk-NLI2AVEF.js';
|
|
10
|
+
import { sortedReplacer } from '../chunk-JBQQ7THH.js';
|
|
11
|
+
import '../chunk-JZUR7ZWE.js';
|
|
12
|
+
import { createSqlSchema } from '../chunk-MSY7FSX4.js';
|
|
13
|
+
import { snapshotExportContention } from '../chunk-AKLS3BYZ.js';
|
|
14
|
+
import { getNodeKinds, getEdgeKinds, defineInternalGraph } from '../chunk-775A4ZMN.js';
|
|
15
|
+
import { computeUniqueKey } from '../chunk-WDGA7IYB.js';
|
|
16
|
+
import { canonicalizeDatabaseTimestamp } from '../chunk-XL7SLCXS.js';
|
|
17
|
+
import '../chunk-DCQVHAYQ.js';
|
|
18
|
+
import '../chunk-CBTIUB76.js';
|
|
19
|
+
import '../chunk-I5MOXA4H.js';
|
|
21
20
|
import { sha256Hex } from '../chunk-K34OPSJK.js';
|
|
22
21
|
import '../chunk-2EBMPJBO.js';
|
|
23
|
-
import '../chunk-
|
|
24
|
-
import '../chunk-
|
|
25
|
-
import '../chunk-
|
|
26
|
-
import '../chunk-
|
|
27
|
-
import { requireDefined } from '../chunk-QMQLUA66.js';
|
|
22
|
+
import '../chunk-YDFDBE6D.js';
|
|
23
|
+
import { asCompiledStatementSql, sql, asCompiledRowsSql } from '../chunk-PQBQG7IM.js';
|
|
24
|
+
import '../chunk-AMKPPFUO.js';
|
|
25
|
+
import '../chunk-W6OG3HDG.js';
|
|
28
26
|
import '../chunk-PC2UBAUB.js';
|
|
29
|
-
import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-
|
|
30
|
-
export { isErr, isOk, unwrap } from '../chunk-
|
|
31
|
-
import '../chunk-
|
|
32
|
-
import '../chunk-
|
|
33
|
-
import '../chunk-
|
|
34
|
-
import
|
|
35
|
-
import '../chunk-
|
|
27
|
+
import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-T4XNKYXB.js';
|
|
28
|
+
export { isErr, isOk, unwrap } from '../chunk-T4XNKYXB.js';
|
|
29
|
+
import { META_EDGE_EQUIVALENT_TO, META_EDGE_SAME_AS } from '../chunk-JXXDZTMN.js';
|
|
30
|
+
import '../chunk-K5ELX2GT.js';
|
|
31
|
+
import { TypeGraphError, ConfigurationError, IdentityContradictionError, NodeNotFoundError } from '../chunk-J3WZ3FA2.js';
|
|
32
|
+
import '../chunk-FWJKWH6U.js';
|
|
33
|
+
import { createDataKeyedBag, hasOwnKey, requireDefined, readOwnProperty } from '../chunk-DYATYRY4.js';
|
|
36
34
|
import { z } from 'zod';
|
|
37
35
|
|
|
38
36
|
// src/graph-merge/canonical-props.ts
|
|
@@ -101,8 +99,834 @@ function compareMergeKeys(left, right) {
|
|
|
101
99
|
return leftKind < rightKind ? -1 : leftKind > rightKind ? 1 : 0;
|
|
102
100
|
}
|
|
103
101
|
|
|
102
|
+
// src/graph-merge/errors.ts
|
|
103
|
+
var MERGE_ERROR_CODES = {
|
|
104
|
+
merge: "GRAPH_MERGE_ERROR",
|
|
105
|
+
invalidOptions: "GRAPH_MERGE_INVALID_OPTIONS",
|
|
106
|
+
branch: "GRAPH_MERGE_BRANCH_ERROR",
|
|
107
|
+
similarityUnavailable: "GRAPH_MERGE_SIMILARITY_UNAVAILABLE",
|
|
108
|
+
conflict: "GRAPH_MERGE_CONFLICT",
|
|
109
|
+
identityConflict: "GRAPH_MERGE_IDENTITY_CONFLICT",
|
|
110
|
+
baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH"
|
|
111
|
+
};
|
|
112
|
+
function toTypeGraphErrorOptions(category, options) {
|
|
113
|
+
return {
|
|
114
|
+
category,
|
|
115
|
+
...options.details === void 0 ? {} : { details: options.details },
|
|
116
|
+
...options.suggestion === void 0 ? {} : { suggestion: options.suggestion },
|
|
117
|
+
...options.cause === void 0 ? {} : { cause: options.cause }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
var MergeError = class extends TypeGraphError {
|
|
121
|
+
static errorCategory = "system";
|
|
122
|
+
constructor(message, options = {}) {
|
|
123
|
+
const errorClass = new.target;
|
|
124
|
+
super(
|
|
125
|
+
message,
|
|
126
|
+
MERGE_ERROR_CODES.merge,
|
|
127
|
+
toTypeGraphErrorOptions(errorClass.errorCategory, options)
|
|
128
|
+
);
|
|
129
|
+
this.name = "MergeError";
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var InvalidMergeOptionsError = class extends MergeError {
|
|
133
|
+
static errorCategory = "user";
|
|
134
|
+
code = MERGE_ERROR_CODES.invalidOptions;
|
|
135
|
+
constructor(message, options = {}) {
|
|
136
|
+
super(message, options);
|
|
137
|
+
this.name = "InvalidMergeOptionsError";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var BranchError = class extends TypeGraphError {
|
|
141
|
+
constructor(message, options = {}) {
|
|
142
|
+
super(
|
|
143
|
+
message,
|
|
144
|
+
MERGE_ERROR_CODES.branch,
|
|
145
|
+
toTypeGraphErrorOptions("system", options)
|
|
146
|
+
);
|
|
147
|
+
this.name = "BranchError";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var SimilarityUnavailableError = class extends MergeError {
|
|
151
|
+
code = MERGE_ERROR_CODES.similarityUnavailable;
|
|
152
|
+
constructor(message, options = {}) {
|
|
153
|
+
super(message, {
|
|
154
|
+
...options,
|
|
155
|
+
suggestion: options.suggestion ?? "Pass MergeOptions.embedder (a local model), or use a fulltext/custom similarity strategy."
|
|
156
|
+
});
|
|
157
|
+
this.name = "SimilarityUnavailableError";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var MergeConflictError = class extends MergeError {
|
|
161
|
+
code = MERGE_ERROR_CODES.conflict;
|
|
162
|
+
constructor(message, options = {}) {
|
|
163
|
+
super(message, options);
|
|
164
|
+
this.name = "MergeConflictError";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var IdentityMergeConflictError = class extends MergeError {
|
|
168
|
+
code = MERGE_ERROR_CODES.identityConflict;
|
|
169
|
+
constructor(message, options = {}) {
|
|
170
|
+
super(message, options);
|
|
171
|
+
this.name = "IdentityMergeConflictError";
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var BaseVersionMismatchError = class extends MergeError {
|
|
175
|
+
code = MERGE_ERROR_CODES.baseVersionMismatch;
|
|
176
|
+
constructor(message, options = {}) {
|
|
177
|
+
super(message, {
|
|
178
|
+
...options,
|
|
179
|
+
suggestion: options.suggestion ?? "Re-branch from the current target so the branch base matches before merging."
|
|
180
|
+
});
|
|
181
|
+
this.name = "BaseVersionMismatchError";
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
function describeCause(cause) {
|
|
185
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/graph-merge/union-find.ts
|
|
189
|
+
var UnionFind = class {
|
|
190
|
+
constructor(compare) {
|
|
191
|
+
this.compare = compare;
|
|
192
|
+
}
|
|
193
|
+
compare;
|
|
194
|
+
parent = /* @__PURE__ */ new Map();
|
|
195
|
+
/** Adds `value` as its own singleton set if it is not already present. */
|
|
196
|
+
add(value) {
|
|
197
|
+
if (!this.parent.has(value)) {
|
|
198
|
+
this.parent.set(value, value);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Returns the representative of `value`'s set, compressing the path to it. */
|
|
202
|
+
find(value) {
|
|
203
|
+
this.add(value);
|
|
204
|
+
let root = value;
|
|
205
|
+
while (this.parent.get(root) !== root) {
|
|
206
|
+
root = requireDefined(this.parent.get(root));
|
|
207
|
+
}
|
|
208
|
+
let cursor = value;
|
|
209
|
+
while (cursor !== root) {
|
|
210
|
+
const next = requireDefined(this.parent.get(cursor));
|
|
211
|
+
this.parent.set(cursor, root);
|
|
212
|
+
cursor = next;
|
|
213
|
+
}
|
|
214
|
+
return root;
|
|
215
|
+
}
|
|
216
|
+
/** Merges the sets of `left` and `right`; the `compare`-minimal root wins. */
|
|
217
|
+
union(left, right) {
|
|
218
|
+
const leftRoot = this.find(left);
|
|
219
|
+
const rightRoot = this.find(right);
|
|
220
|
+
if (leftRoot === rightRoot) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (this.compare(leftRoot, rightRoot) <= 0) {
|
|
224
|
+
this.parent.set(rightRoot, leftRoot);
|
|
225
|
+
} else {
|
|
226
|
+
this.parent.set(leftRoot, rightRoot);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
|
|
230
|
+
members() {
|
|
231
|
+
return [...this.parent.keys()];
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/graph-merge/merge-identity.ts
|
|
236
|
+
function endpointTuple(assertion) {
|
|
237
|
+
return [assertion.a.kind, assertion.a.id, assertion.b.kind, assertion.b.id];
|
|
238
|
+
}
|
|
239
|
+
function identityEndpointKey(assertion) {
|
|
240
|
+
return encodeTupleKey(endpointTuple(assertion));
|
|
241
|
+
}
|
|
242
|
+
function identitySemanticKey(assertion) {
|
|
243
|
+
return identityAssertionSemanticKey(
|
|
244
|
+
assertion.relation,
|
|
245
|
+
assertion.a,
|
|
246
|
+
assertion.b
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
function compareIdentitySurvivors(left, right) {
|
|
250
|
+
const byValidity = compareCodePoints(left.validFrom, right.validFrom);
|
|
251
|
+
return byValidity === 0 ? compareCodePoints(left.id, right.id) : byValidity;
|
|
252
|
+
}
|
|
253
|
+
var DUPLICATE_IDENTITY_ASSERTION_DROP_REASON = "identity:duplicate-assertion";
|
|
254
|
+
var REDUNDANT_IDENTITY_ASSERTION_DROP_REASON = "identity:endpoints-collapsed";
|
|
255
|
+
function droppedIdentityAssertion(assertion, reason) {
|
|
256
|
+
return { kind: "identity", id: assertion.id, reason };
|
|
257
|
+
}
|
|
258
|
+
function dedupeIdentityAssertions(assertions, committedIds) {
|
|
259
|
+
const survivorBySemantic = /* @__PURE__ */ new Map();
|
|
260
|
+
const dropped = [];
|
|
261
|
+
for (const assertion of assertions) {
|
|
262
|
+
const key = identitySemanticKey(assertion);
|
|
263
|
+
const previous = survivorBySemantic.get(key);
|
|
264
|
+
if (previous === void 0) {
|
|
265
|
+
survivorBySemantic.set(key, assertion);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const assertionCommitted = committedIds.has(assertion.id);
|
|
269
|
+
const previousCommitted = committedIds.has(previous.id);
|
|
270
|
+
const [survivor, loser] = assertionCommitted === previousCommitted ? compareIdentitySurvivors(assertion, previous) < 0 ? [assertion, previous] : [previous, assertion] : assertionCommitted ? [assertion, previous] : [previous, assertion];
|
|
271
|
+
survivorBySemantic.set(key, survivor);
|
|
272
|
+
if (loser.id === survivor.id && loser.validFrom === survivor.validFrom && (loser.validTo ?? void 0) === (survivor.validTo ?? void 0)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
dropped.push(
|
|
276
|
+
droppedIdentityAssertion(loser, DUPLICATE_IDENTITY_ASSERTION_DROP_REASON)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
survivors: [...survivorBySemantic.values()].toSorted(
|
|
281
|
+
(left, right) => compareCodePoints(identitySemanticKey(left), identitySemanticKey(right))
|
|
282
|
+
),
|
|
283
|
+
dropped
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function assertNoOpposingIdentityRelations(assertions) {
|
|
287
|
+
const byEndpoint = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const assertion of assertions) {
|
|
289
|
+
const key = identityEndpointKey(assertion);
|
|
290
|
+
const group = byEndpoint.get(key) ?? [];
|
|
291
|
+
group.push(assertion);
|
|
292
|
+
byEndpoint.set(key, group);
|
|
293
|
+
}
|
|
294
|
+
for (const [endpoint, group] of byEndpoint) {
|
|
295
|
+
if (new Set(group.map((assertion) => assertion.relation)).size < 2) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
throw new IdentityMergeConflictError(
|
|
299
|
+
"Branches asserted opposing identity relations for one endpoint pair.",
|
|
300
|
+
{ details: { endpoint, assertions: group } }
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function assertNoRetractReassertRace(staging) {
|
|
305
|
+
const retractedBySemantic = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const staged of staging.retractedIdentityAssertions) {
|
|
307
|
+
const key = identitySemanticKey(staged.assertion);
|
|
308
|
+
const retractions = retractedBySemantic.get(key) ?? [];
|
|
309
|
+
retractions.push(staged);
|
|
310
|
+
retractedBySemantic.set(key, retractions);
|
|
311
|
+
}
|
|
312
|
+
for (const staged of staging.newIdentityAssertions) {
|
|
313
|
+
const retractions = retractedBySemantic.get(identitySemanticKey(staged.assertion)) ?? [];
|
|
314
|
+
const selfRetracted = retractions.some(
|
|
315
|
+
(retraction) => retraction.branchId === staged.branchId
|
|
316
|
+
);
|
|
317
|
+
if (selfRetracted) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const crossBranchRetraction = retractions.find(
|
|
321
|
+
(retraction) => retraction.assertion.id !== staged.assertion.id
|
|
322
|
+
);
|
|
323
|
+
if (crossBranchRetraction !== void 0) {
|
|
324
|
+
throw new IdentityMergeConflictError(
|
|
325
|
+
"Branches contain a retract/reassert race for one identity pair.",
|
|
326
|
+
{
|
|
327
|
+
details: {
|
|
328
|
+
retractedAssertion: crossBranchRetraction.assertion,
|
|
329
|
+
retractedBy: crossBranchRetraction.branchId,
|
|
330
|
+
reassertedAssertion: staged.assertion,
|
|
331
|
+
reassertedBy: staged.branchId
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
var RETRACTION_TARGET_MISMATCH_DROP_REASON = "identity:retraction-target-mismatch";
|
|
339
|
+
var RETRACTION_DELETION_OVERRULED_DROP_REASON = "identity:deletion-overruled";
|
|
340
|
+
var NO_STORED_ASSERTIONS = /* @__PURE__ */ new Map();
|
|
341
|
+
function planIdentityChanges(staging, storedIdentityRowsById) {
|
|
342
|
+
assertNoOpposingIdentityRelations(
|
|
343
|
+
staging.newIdentityAssertions.map((staged) => staged.assertion)
|
|
344
|
+
);
|
|
345
|
+
assertNoRetractReassertRace(staging);
|
|
346
|
+
assertOneIdOneTruth(
|
|
347
|
+
staging.newIdentityAssertions.map((staged) => staged.assertion),
|
|
348
|
+
NO_STORED_ASSERTIONS
|
|
349
|
+
);
|
|
350
|
+
const committedIds = new Set(
|
|
351
|
+
staging.newIdentityAssertions.map((staged) => staged.assertion).filter((assertion) => {
|
|
352
|
+
const stored = storedIdentityRowsById.get(assertion.id);
|
|
353
|
+
return stored !== void 0 && assertionTruthKey(stored) === assertionTruthKey(assertion);
|
|
354
|
+
}).map((assertion) => assertion.id)
|
|
355
|
+
);
|
|
356
|
+
const deduped = dedupeIdentityAssertions(
|
|
357
|
+
staging.newIdentityAssertions.map((staged) => staged.assertion),
|
|
358
|
+
committedIds
|
|
359
|
+
);
|
|
360
|
+
const retractionById = /* @__PURE__ */ new Map();
|
|
361
|
+
for (const staged of staging.retractedIdentityAssertions) {
|
|
362
|
+
const previous = retractionById.get(staged.assertion.id);
|
|
363
|
+
if (previous !== void 0 && assertionIdentityKey(previous) !== assertionIdentityKey(staged.assertion)) {
|
|
364
|
+
throw new IdentityMergeConflictError(
|
|
365
|
+
"One assertion id was staged for retraction with two different identity truths.",
|
|
366
|
+
{
|
|
367
|
+
details: {
|
|
368
|
+
assertionId: staged.assertion.id,
|
|
369
|
+
first: previous,
|
|
370
|
+
second: staged.assertion
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
retractionById.set(staged.assertion.id, staged.assertion);
|
|
376
|
+
}
|
|
377
|
+
const retractions = [];
|
|
378
|
+
const retractionDropped = [];
|
|
379
|
+
for (const [assertionId, retraction] of retractionById) {
|
|
380
|
+
const stored = storedIdentityRowsById.get(assertionId);
|
|
381
|
+
if (stored !== void 0 && stored.validTo === void 0 && assertionIdentityKey(stored) !== assertionIdentityKey(retraction)) {
|
|
382
|
+
retractionDropped.push({
|
|
383
|
+
kind: "identity",
|
|
384
|
+
id: assertionId,
|
|
385
|
+
reason: RETRACTION_TARGET_MISMATCH_DROP_REASON
|
|
386
|
+
});
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
retractions.push(retraction);
|
|
390
|
+
}
|
|
391
|
+
const survivingIds = new Set(
|
|
392
|
+
deduped.survivors.map((survivor) => survivor.id)
|
|
393
|
+
);
|
|
394
|
+
for (const retraction of retractions) {
|
|
395
|
+
if (survivingIds.has(retraction.id)) {
|
|
396
|
+
throw new IdentityMergeConflictError(
|
|
397
|
+
"One assertion id was staged as both a new assertion and a retraction.",
|
|
398
|
+
{ details: { assertionId: retraction.id } }
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
assertions: deduped.survivors,
|
|
404
|
+
retractions: retractions.toSorted(
|
|
405
|
+
(left, right) => compareCodePoints(left.id, right.id)
|
|
406
|
+
),
|
|
407
|
+
dropped: [...deduped.dropped, ...retractionDropped]
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function resolveIdentityEndpoint(endpoint, canonicalOf, retypeMap) {
|
|
411
|
+
const key = mergeKeyOf(endpoint);
|
|
412
|
+
const canonical = canonicalOf.get(key) ?? key;
|
|
413
|
+
return {
|
|
414
|
+
kind: retypeMap.get(canonical) ?? kindOf(canonical),
|
|
415
|
+
id: idOf(canonical)
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function remapIdentityAssertionEndpoints(assertions, canonicalOf, retypeMap, storedRowsById) {
|
|
419
|
+
const remapped = [];
|
|
420
|
+
const dropped = [];
|
|
421
|
+
for (const assertion of assertions) {
|
|
422
|
+
const remappedA = resolveIdentityEndpoint(
|
|
423
|
+
assertion.a,
|
|
424
|
+
canonicalOf,
|
|
425
|
+
retypeMap
|
|
426
|
+
);
|
|
427
|
+
const remappedB = resolveIdentityEndpoint(
|
|
428
|
+
assertion.b,
|
|
429
|
+
canonicalOf,
|
|
430
|
+
retypeMap
|
|
431
|
+
);
|
|
432
|
+
if (mergeKeyOf(remappedA) === mergeKeyOf(remappedB)) {
|
|
433
|
+
if (assertion.relation === "different") {
|
|
434
|
+
throw new IdentityMergeConflictError(
|
|
435
|
+
"Node reconciliation collapsed a different-identity assertion onto one survivor.",
|
|
436
|
+
{ details: { assertion, survivor: remappedA } }
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
dropped.push(
|
|
440
|
+
droppedIdentityAssertion(
|
|
441
|
+
assertion,
|
|
442
|
+
REDUNDANT_IDENTITY_ASSERTION_DROP_REASON
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const [a, b] = normalizeIdentityPair(remappedA, remappedB);
|
|
448
|
+
const result = { ...assertion, a, b };
|
|
449
|
+
const stored = storedRowsById.get(assertion.id);
|
|
450
|
+
if (stored !== void 0 && assertionTruthKey(stored) === assertionTruthKey(assertion) && assertionTruthKey(result) !== assertionTruthKey(assertion)) {
|
|
451
|
+
throw new IdentityMergeConflictError(
|
|
452
|
+
"Node reconciliation moved the endpoints of an assertion already committed on the target; committed identity rows cannot be rewritten by a merge.",
|
|
453
|
+
{
|
|
454
|
+
details: { assertionId: assertion.id, stored, remapped: result },
|
|
455
|
+
suggestion: "Exclude the colliding nodes from entity resolution, or manually retract the committed assertion and re-assert the canonical pair under a new id before merging."
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
remapped.push(result);
|
|
460
|
+
}
|
|
461
|
+
const committedIds = new Set(
|
|
462
|
+
remapped.filter((assertion) => {
|
|
463
|
+
const stored = storedRowsById.get(assertion.id);
|
|
464
|
+
return stored !== void 0 && assertionTruthKey(stored) === assertionTruthKey(assertion);
|
|
465
|
+
}).map((assertion) => assertion.id)
|
|
466
|
+
);
|
|
467
|
+
const deduped = dedupeIdentityAssertions(remapped, committedIds);
|
|
468
|
+
assertNoOpposingIdentityRelations(deduped.survivors);
|
|
469
|
+
return {
|
|
470
|
+
assertions: deduped.survivors,
|
|
471
|
+
dropped: [...dropped, ...deduped.dropped]
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function assertIdentityEndpointsNotDeleted(assertions, nodeDeletions, retypeMap) {
|
|
475
|
+
const deletedIdentities = /* @__PURE__ */ new Set();
|
|
476
|
+
for (const key of nodeDeletions.keys()) {
|
|
477
|
+
deletedIdentities.add(key);
|
|
478
|
+
const retyped = retypeMap.get(key);
|
|
479
|
+
if (retyped !== void 0) {
|
|
480
|
+
deletedIdentities.add(mergeKey(retyped, idOf(key)));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
for (const assertion of assertions) {
|
|
484
|
+
for (const endpoint of [assertion.a, assertion.b]) {
|
|
485
|
+
if (!deletedIdentities.has(mergeKeyOf(endpoint))) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
throw new IdentityMergeConflictError(
|
|
489
|
+
"A branch asserted an identity relation over a node another branch deleted.",
|
|
490
|
+
{ details: { assertion, deletedEndpoint: endpoint } }
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function assertNoContradictoryIdentityClosure(plannedAssertions, retractions, baseAssertions, deletedNodes, canonicalOf, retypeMap, identityContext, nodeUniverse) {
|
|
496
|
+
const retracted = new Set(retractions);
|
|
497
|
+
const survivesDeletion = (assertion) => !deletedNodes.has(mergeKey(assertion.a.kind, assertion.a.id)) && !deletedNodes.has(mergeKey(assertion.b.kind, assertion.b.id));
|
|
498
|
+
const mergedLedger = [
|
|
499
|
+
...plannedAssertions,
|
|
500
|
+
...baseAssertions.filter(
|
|
501
|
+
(assertion) => !retracted.has(assertion.id) && survivesDeletion(assertion)
|
|
502
|
+
).map((assertion) => ({
|
|
503
|
+
...assertion,
|
|
504
|
+
a: resolveIdentityEndpoint(assertion.a, canonicalOf, retypeMap),
|
|
505
|
+
b: resolveIdentityEndpoint(assertion.b, canonicalOf, retypeMap)
|
|
506
|
+
}))
|
|
507
|
+
];
|
|
508
|
+
const sameClasses = new UnionFind(
|
|
509
|
+
(left, right) => compareMergeKeys(left, right)
|
|
510
|
+
);
|
|
511
|
+
for (const node of nodeUniverse) {
|
|
512
|
+
sameClasses.add(mergeKey(node.kind, node.id));
|
|
513
|
+
}
|
|
514
|
+
for (const assertion of mergedLedger) {
|
|
515
|
+
if (assertion.relation === "same") {
|
|
516
|
+
sameClasses.union(mergeKeyOf(assertion.a), mergeKeyOf(assertion.b));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const membersOfClass = (root) => sameClasses.members().filter((member) => sameClasses.find(member) === root).map((member) => ({ kind: kindOf(member), id: idOf(member) })).toSorted(
|
|
520
|
+
(left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right))
|
|
521
|
+
);
|
|
522
|
+
const firstDisjointPair = (kinds) => {
|
|
523
|
+
for (const [index, left] of kinds.entries()) {
|
|
524
|
+
for (const right of kinds.slice(index + 1)) {
|
|
525
|
+
if (identityContext.areDisjoint(left, right)) return [left, right];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return void 0;
|
|
529
|
+
};
|
|
530
|
+
const refsById = /* @__PURE__ */ new Map();
|
|
531
|
+
const addRef = (kind, id) => {
|
|
532
|
+
const keys = refsById.get(id) ?? [];
|
|
533
|
+
keys.push(mergeKey(kind, id));
|
|
534
|
+
refsById.set(id, keys);
|
|
535
|
+
};
|
|
536
|
+
for (const node of nodeUniverse) addRef(node.kind, node.id);
|
|
537
|
+
for (const assertion of mergedLedger) {
|
|
538
|
+
addRef(assertion.a.kind, assertion.a.id);
|
|
539
|
+
addRef(assertion.b.kind, assertion.b.id);
|
|
540
|
+
}
|
|
541
|
+
for (const keys of refsById.values()) {
|
|
542
|
+
const disjointKinds = firstDisjointPair([
|
|
543
|
+
...new Set(keys.map((key) => kindOf(key)))
|
|
544
|
+
]);
|
|
545
|
+
if (disjointKinds === void 0) continue;
|
|
546
|
+
throw new IdentityMergeConflictError(
|
|
547
|
+
"The merged graph would give one id to two ontology-disjoint kinds.",
|
|
548
|
+
{
|
|
549
|
+
details: {
|
|
550
|
+
disjointKinds,
|
|
551
|
+
sharedId: idOf(requireDefined(keys[0]))
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
if (identityContext.sameIdAcrossKinds === "fold") {
|
|
557
|
+
for (const keys of refsById.values()) {
|
|
558
|
+
const [first, ...rest] = keys;
|
|
559
|
+
if (first === void 0) continue;
|
|
560
|
+
for (const other of rest) sameClasses.union(first, other);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
for (const assertion of mergedLedger) {
|
|
564
|
+
if (assertion.relation !== "different") {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const root = sameClasses.find(mergeKeyOf(assertion.a));
|
|
568
|
+
if (root !== sameClasses.find(mergeKeyOf(assertion.b))) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
throw new IdentityMergeConflictError(
|
|
572
|
+
"The merged identity ledger would assert one pair of nodes is both the same and different.",
|
|
573
|
+
{ details: { assertion, sameClass: membersOfClass(root) } }
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
const kindsByRoot = /* @__PURE__ */ new Map();
|
|
577
|
+
for (const member of sameClasses.members()) {
|
|
578
|
+
const root = sameClasses.find(member);
|
|
579
|
+
const kinds = kindsByRoot.get(root) ?? /* @__PURE__ */ new Set();
|
|
580
|
+
kinds.add(kindOf(member));
|
|
581
|
+
kindsByRoot.set(root, kinds);
|
|
582
|
+
}
|
|
583
|
+
for (const [root, kinds] of kindsByRoot) {
|
|
584
|
+
const disjointKinds = firstDisjointPair([...kinds]);
|
|
585
|
+
if (disjointKinds === void 0) continue;
|
|
586
|
+
throw new IdentityMergeConflictError(
|
|
587
|
+
"The merged identity ledger would join two ontology-disjoint kinds into one class.",
|
|
588
|
+
{ details: { disjointKinds, sameClass: membersOfClass(root) } }
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function buildIdentityPeerProbe(target, probedIds, targetPeers, plan, profile) {
|
|
593
|
+
const finalIds = /* @__PURE__ */ new Set([
|
|
594
|
+
...plan.canonicalEntities.filter(
|
|
595
|
+
(entity) => !plan.nodeDeletions.has(mergeKey(entity.kind, entity.canonicalId))
|
|
596
|
+
).map((entity) => entity.canonicalId),
|
|
597
|
+
...plan.identityAssertions.flatMap((assertion) => [
|
|
598
|
+
assertion.a.id,
|
|
599
|
+
assertion.b.id
|
|
600
|
+
])
|
|
601
|
+
]);
|
|
602
|
+
const plannedDeletions = new Set(plan.nodeDeletions.keys());
|
|
603
|
+
const relevantPeers = targetPeers.filter(
|
|
604
|
+
(peer) => finalIds.has(peer.id) && !plannedDeletions.has(mergeKey(peer.kind, peer.id))
|
|
605
|
+
);
|
|
606
|
+
const seedsByKey = /* @__PURE__ */ new Map();
|
|
607
|
+
const addSeed = (kind, id) => {
|
|
608
|
+
seedsByKey.set(mergeKey(kind, id), { kind, id });
|
|
609
|
+
};
|
|
610
|
+
for (const entity of plan.canonicalEntities) {
|
|
611
|
+
const sourceKey = mergeKey(entity.kind, entity.canonicalId);
|
|
612
|
+
if (plan.nodeDeletions.has(sourceKey)) continue;
|
|
613
|
+
addSeed(plan.retypeMap.get(sourceKey) ?? entity.kind, entity.canonicalId);
|
|
614
|
+
}
|
|
615
|
+
for (const assertion of plan.identityAssertions) {
|
|
616
|
+
addSeed(assertion.a.kind, assertion.a.id);
|
|
617
|
+
addSeed(assertion.b.kind, assertion.b.id);
|
|
618
|
+
}
|
|
619
|
+
for (const peer of relevantPeers) addSeed(peer.kind, peer.id);
|
|
620
|
+
const seeds = [...seedsByKey.values()];
|
|
621
|
+
const liveKeys = new Set(
|
|
622
|
+
targetPeers.map((peer) => mergeKey(peer.kind, peer.id)).filter((key) => !plannedDeletions.has(key))
|
|
623
|
+
);
|
|
624
|
+
const snapshot = await snapshotIdentityClasses(
|
|
625
|
+
target,
|
|
626
|
+
seeds,
|
|
627
|
+
liveKeys,
|
|
628
|
+
plannedDeletions
|
|
629
|
+
);
|
|
630
|
+
const memberKeys = /* @__PURE__ */ new Set([
|
|
631
|
+
...seeds.map((seed) => mergeKey(seed.kind, seed.id)),
|
|
632
|
+
...snapshot.groups.flatMap(
|
|
633
|
+
(group) => group.map((member) => mergeKey(member.kind, member.id))
|
|
634
|
+
)
|
|
635
|
+
]);
|
|
636
|
+
const ledger = (await relevantLedgerAssertions(target, memberKeys, storeBackend(target))).filter(
|
|
637
|
+
(assertion) => !plannedDeletions.has(mergeKey(assertion.a.kind, assertion.a.id)) && !plannedDeletions.has(mergeKey(assertion.b.kind, assertion.b.id))
|
|
638
|
+
);
|
|
639
|
+
return {
|
|
640
|
+
probe: {
|
|
641
|
+
profile,
|
|
642
|
+
plannedDeletions,
|
|
643
|
+
ids: probedIds.filter((id) => finalIds.has(id)),
|
|
644
|
+
observed: new Set(
|
|
645
|
+
relevantPeers.map((peer) => mergeKey(peer.kind, peer.id))
|
|
646
|
+
),
|
|
647
|
+
memberKeys,
|
|
648
|
+
differentFingerprint: differentLedgerFingerprint(ledger),
|
|
649
|
+
classFingerprints: snapshot.fingerprints,
|
|
650
|
+
seeds
|
|
651
|
+
},
|
|
652
|
+
snapshot,
|
|
653
|
+
ledger
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async function snapshotIdentityClasses(target, seeds, liveKeys, plannedDeletions, txBackend) {
|
|
657
|
+
const classes = await storeRuntime(target).structuralIdentityClasses(
|
|
658
|
+
seeds,
|
|
659
|
+
txBackend
|
|
660
|
+
);
|
|
661
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
662
|
+
const groups = [];
|
|
663
|
+
for (const seed of seeds) {
|
|
664
|
+
const key = mergeKey(seed.kind, seed.id);
|
|
665
|
+
const members = (classes.get(identityReferenceKey(seed)) ?? [seed]).filter(
|
|
666
|
+
(member) => !plannedDeletions.has(mergeKey(member.kind, member.id))
|
|
667
|
+
);
|
|
668
|
+
groups.push(members);
|
|
669
|
+
fingerprints.set(key, encodeClassFingerprint(liveKeys.has(key), members));
|
|
670
|
+
}
|
|
671
|
+
return { fingerprints, groups };
|
|
672
|
+
}
|
|
673
|
+
async function relevantLedgerAssertions(target, memberKeys, backend) {
|
|
674
|
+
const current = await storeRuntime(target).identityAssertionsAtTarget(
|
|
675
|
+
backend,
|
|
676
|
+
"state"
|
|
677
|
+
);
|
|
678
|
+
return current.filter(
|
|
679
|
+
(assertion) => memberKeys.has(mergeKey(assertion.a.kind, assertion.a.id)) || memberKeys.has(mergeKey(assertion.b.kind, assertion.b.id))
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
function differentLedgerFingerprint(assertions) {
|
|
683
|
+
return JSON.stringify(
|
|
684
|
+
assertions.filter((assertion) => assertion.relation === "different").map((assertion) => [assertion.id, ...endpointTuple(assertion)]).toSorted(
|
|
685
|
+
(left, right) => compareStrings(encodeTupleKey(left), encodeTupleKey(right))
|
|
686
|
+
)
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
function encodeClassFingerprint(live, members) {
|
|
690
|
+
return JSON.stringify([
|
|
691
|
+
live,
|
|
692
|
+
members.map((member) => [member.kind, member.id]).toSorted(
|
|
693
|
+
([leftKind, leftId], [rightKind, rightId]) => compareMergeKeys(
|
|
694
|
+
mergeKey(leftKind, leftId),
|
|
695
|
+
mergeKey(rightKind, rightId)
|
|
696
|
+
)
|
|
697
|
+
)
|
|
698
|
+
]);
|
|
699
|
+
}
|
|
700
|
+
function assertionTruthKey(assertion) {
|
|
701
|
+
return JSON.stringify([
|
|
702
|
+
assertion.relation,
|
|
703
|
+
...endpointTuple(assertion),
|
|
704
|
+
assertion.validFrom,
|
|
705
|
+
assertion.validTo ?? null
|
|
706
|
+
]);
|
|
707
|
+
}
|
|
708
|
+
function assertionIdentityKey(assertion) {
|
|
709
|
+
return JSON.stringify([
|
|
710
|
+
assertion.relation,
|
|
711
|
+
...endpointTuple(assertion),
|
|
712
|
+
assertion.validFrom
|
|
713
|
+
]);
|
|
714
|
+
}
|
|
715
|
+
function assertOneIdOneTruth(planned, storedById) {
|
|
716
|
+
const plannedById = /* @__PURE__ */ new Map();
|
|
717
|
+
for (const assertion of planned) {
|
|
718
|
+
const previous = plannedById.get(assertion.id);
|
|
719
|
+
if (previous !== void 0 && assertionTruthKey(previous) !== assertionTruthKey(assertion)) {
|
|
720
|
+
throw new IdentityMergeConflictError(
|
|
721
|
+
"One assertion id was staged for two different identity truths.",
|
|
722
|
+
{
|
|
723
|
+
details: {
|
|
724
|
+
assertionId: assertion.id,
|
|
725
|
+
first: previous,
|
|
726
|
+
second: assertion
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
plannedById.set(assertion.id, assertion);
|
|
732
|
+
const stored = storedById.get(assertion.id);
|
|
733
|
+
if (stored !== void 0 && assertionTruthKey(stored) !== assertionTruthKey(assertion)) {
|
|
734
|
+
throw new IdentityMergeConflictError(
|
|
735
|
+
"A staged assertion id already identifies different truth in the target ledger.",
|
|
736
|
+
{ details: { assertionId: assertion.id, staged: assertion, stored } }
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async function assertPlannedIdentityIdsFresh(target, txBackend, plan) {
|
|
742
|
+
const plannedIds = [
|
|
743
|
+
...plan.identityAssertions.map((assertion) => assertion.id),
|
|
744
|
+
...plan.identityRetractions.map((retraction) => retraction.id)
|
|
745
|
+
];
|
|
746
|
+
if (plannedIds.length === 0) return;
|
|
747
|
+
const storedById = await storeRuntime(target).identityAssertionRowsByIds(
|
|
748
|
+
plannedIds,
|
|
749
|
+
txBackend
|
|
750
|
+
);
|
|
751
|
+
for (const assertion of plan.identityAssertions) {
|
|
752
|
+
const stored = storedById.get(assertion.id);
|
|
753
|
+
if (stored !== void 0 && assertionTruthKey(stored) !== assertionTruthKey(assertion)) {
|
|
754
|
+
throw new BaseVersionMismatchError(
|
|
755
|
+
"The merge validated its assertion ids as of planning, but a row identifying different truth under a planned id was committed before the commit transaction.",
|
|
756
|
+
{
|
|
757
|
+
details: { assertionId: assertion.id },
|
|
758
|
+
suggestion: "Retry the merge \u2014 the plan is recomputed from current state. A persistent refusal means the branch's assertion id conflicts with target truth and needs manual resolution."
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
for (const retraction of plan.identityRetractions) {
|
|
764
|
+
const stored = storedById.get(retraction.id);
|
|
765
|
+
if (stored !== void 0 && stored.validTo === void 0 && assertionIdentityKey(stored) !== assertionIdentityKey(retraction)) {
|
|
766
|
+
throw new BaseVersionMismatchError(
|
|
767
|
+
"The merge validated each planned retraction against the target row its id identified at planning, but that row's truth changed before the commit transaction.",
|
|
768
|
+
{
|
|
769
|
+
details: { assertionId: retraction.id },
|
|
770
|
+
suggestion: "Retry the merge \u2014 the plan is recomputed from current state."
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function affectedIdentityClassSeeds(plan, profile) {
|
|
777
|
+
const seedsByKey = /* @__PURE__ */ new Map();
|
|
778
|
+
const addSeed = (kind, id) => {
|
|
779
|
+
const key = mergeKey(kind, id);
|
|
780
|
+
if (plan.nodeDeletions.has(key)) return;
|
|
781
|
+
seedsByKey.set(key, { kind, id });
|
|
782
|
+
};
|
|
783
|
+
for (const assertion of [
|
|
784
|
+
...plan.identityAssertions,
|
|
785
|
+
...plan.identityRetractions
|
|
786
|
+
]) {
|
|
787
|
+
addSeed(assertion.a.kind, assertion.a.id);
|
|
788
|
+
addSeed(assertion.b.kind, assertion.b.id);
|
|
789
|
+
}
|
|
790
|
+
if (profile === "fold") {
|
|
791
|
+
for (const entity of plan.canonicalEntities) {
|
|
792
|
+
const sourceKey = mergeKey(entity.kind, entity.canonicalId);
|
|
793
|
+
if (plan.nodeDeletions.has(sourceKey)) continue;
|
|
794
|
+
addSeed(plan.retypeMap.get(sourceKey) ?? entity.kind, entity.canonicalId);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return [...seedsByKey.values()];
|
|
798
|
+
}
|
|
799
|
+
async function assertMergedIdentityClassesConsistent(target, txBackend, plan) {
|
|
800
|
+
await storeRuntime(target).assertIdentityClassesConsistentAtTarget(
|
|
801
|
+
txBackend,
|
|
802
|
+
affectedIdentityClassSeeds(plan, target.graph.identity?.sameIdAcrossKinds)
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
var IDENTITY_ENVIRONMENT_CODES = /* @__PURE__ */ new Set([
|
|
806
|
+
"IDENTITY_MERGE_REQUIRES_PROFILE",
|
|
807
|
+
"IDENTITY_REQUIRES_ATOMIC_BACKEND",
|
|
808
|
+
"IDENTITY_REQUIRES_STATEMENT_EXECUTION",
|
|
809
|
+
"IDENTITY_BIND_BUDGET_TOO_SMALL",
|
|
810
|
+
// Environment/corruption statements — translating one into "re-plan against
|
|
811
|
+
// the current target" would be advice that can never succeed. The applier
|
|
812
|
+
// reaches the last two through the current different-ness probe (see
|
|
813
|
+
// `identity/separation`), which refuses rather than guessing when the derived
|
|
814
|
+
// separation relation is missing or disagrees with the ledger.
|
|
815
|
+
"IDENTITY_NOT_ENABLED",
|
|
816
|
+
"IDENTITY_STORAGE_MISSING",
|
|
817
|
+
"IDENTITY_SCHEMA_CONTRADICTION",
|
|
818
|
+
"IDENTITY_IMPORT_REQUIRES_PROFILE"
|
|
819
|
+
]);
|
|
820
|
+
function identityRefusalCode(error) {
|
|
821
|
+
const detailCode = error.details["code"];
|
|
822
|
+
if (typeof detailCode === "string" && detailCode.startsWith("IDENTITY_")) {
|
|
823
|
+
return detailCode;
|
|
824
|
+
}
|
|
825
|
+
if (error.code.startsWith("IDENTITY_")) return error.code;
|
|
826
|
+
const issues = error.details["issues"];
|
|
827
|
+
if (Array.isArray(issues)) {
|
|
828
|
+
for (const issue of issues) {
|
|
829
|
+
const code = issue["code"];
|
|
830
|
+
if (typeof code === "string" && code.startsWith("IDENTITY_")) {
|
|
831
|
+
return code;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return void 0;
|
|
836
|
+
}
|
|
837
|
+
function translateIdentityCommitError(error) {
|
|
838
|
+
if (error instanceof MergeError) return error;
|
|
839
|
+
if (!(error instanceof TypeGraphError)) return error;
|
|
840
|
+
const identityCode = identityRefusalCode(error);
|
|
841
|
+
const identityRefusal = error instanceof IdentityContradictionError || error instanceof NodeNotFoundError || identityCode !== void 0 && !IDENTITY_ENVIRONMENT_CODES.has(identityCode);
|
|
842
|
+
if (!identityRefusal) return error;
|
|
843
|
+
return new IdentityMergeConflictError(
|
|
844
|
+
`The identity applier refused the resolved plan inside the commit transaction \u2014 identity truth on the target is incompatible with the plan. Cause: ${describeCause(error)}`,
|
|
845
|
+
{
|
|
846
|
+
cause: error,
|
|
847
|
+
details: { ...identityCode === void 0 ? {} : { identityCode } },
|
|
848
|
+
suggestion: "Retry once \u2014 the plan is recomputed from current state. If the refusal persists, the branches' identity truth conflicts with the target and needs manual resolution (change the branch's assertion, or resolve the target's truth first)."
|
|
849
|
+
}
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
async function assertIdentityPeersStable(target, txBackend, probe, plan) {
|
|
853
|
+
if (probe === void 0) return;
|
|
854
|
+
const live = (await storeRuntime(target).liveNodesSharingIds(probe.ids, txBackend)).filter((peer) => !probe.plannedDeletions.has(mergeKey(peer.kind, peer.id)));
|
|
855
|
+
const seedKindsById = /* @__PURE__ */ new Map();
|
|
856
|
+
for (const seed of probe.seeds) {
|
|
857
|
+
const kinds = seedKindsById.get(seed.id) ?? /* @__PURE__ */ new Set();
|
|
858
|
+
kinds.add(seed.kind);
|
|
859
|
+
seedKindsById.set(seed.id, kinds);
|
|
860
|
+
}
|
|
861
|
+
const appeared = live.filter(
|
|
862
|
+
(peer) => probe.profile === "fold" || [...seedKindsById.get(peer.id) ?? []].some(
|
|
863
|
+
(kind) => target.registry.areDisjoint(peer.kind, kind)
|
|
864
|
+
)
|
|
865
|
+
).map((peer) => mergeKey(peer.kind, peer.id)).filter((key) => !probe.observed.has(key)).sort((left, right) => compareMergeKeys(left, right));
|
|
866
|
+
if (appeared.length > 0) {
|
|
867
|
+
throw new BaseVersionMismatchError(
|
|
868
|
+
`mergeIncremental() simulated identity folding against the target's live same-id peers as of planning, but ${appeared.length} committed row(s) sharing a planned node's id appeared before the commit transaction. Committing the plan could fold nodes into classes the plan never validated.`,
|
|
869
|
+
{ details: { appeared } }
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
const { fingerprints: liveFingerprints, groups: liveGroups } = await snapshotIdentityClasses(
|
|
873
|
+
target,
|
|
874
|
+
probe.seeds,
|
|
875
|
+
new Set(live.map((peer) => mergeKey(peer.kind, peer.id))),
|
|
876
|
+
probe.plannedDeletions,
|
|
877
|
+
txBackend
|
|
878
|
+
);
|
|
879
|
+
const drifted = [...probe.classFingerprints].filter(([key, fingerprint]) => liveFingerprints.get(key) !== fingerprint).map(([key]) => key).sort((left, right) => compareStrings(left, right));
|
|
880
|
+
if (drifted.length > 0) {
|
|
881
|
+
throw new BaseVersionMismatchError(
|
|
882
|
+
`mergeIncremental() validated identity against the classes visible at planning, but the identity class of ${drifted.length} node(s) the plan folds on changed before the commit transaction. Committing the plan could contradict identity truth it never validated.`,
|
|
883
|
+
{ details: { drifted } }
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
const ledger = (await relevantLedgerAssertions(target, probe.memberKeys, txBackend)).filter(
|
|
887
|
+
(assertion) => !probe.plannedDeletions.has(mergeKey(assertion.a.kind, assertion.a.id)) && !probe.plannedDeletions.has(mergeKey(assertion.b.kind, assertion.b.id))
|
|
888
|
+
);
|
|
889
|
+
if (differentLedgerFingerprint(ledger) !== probe.differentFingerprint) {
|
|
890
|
+
throw new BaseVersionMismatchError(
|
|
891
|
+
"mergeIncremental() validated identity against the `different` assertions visible at planning, but that ledger changed before the commit transaction. Committing the plan could contradict identity truth it never validated.",
|
|
892
|
+
{ details: {} }
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
assertNoContradictoryIdentityClosure(
|
|
897
|
+
plan.identityAssertions,
|
|
898
|
+
plan.identityRetractions.map((retraction) => retraction.id),
|
|
899
|
+
ledger,
|
|
900
|
+
probe.plannedDeletions,
|
|
901
|
+
plan.canonicalOf,
|
|
902
|
+
plan.retypeMap,
|
|
903
|
+
{
|
|
904
|
+
sameIdAcrossKinds: probe.profile,
|
|
905
|
+
areDisjoint: (left, right) => target.registry.areDisjoint(left, right)
|
|
906
|
+
},
|
|
907
|
+
[...probe.seeds, ...liveGroups.flat()]
|
|
908
|
+
);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (!(error instanceof IdentityMergeConflictError)) throw error;
|
|
911
|
+
throw new BaseVersionMismatchError(
|
|
912
|
+
"mergeIncremental() validated identity as of planning, but identity truth committed before the commit transaction invalidates the plan. Re-plan against the current target.",
|
|
913
|
+
{ details: { conflict: error.message } }
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
104
918
|
// src/graph-merge/state-diff.ts
|
|
105
919
|
var ENUMERATION_PAGE_SIZE = 1e3;
|
|
920
|
+
var EXPLICIT_RETRACTION = { kind: "explicit" };
|
|
921
|
+
function validWindowOf(row) {
|
|
922
|
+
return {
|
|
923
|
+
validFrom: canonicalizeDatabaseTimestamp(row.valid_from),
|
|
924
|
+
validTo: canonicalizeDatabaseTimestamp(row.valid_to)
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function windowsDiffer(left, right) {
|
|
928
|
+
return left.validFrom !== right.validFrom || left.validTo !== right.validTo;
|
|
929
|
+
}
|
|
106
930
|
async function enumerateAllNodes(backend, graphId, kind) {
|
|
107
931
|
const collected = [];
|
|
108
932
|
let after;
|
|
@@ -163,6 +987,7 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
163
987
|
const created = [];
|
|
164
988
|
const modified = [];
|
|
165
989
|
const deleted = [];
|
|
990
|
+
const windowed = [];
|
|
166
991
|
for (const forkRow of forkRows) {
|
|
167
992
|
if (!isLive(forkRow)) {
|
|
168
993
|
continue;
|
|
@@ -188,6 +1013,16 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
188
1013
|
row: forkRow
|
|
189
1014
|
});
|
|
190
1015
|
}
|
|
1016
|
+
const baseWindow = validWindowOf(baseRow);
|
|
1017
|
+
const forkWindow = validWindowOf(forkRow);
|
|
1018
|
+
if (windowsDiffer(baseWindow, forkWindow)) {
|
|
1019
|
+
windowed.push({
|
|
1020
|
+
id: forkRow.id,
|
|
1021
|
+
kind,
|
|
1022
|
+
base: baseWindow,
|
|
1023
|
+
fork: forkWindow
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
191
1026
|
}
|
|
192
1027
|
for (const baseRow of baseRows) {
|
|
193
1028
|
if (!isLive(baseRow)) {
|
|
@@ -195,10 +1030,14 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
195
1030
|
}
|
|
196
1031
|
const forkRow = forkIndex.get(baseRow.id);
|
|
197
1032
|
if (forkRow === void 0 || !isLive(forkRow)) {
|
|
198
|
-
deleted.push({
|
|
1033
|
+
deleted.push({
|
|
1034
|
+
id: baseRow.id,
|
|
1035
|
+
kind,
|
|
1036
|
+
deletedAt: forkRow === void 0 ? void 0 : canonicalizeDatabaseTimestamp(forkRow.deleted_at)
|
|
1037
|
+
});
|
|
199
1038
|
}
|
|
200
1039
|
}
|
|
201
|
-
return { new: created, modified, deleted };
|
|
1040
|
+
return { new: created, modified, deleted, windowed };
|
|
202
1041
|
}
|
|
203
1042
|
function diffEdgeKind(kind, baseRows, forkRows) {
|
|
204
1043
|
const baseIndex = indexById(baseRows);
|
|
@@ -206,6 +1045,7 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
206
1045
|
const created = [];
|
|
207
1046
|
const modified = [];
|
|
208
1047
|
const deleted = [];
|
|
1048
|
+
const windowed = [];
|
|
209
1049
|
for (const forkRow of forkRows) {
|
|
210
1050
|
if (!isLive(forkRow)) {
|
|
211
1051
|
continue;
|
|
@@ -239,6 +1079,22 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
239
1079
|
row: forkRow
|
|
240
1080
|
});
|
|
241
1081
|
}
|
|
1082
|
+
const baseWindow = validWindowOf(baseRow);
|
|
1083
|
+
const forkWindow = validWindowOf(forkRow);
|
|
1084
|
+
if (windowsDiffer(baseWindow, forkWindow)) {
|
|
1085
|
+
windowed.push({
|
|
1086
|
+
id: forkRow.id,
|
|
1087
|
+
kind,
|
|
1088
|
+
fromId: forkRow.from_id,
|
|
1089
|
+
toId: forkRow.to_id,
|
|
1090
|
+
fromKind: forkRow.from_kind,
|
|
1091
|
+
toKind: forkRow.to_kind,
|
|
1092
|
+
props: forkProps,
|
|
1093
|
+
baseProps,
|
|
1094
|
+
base: baseWindow,
|
|
1095
|
+
fork: forkWindow
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
242
1098
|
}
|
|
243
1099
|
for (const baseRow of baseRows) {
|
|
244
1100
|
if (!isLive(baseRow)) {
|
|
@@ -249,7 +1105,51 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
249
1105
|
deleted.push({ id: baseRow.id, kind });
|
|
250
1106
|
}
|
|
251
1107
|
}
|
|
252
|
-
return { new: created, modified, deleted };
|
|
1108
|
+
return { new: created, modified, deleted, windowed };
|
|
1109
|
+
}
|
|
1110
|
+
function classifyRetraction(assertion, deletedByKey, forkAssertion) {
|
|
1111
|
+
if (forkAssertion !== void 0) {
|
|
1112
|
+
const stamp = forkAssertion.endedBy;
|
|
1113
|
+
if (stamp !== void 0 && deletedByKey.has(mergeKey(stamp.kind, stamp.id))) {
|
|
1114
|
+
return { kind: "cascade", deletedNode: { ...stamp } };
|
|
1115
|
+
}
|
|
1116
|
+
return EXPLICIT_RETRACTION;
|
|
1117
|
+
}
|
|
1118
|
+
for (const endpoint of [assertion.a, assertion.b]) {
|
|
1119
|
+
const deletion = deletedByKey.get(mergeKey(endpoint.kind, endpoint.id));
|
|
1120
|
+
if (deletion !== void 0 && deletion.deletedAt === void 0) {
|
|
1121
|
+
return {
|
|
1122
|
+
kind: "cascade",
|
|
1123
|
+
deletedNode: { kind: deletion.kind, id: deletion.id }
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return EXPLICIT_RETRACTION;
|
|
1128
|
+
}
|
|
1129
|
+
async function classifyRetractions(forkStore, retracted, deletedNodes) {
|
|
1130
|
+
if (retracted.length === 0 || deletedNodes.length === 0) {
|
|
1131
|
+
return retracted.map((assertion) => ({
|
|
1132
|
+
assertion,
|
|
1133
|
+
cause: EXPLICIT_RETRACTION
|
|
1134
|
+
}));
|
|
1135
|
+
}
|
|
1136
|
+
const deletedByKey = new Map(
|
|
1137
|
+
deletedNodes.map((deletion) => [
|
|
1138
|
+
mergeKey(deletion.kind, deletion.id),
|
|
1139
|
+
deletion
|
|
1140
|
+
])
|
|
1141
|
+
);
|
|
1142
|
+
const forkRowsById = await storeRuntime(forkStore).identityAssertionRowsByIds(
|
|
1143
|
+
retracted.map((assertion) => assertion.id)
|
|
1144
|
+
);
|
|
1145
|
+
return retracted.map((assertion) => ({
|
|
1146
|
+
assertion,
|
|
1147
|
+
cause: classifyRetraction(
|
|
1148
|
+
assertion,
|
|
1149
|
+
deletedByKey,
|
|
1150
|
+
forkRowsById.get(assertion.id)
|
|
1151
|
+
)
|
|
1152
|
+
}));
|
|
253
1153
|
}
|
|
254
1154
|
function byId(left, right) {
|
|
255
1155
|
return compareStrings(left.id, right.id);
|
|
@@ -258,9 +1158,20 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
258
1158
|
const graph = baseStore.graph;
|
|
259
1159
|
const nodeKinds = getNodeKinds(graph);
|
|
260
1160
|
const edgeKinds = getEdgeKinds(graph);
|
|
1161
|
+
const [baseIdentity, forkIdentity] = await Promise.all([
|
|
1162
|
+
storeRuntime(baseStore).readCurrentIdentityAssertions("state"),
|
|
1163
|
+
storeRuntime(forkStore).readCurrentIdentityAssertions("state")
|
|
1164
|
+
]);
|
|
1165
|
+
const baseIdentityById = new Map(
|
|
1166
|
+
baseIdentity.map((assertion) => [assertion.id, assertion])
|
|
1167
|
+
);
|
|
1168
|
+
const forkIdentityById = new Map(
|
|
1169
|
+
forkIdentity.map((assertion) => [assertion.id, assertion])
|
|
1170
|
+
);
|
|
261
1171
|
const newNodes = [];
|
|
262
1172
|
const modifiedNodes = [];
|
|
263
1173
|
const deletedNodes = [];
|
|
1174
|
+
const windowedNodes = [];
|
|
264
1175
|
const forkNodeVersions = /* @__PURE__ */ new Map();
|
|
265
1176
|
for (const kind of nodeKinds) {
|
|
266
1177
|
const baseRows = await enumerateAllNodes(
|
|
@@ -288,10 +1199,14 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
288
1199
|
for (const entry of delta.deleted) {
|
|
289
1200
|
deletedNodes.push(entry);
|
|
290
1201
|
}
|
|
1202
|
+
for (const entry of delta.windowed) {
|
|
1203
|
+
windowedNodes.push(entry);
|
|
1204
|
+
}
|
|
291
1205
|
}
|
|
292
1206
|
const newEdges = [];
|
|
293
1207
|
const modifiedEdges = [];
|
|
294
1208
|
const deletedEdges = [];
|
|
1209
|
+
const windowedEdges = [];
|
|
295
1210
|
const forkEdgeSignatures = /* @__PURE__ */ new Map();
|
|
296
1211
|
for (const kind of edgeKinds) {
|
|
297
1212
|
const baseRows = await enumerateAllEdges(
|
|
@@ -329,17 +1244,46 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
329
1244
|
for (const entry of delta.deleted) {
|
|
330
1245
|
deletedEdges.push(entry);
|
|
331
1246
|
}
|
|
1247
|
+
for (const entry of delta.windowed) {
|
|
1248
|
+
windowedEdges.push(entry);
|
|
1249
|
+
}
|
|
332
1250
|
}
|
|
1251
|
+
const retractedAssertions = baseIdentity.filter((assertion) => {
|
|
1252
|
+
const fork = forkIdentityById.get(assertion.id);
|
|
1253
|
+
return fork === void 0 || assertionTruthKey(fork) !== assertionTruthKey(assertion);
|
|
1254
|
+
}).toSorted((left, right) => compareStrings(left.id, right.id));
|
|
1255
|
+
const classifiedRetractions = await classifyRetractions(
|
|
1256
|
+
forkStore,
|
|
1257
|
+
retractedAssertions,
|
|
1258
|
+
deletedNodes
|
|
1259
|
+
);
|
|
333
1260
|
return {
|
|
334
1261
|
nodes: {
|
|
335
1262
|
new: newNodes.sort((left, right) => byId(left, right)),
|
|
336
1263
|
modified: modifiedNodes.sort((left, right) => byId(left, right)),
|
|
337
|
-
deleted: deletedNodes.sort((left, right) => byId(left, right))
|
|
1264
|
+
deleted: deletedNodes.sort((left, right) => byId(left, right)),
|
|
1265
|
+
windowed: windowedNodes.sort((left, right) => byId(left, right))
|
|
338
1266
|
},
|
|
339
1267
|
edges: {
|
|
340
1268
|
new: newEdges.sort((left, right) => byId(left, right)),
|
|
341
1269
|
modified: modifiedEdges.sort((left, right) => byId(left, right)),
|
|
342
|
-
deleted: deletedEdges.sort((left, right) => byId(left, right))
|
|
1270
|
+
deleted: deletedEdges.sort((left, right) => byId(left, right)),
|
|
1271
|
+
windowed: windowedEdges.sort((left, right) => byId(left, right))
|
|
1272
|
+
},
|
|
1273
|
+
identity: {
|
|
1274
|
+
// Ids present on BOTH sides are compared by COMPLETE truth, not
|
|
1275
|
+
// presence: a fork can hard-delete an assertion's endpoint (physically
|
|
1276
|
+
// removing the row), recreate it, and legally import the same id for
|
|
1277
|
+
// different truth. Presence-only comparison would diff that replacement
|
|
1278
|
+
// as empty and the merge would silently keep the base truth. A shared
|
|
1279
|
+
// id with changed truth surfaces as BOTH retracted (the base row) and
|
|
1280
|
+
// new (the fork row), so planning sees the replacement and can refuse
|
|
1281
|
+
// unsupported id reuse as a typed conflict.
|
|
1282
|
+
new: forkIdentity.filter((assertion) => {
|
|
1283
|
+
const base = baseIdentityById.get(assertion.id);
|
|
1284
|
+
return base === void 0 || assertionTruthKey(base) !== assertionTruthKey(assertion);
|
|
1285
|
+
}).toSorted((left, right) => compareStrings(left.id, right.id)),
|
|
1286
|
+
retracted: classifiedRetractions
|
|
343
1287
|
},
|
|
344
1288
|
forkNodeVersions,
|
|
345
1289
|
forkEdgeSignatures
|
|
@@ -353,9 +1297,11 @@ function asBranchId(value) {
|
|
|
353
1297
|
function asBaseVersion(value) {
|
|
354
1298
|
return value;
|
|
355
1299
|
}
|
|
1300
|
+
var VALIDITY_END_TARGET_PRECEDENCE = "target";
|
|
356
1301
|
|
|
357
1302
|
// src/graph-merge/base-version.ts
|
|
358
1303
|
var TOKEN_SEPARATOR = "\0";
|
|
1304
|
+
var SCHEMA_VERSION_TAG = "#s";
|
|
359
1305
|
var REVISION_COMPONENT_PREFIX = "revision:";
|
|
360
1306
|
var REVISION_COMPONENT_SEPARATOR = ":";
|
|
361
1307
|
var INITIAL_REVISION = "initial";
|
|
@@ -376,7 +1322,7 @@ function byDigestEntry(left, right) {
|
|
|
376
1322
|
return byKind === 0 ? compareStrings(left.id, right.id) : byKind;
|
|
377
1323
|
}
|
|
378
1324
|
var CONTENT_FINGERPRINT_BYTES = 16;
|
|
379
|
-
async function computeContentComponent(backend, graphId, graph) {
|
|
1325
|
+
async function computeContentComponent(backend, graphId, graph, identityAssertions) {
|
|
380
1326
|
const nodeKinds = getNodeKinds(graph);
|
|
381
1327
|
const edgeKinds = getEdgeKinds(graph);
|
|
382
1328
|
const nodeDigest = [];
|
|
@@ -416,30 +1362,41 @@ async function computeContentComponent(backend, graphId, graph) {
|
|
|
416
1362
|
return sha256Hex(
|
|
417
1363
|
canonicalizeProps({
|
|
418
1364
|
nodes: nodeDigest.sort((left, right) => byDigestEntry(left, right)),
|
|
419
|
-
edges: edgeDigest.sort((left, right) => byDigestEntry(left, right))
|
|
1365
|
+
edges: edgeDigest.sort((left, right) => byDigestEntry(left, right)),
|
|
1366
|
+
...identityAssertions.length === 0 ? {} : { identity: identityAssertions }
|
|
420
1367
|
}),
|
|
421
1368
|
CONTENT_FINGERPRINT_BYTES
|
|
422
1369
|
);
|
|
423
1370
|
}
|
|
424
1371
|
async function computeBaseVersion(store) {
|
|
425
1372
|
if (store.revisionTrackingEnabled) {
|
|
426
|
-
const [schemaComponent2, origin, revision] = await Promise.all([
|
|
1373
|
+
const [schemaComponent2, origin, revision, activeVersion] = await Promise.all([
|
|
427
1374
|
computeSchemaComponent(store),
|
|
428
1375
|
store.revisionOriginNow(),
|
|
429
|
-
store.revisionNow()
|
|
1376
|
+
store.revisionNow(),
|
|
1377
|
+
readActiveSchemaVersion(storeBackend(store), store.graphId)
|
|
430
1378
|
]);
|
|
431
1379
|
return asBaseVersion(
|
|
432
|
-
`${schemaComponent2}${TOKEN_SEPARATOR}${revisionComponent(origin, revision)}`
|
|
1380
|
+
`${schemaComponent2}${SCHEMA_VERSION_TAG}${activeVersion}${TOKEN_SEPARATOR}${revisionComponent(origin, revision)}`
|
|
433
1381
|
);
|
|
434
1382
|
}
|
|
435
1383
|
const [schemaComponent, contentComponent] = await Promise.all([
|
|
436
1384
|
computeSchemaComponent(store),
|
|
437
|
-
|
|
1385
|
+
computeStoreContentComponent(store)
|
|
438
1386
|
]);
|
|
439
1387
|
return asBaseVersion(
|
|
440
1388
|
`${schemaComponent}${TOKEN_SEPARATOR}${contentComponent}`
|
|
441
1389
|
);
|
|
442
1390
|
}
|
|
1391
|
+
async function computeStoreContentComponent(store) {
|
|
1392
|
+
const identityAssertions = await storeRuntime(store).readCurrentIdentityAssertions("state");
|
|
1393
|
+
return computeContentComponent(
|
|
1394
|
+
storeBackend(store),
|
|
1395
|
+
store.graphId,
|
|
1396
|
+
store.graph,
|
|
1397
|
+
identityAssertions
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
443
1400
|
function revisionComponent(origin, revision) {
|
|
444
1401
|
return `${REVISION_COMPONENT_PREFIX}${origin}${REVISION_COMPONENT_SEPARATOR}${revision ?? INITIAL_REVISION}`;
|
|
445
1402
|
}
|
|
@@ -469,70 +1426,6 @@ function contentComponentOf(version) {
|
|
|
469
1426
|
return separatorIndex === -1 ? version : version.slice(separatorIndex + 1);
|
|
470
1427
|
}
|
|
471
1428
|
|
|
472
|
-
// src/graph-merge/errors.ts
|
|
473
|
-
var MERGE_ERROR_CODES = {
|
|
474
|
-
merge: "GRAPH_MERGE_ERROR",
|
|
475
|
-
branch: "GRAPH_MERGE_BRANCH_ERROR",
|
|
476
|
-
similarityUnavailable: "GRAPH_MERGE_SIMILARITY_UNAVAILABLE",
|
|
477
|
-
conflict: "GRAPH_MERGE_CONFLICT",
|
|
478
|
-
baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH"
|
|
479
|
-
};
|
|
480
|
-
function toTypeGraphErrorOptions(category, options) {
|
|
481
|
-
return {
|
|
482
|
-
category,
|
|
483
|
-
...options.details === void 0 ? {} : { details: options.details },
|
|
484
|
-
...options.suggestion === void 0 ? {} : { suggestion: options.suggestion },
|
|
485
|
-
...options.cause === void 0 ? {} : { cause: options.cause }
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
var MergeError = class extends TypeGraphError {
|
|
489
|
-
constructor(message, options = {}) {
|
|
490
|
-
super(
|
|
491
|
-
message,
|
|
492
|
-
MERGE_ERROR_CODES.merge,
|
|
493
|
-
toTypeGraphErrorOptions("system", options)
|
|
494
|
-
);
|
|
495
|
-
this.name = "MergeError";
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
var BranchError = class extends TypeGraphError {
|
|
499
|
-
constructor(message, options = {}) {
|
|
500
|
-
super(
|
|
501
|
-
message,
|
|
502
|
-
MERGE_ERROR_CODES.branch,
|
|
503
|
-
toTypeGraphErrorOptions("system", options)
|
|
504
|
-
);
|
|
505
|
-
this.name = "BranchError";
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
var SimilarityUnavailableError = class extends MergeError {
|
|
509
|
-
code = MERGE_ERROR_CODES.similarityUnavailable;
|
|
510
|
-
constructor(message, options = {}) {
|
|
511
|
-
super(message, {
|
|
512
|
-
...options,
|
|
513
|
-
suggestion: options.suggestion ?? "Pass MergeOptions.embedder (a local model), or use a fulltext/custom similarity strategy."
|
|
514
|
-
});
|
|
515
|
-
this.name = "SimilarityUnavailableError";
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
var MergeConflictError = class extends MergeError {
|
|
519
|
-
code = MERGE_ERROR_CODES.conflict;
|
|
520
|
-
constructor(message, options = {}) {
|
|
521
|
-
super(message, options);
|
|
522
|
-
this.name = "MergeConflictError";
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
var BaseVersionMismatchError = class extends MergeError {
|
|
526
|
-
code = MERGE_ERROR_CODES.baseVersionMismatch;
|
|
527
|
-
constructor(message, options = {}) {
|
|
528
|
-
super(message, {
|
|
529
|
-
...options,
|
|
530
|
-
suggestion: options.suggestion ?? "Re-branch from the current target so the branch base matches before merging."
|
|
531
|
-
});
|
|
532
|
-
this.name = "BaseVersionMismatchError";
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
|
|
536
1429
|
// src/graph-merge/working-copy.ts
|
|
537
1430
|
var CLONE_IMPORT_BATCH_SIZE = 1e3;
|
|
538
1431
|
function cloneWorkingCopyStrategy(makeBackend) {
|
|
@@ -549,20 +1442,29 @@ function cloneWorkingCopyStrategy(makeBackend) {
|
|
|
549
1442
|
revisionTracking: baseStore.revisionTrackingEnabled
|
|
550
1443
|
}
|
|
551
1444
|
);
|
|
552
|
-
const
|
|
1445
|
+
const exportOptions = {
|
|
1446
|
+
includeMeta: true,
|
|
1447
|
+
includeTemporal: true,
|
|
1448
|
+
includeDeleted: false,
|
|
1449
|
+
batchSize: CLONE_IMPORT_BATCH_SIZE
|
|
1450
|
+
};
|
|
1451
|
+
const importOptions = {
|
|
1452
|
+
onConflict: "error",
|
|
1453
|
+
onUnknownProperty: "error",
|
|
1454
|
+
validateReferences: true,
|
|
1455
|
+
batchSize: CLONE_IMPORT_BATCH_SIZE
|
|
1456
|
+
};
|
|
1457
|
+
const result = snapshotExportContention(
|
|
1458
|
+
storeBackend(baseStore),
|
|
1459
|
+
storeBackend(freshStore)
|
|
1460
|
+
) === void 0 ? await importGraphStream(
|
|
553
1461
|
freshStore,
|
|
554
|
-
exportGraphStream(baseStore,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
{
|
|
561
|
-
onConflict: "error",
|
|
562
|
-
onUnknownProperty: "error",
|
|
563
|
-
validateReferences: true,
|
|
564
|
-
batchSize: CLONE_IMPORT_BATCH_SIZE
|
|
565
|
-
}
|
|
1462
|
+
exportGraphStream(baseStore, exportOptions),
|
|
1463
|
+
importOptions
|
|
1464
|
+
) : await importGraph(
|
|
1465
|
+
freshStore,
|
|
1466
|
+
await exportGraph(baseStore, exportOptions),
|
|
1467
|
+
importOptions
|
|
566
1468
|
);
|
|
567
1469
|
if (!result.success) {
|
|
568
1470
|
throw new BranchError(
|
|
@@ -589,7 +1491,13 @@ async function branch(baseStore, makeBackend, options, strategy) {
|
|
|
589
1491
|
const id = options?.id ?? asBranchId(generateId());
|
|
590
1492
|
const workingCopyStrategy = strategy ?? cloneWorkingCopyStrategy(makeBackend);
|
|
591
1493
|
const store = await workingCopyStrategy.create(baseStore);
|
|
592
|
-
|
|
1494
|
+
const schemaAnchor = await readSchemaAnchor(store);
|
|
1495
|
+
return ok({
|
|
1496
|
+
id,
|
|
1497
|
+
base,
|
|
1498
|
+
store,
|
|
1499
|
+
...schemaAnchor === void 0 ? { schemaAnchor: void 0 } : { schemaAnchor }
|
|
1500
|
+
});
|
|
593
1501
|
} catch (error) {
|
|
594
1502
|
return err(
|
|
595
1503
|
new BranchError("Failed to create working-copy branch of base store", {
|
|
@@ -598,6 +1506,18 @@ async function branch(baseStore, makeBackend, options, strategy) {
|
|
|
598
1506
|
);
|
|
599
1507
|
}
|
|
600
1508
|
}
|
|
1509
|
+
async function readSchemaAnchor(store) {
|
|
1510
|
+
try {
|
|
1511
|
+
const schemaRow = await storeBackend(store).getActiveSchema(store.graphId);
|
|
1512
|
+
return schemaRow === void 0 ? void 0 : { version: schemaRow.version, hash: schemaRow.schema_hash };
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
try {
|
|
1515
|
+
await storeBackend(store).close();
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
throw error;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
601
1521
|
|
|
602
1522
|
// src/graph-merge/blocking.ts
|
|
603
1523
|
var UNBLOCKED_BUCKET_KEY = "unblocked";
|
|
@@ -610,7 +1530,7 @@ function isUniqueBucketKey(key) {
|
|
|
610
1530
|
function constraintSignature(node, constraint) {
|
|
611
1531
|
const props = node;
|
|
612
1532
|
for (const field of constraint.fields) {
|
|
613
|
-
const value = props
|
|
1533
|
+
const value = readOwnProperty(props, field);
|
|
614
1534
|
if (value === void 0 || value === null) {
|
|
615
1535
|
return void 0;
|
|
616
1536
|
}
|
|
@@ -734,7 +1654,7 @@ function collectConflictingValues(property, contributions) {
|
|
|
734
1654
|
const seen = /* @__PURE__ */ new Set();
|
|
735
1655
|
const values = [];
|
|
736
1656
|
for (const { branchId, props } of contributions) {
|
|
737
|
-
if (!(property
|
|
1657
|
+
if (!hasOwnKey(props, property)) {
|
|
738
1658
|
continue;
|
|
739
1659
|
}
|
|
740
1660
|
const value = props[property];
|
|
@@ -807,6 +1727,9 @@ function resolvePropertyUnion(input, context, branchRank) {
|
|
|
807
1727
|
|
|
808
1728
|
// src/graph-merge/canonicalize.ts
|
|
809
1729
|
var BASE_PROVENANCE_BRANCH = asBranchId("__committed_base__");
|
|
1730
|
+
var COMMITTED_TARGET_BRANCH = asBranchId(
|
|
1731
|
+
"__committed_target__"
|
|
1732
|
+
);
|
|
810
1733
|
function pickClusterSurvivor(members, cluster, canonicalOverride, preferKind) {
|
|
811
1734
|
const byKey = (left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right));
|
|
812
1735
|
const baseMembers = members.filter((member) => member.origin === "base").sort(byKey);
|
|
@@ -839,11 +1762,11 @@ function findPreferredMember(members, property, preferredBranchId) {
|
|
|
839
1762
|
return void 0;
|
|
840
1763
|
}
|
|
841
1764
|
return members.find(
|
|
842
|
-
(member) => member.branchId === preferredBranchId &&
|
|
1765
|
+
(member) => member.branchId === preferredBranchId && hasOwnKey(member.props, property)
|
|
843
1766
|
);
|
|
844
1767
|
}
|
|
845
1768
|
function memberPropertyValue(member, property) {
|
|
846
|
-
if (!(
|
|
1769
|
+
if (!hasOwnKey(member.props, property)) {
|
|
847
1770
|
return void 0;
|
|
848
1771
|
}
|
|
849
1772
|
return member.props[property];
|
|
@@ -871,7 +1794,7 @@ function unionProperties(canonicalMember, members, context, baseContext, branchR
|
|
|
871
1794
|
propertyNames.add(name);
|
|
872
1795
|
}
|
|
873
1796
|
}
|
|
874
|
-
const props =
|
|
1797
|
+
const props = createDataKeyedBag();
|
|
875
1798
|
const conflicts = [];
|
|
876
1799
|
for (const property of [...propertyNames].sort(
|
|
877
1800
|
(left, right) => compareStrings(left, right)
|
|
@@ -888,7 +1811,7 @@ function unionProperties(canonicalMember, members, context, baseContext, branchR
|
|
|
888
1811
|
preferredBranchId
|
|
889
1812
|
);
|
|
890
1813
|
const baseInvolved = hasBaseMember && members.some(
|
|
891
|
-
(member) => member.origin === "base" &&
|
|
1814
|
+
(member) => member.origin === "base" && hasOwnKey(member.props, property)
|
|
892
1815
|
);
|
|
893
1816
|
const reportedValues = values.filter(
|
|
894
1817
|
(value2) => value2.branchId !== BASE_PROVENANCE_BRANCH && value2.branchId !== preferredBranchId
|
|
@@ -929,89 +1852,56 @@ function canonicalizeCluster(cluster, members, policy, branchRank, weights, cano
|
|
|
929
1852
|
policy: basePolicy,
|
|
930
1853
|
...weights === void 0 ? {} : { weights }
|
|
931
1854
|
};
|
|
932
|
-
const { props, conflicts } = unionProperties(
|
|
933
|
-
survivor,
|
|
934
|
-
members,
|
|
935
|
-
context,
|
|
936
|
-
baseContext,
|
|
937
|
-
branchRank,
|
|
938
|
-
preferredBranchId
|
|
939
|
-
);
|
|
940
|
-
const memberIds = [
|
|
941
|
-
...new Set(cluster.members.map((member) => idOf(member)))
|
|
942
|
-
].sort((left, right) => compareStrings(left, right));
|
|
943
|
-
const branchOrigins = [
|
|
944
|
-
...new Set(
|
|
945
|
-
members.map((member) => member.branchId).filter(
|
|
946
|
-
(branchId) => branchId !== BASE_PROVENANCE_BRANCH && branchId !== preferredBranchId
|
|
947
|
-
)
|
|
948
|
-
)
|
|
949
|
-
].sort((left, right) => compareStrings(left, right));
|
|
950
|
-
const resolution = {
|
|
951
|
-
canonicalId,
|
|
952
|
-
memberIds,
|
|
953
|
-
kind,
|
|
954
|
-
branchOrigins
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
this.parent.set(value, value);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
/** Returns the representative of `value`'s set, compressing the path to it. */
|
|
973
|
-
find(value) {
|
|
974
|
-
this.add(value);
|
|
975
|
-
let root = value;
|
|
976
|
-
while (this.parent.get(root) !== root) {
|
|
977
|
-
root = requireDefined(this.parent.get(root));
|
|
978
|
-
}
|
|
979
|
-
let cursor = value;
|
|
980
|
-
while (cursor !== root) {
|
|
981
|
-
const next = requireDefined(this.parent.get(cursor));
|
|
982
|
-
this.parent.set(cursor, root);
|
|
983
|
-
cursor = next;
|
|
984
|
-
}
|
|
985
|
-
return root;
|
|
986
|
-
}
|
|
987
|
-
/** Merges the sets of `left` and `right`; the `compare`-minimal root wins. */
|
|
988
|
-
union(left, right) {
|
|
989
|
-
const leftRoot = this.find(left);
|
|
990
|
-
const rightRoot = this.find(right);
|
|
991
|
-
if (leftRoot === rightRoot) {
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (this.compare(leftRoot, rightRoot) <= 0) {
|
|
995
|
-
this.parent.set(rightRoot, leftRoot);
|
|
996
|
-
} else {
|
|
997
|
-
this.parent.set(leftRoot, rightRoot);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
/** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
|
|
1001
|
-
members() {
|
|
1002
|
-
return [...this.parent.keys()];
|
|
1003
|
-
}
|
|
1004
|
-
};
|
|
1855
|
+
const { props, conflicts } = unionProperties(
|
|
1856
|
+
survivor,
|
|
1857
|
+
members,
|
|
1858
|
+
context,
|
|
1859
|
+
baseContext,
|
|
1860
|
+
branchRank,
|
|
1861
|
+
preferredBranchId
|
|
1862
|
+
);
|
|
1863
|
+
const memberIds = [
|
|
1864
|
+
...new Set(cluster.members.map((member) => idOf(member)))
|
|
1865
|
+
].sort((left, right) => compareStrings(left, right));
|
|
1866
|
+
const branchOrigins = [
|
|
1867
|
+
...new Set(
|
|
1868
|
+
members.map((member) => member.branchId).filter(
|
|
1869
|
+
(branchId) => branchId !== BASE_PROVENANCE_BRANCH && branchId !== preferredBranchId
|
|
1870
|
+
)
|
|
1871
|
+
)
|
|
1872
|
+
].sort((left, right) => compareStrings(left, right));
|
|
1873
|
+
const resolution = {
|
|
1874
|
+
canonicalId,
|
|
1875
|
+
memberIds,
|
|
1876
|
+
kind,
|
|
1877
|
+
branchOrigins
|
|
1878
|
+
};
|
|
1879
|
+
const windowSource = members.find(
|
|
1880
|
+
(member) => member.branchId === COMMITTED_TARGET_BRANCH && member.kind === kind && member.id === canonicalId
|
|
1881
|
+
) ?? survivor;
|
|
1882
|
+
return {
|
|
1883
|
+
canonicalId,
|
|
1884
|
+
kind,
|
|
1885
|
+
props,
|
|
1886
|
+
resolution,
|
|
1887
|
+
conflicts,
|
|
1888
|
+
...windowSource.validFrom === void 0 ? {} : { validFrom: windowSource.validFrom },
|
|
1889
|
+
...windowSource.validTo === void 0 ? {} : { validTo: windowSource.validTo }
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1005
1892
|
|
|
1006
1893
|
// src/graph-merge/closures.ts
|
|
1007
1894
|
var SUB_CLASS_OF_META_EDGE = "subClassOf";
|
|
1008
|
-
var
|
|
1895
|
+
var EQUIVALENCE_META_EDGES = /* @__PURE__ */ new Set([
|
|
1896
|
+
META_EDGE_EQUIVALENT_TO,
|
|
1897
|
+
META_EDGE_SAME_AS
|
|
1898
|
+
]);
|
|
1009
1899
|
function buildSubClassClosure(ontology) {
|
|
1010
1900
|
const unionFind = new UnionFind(compareStrings);
|
|
1011
1901
|
for (const relation of ontology) {
|
|
1012
1902
|
unionFind.add(relation.from);
|
|
1013
1903
|
unionFind.add(relation.to);
|
|
1014
|
-
if (relation.metaEdge
|
|
1904
|
+
if (EQUIVALENCE_META_EDGES.has(relation.metaEdge)) {
|
|
1015
1905
|
unionFind.union(relation.from, relation.to);
|
|
1016
1906
|
}
|
|
1017
1907
|
}
|
|
@@ -1073,7 +1963,7 @@ function clampScore(value) {
|
|
|
1073
1963
|
return value;
|
|
1074
1964
|
}
|
|
1075
1965
|
function readField(node, field) {
|
|
1076
|
-
return node
|
|
1966
|
+
return readOwnProperty(node, field);
|
|
1077
1967
|
}
|
|
1078
1968
|
function fieldText(node, fields) {
|
|
1079
1969
|
const parts = [];
|
|
@@ -1611,7 +2501,7 @@ function groupByEntity(items, entityOf) {
|
|
|
1611
2501
|
}
|
|
1612
2502
|
return grouped;
|
|
1613
2503
|
}
|
|
1614
|
-
function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntity,
|
|
2504
|
+
function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntity, dropDeleted, policy, branchRank, preferredBranchId) {
|
|
1615
2505
|
const modifiedByKey = groupByEntity(modified, modifiedEntity);
|
|
1616
2506
|
const deletedByKey = groupByEntity(deleted, deletedEntity);
|
|
1617
2507
|
const survivingModifications = [];
|
|
@@ -1656,7 +2546,7 @@ function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntit
|
|
|
1656
2546
|
});
|
|
1657
2547
|
if (effectivePolicy === "deleteWins") {
|
|
1658
2548
|
deletions.push({ id, kind });
|
|
1659
|
-
dropped.push(
|
|
2549
|
+
dropped.push(dropDeleted(id));
|
|
1660
2550
|
continue;
|
|
1661
2551
|
}
|
|
1662
2552
|
for (const modification of modifications) {
|
|
@@ -1746,7 +2636,7 @@ function resolveDeleteModify(staging, policy, branchRank, preferredBranchId) {
|
|
|
1746
2636
|
staging.deletedNodes,
|
|
1747
2637
|
(item) => item.node,
|
|
1748
2638
|
(item) => item.node,
|
|
1749
|
-
"node",
|
|
2639
|
+
(id) => ({ kind: "node", id, reason: DELETED_NODE_DROP_REASON }),
|
|
1750
2640
|
policy,
|
|
1751
2641
|
branchRank,
|
|
1752
2642
|
preferredBranchId
|
|
@@ -1759,7 +2649,7 @@ function resolveDeleteModify(staging, policy, branchRank, preferredBranchId) {
|
|
|
1759
2649
|
};
|
|
1760
2650
|
}
|
|
1761
2651
|
function threeWayMergeProps(entityId, kind, baseProps, contributions, context, branchRank, preferredBranchId) {
|
|
1762
|
-
const merged =
|
|
2652
|
+
const merged = createDataKeyedBag();
|
|
1763
2653
|
const conflicts = [];
|
|
1764
2654
|
const propertyNames = new Set(Object.keys(baseProps));
|
|
1765
2655
|
for (const contribution of contributions) {
|
|
@@ -1768,10 +2658,10 @@ function threeWayMergeProps(entityId, kind, baseProps, contributions, context, b
|
|
|
1768
2658
|
}
|
|
1769
2659
|
}
|
|
1770
2660
|
for (const property of [...propertyNames].sort(compareStrings)) {
|
|
1771
|
-
const baseHas = property
|
|
2661
|
+
const baseHas = hasOwnKey(baseProps, property);
|
|
1772
2662
|
const baseKey = baseHas ? canonicalValueKey(requireDefined(baseProps[property])) : void 0;
|
|
1773
2663
|
const changed = contributions.filter((contribution) => {
|
|
1774
|
-
if (!(
|
|
2664
|
+
if (!hasOwnKey(contribution.forkProps, property)) {
|
|
1775
2665
|
return false;
|
|
1776
2666
|
}
|
|
1777
2667
|
const value2 = contribution.forkProps[property];
|
|
@@ -1783,7 +2673,7 @@ function threeWayMergeProps(entityId, kind, baseProps, contributions, context, b
|
|
|
1783
2673
|
const values = collectConflictingValues(property, changed);
|
|
1784
2674
|
if (values.length === 0) {
|
|
1785
2675
|
const deletedByFork = baseHas && contributions.some(
|
|
1786
|
-
(contribution) => !(
|
|
2676
|
+
(contribution) => !hasOwnKey(contribution.forkProps, property)
|
|
1787
2677
|
);
|
|
1788
2678
|
if (baseHas && !deletedByFork) {
|
|
1789
2679
|
merged[property] = requireDefined(baseProps[property]);
|
|
@@ -1834,7 +2724,7 @@ function resolveEdgeDeleteModify(staging, policy, branchRank, preferredBranchId)
|
|
|
1834
2724
|
staging.deletedEdges,
|
|
1835
2725
|
(item) => item.edge,
|
|
1836
2726
|
(item) => item.edge,
|
|
1837
|
-
"edge",
|
|
2727
|
+
(id) => ({ kind: "edge", id, reason: DELETED_NODE_DROP_REASON }),
|
|
1838
2728
|
policy,
|
|
1839
2729
|
branchRank,
|
|
1840
2730
|
preferredBranchId
|
|
@@ -1861,8 +2751,166 @@ function reconcileEdgeModifications(survivingModifications, policy, branchRank,
|
|
|
1861
2751
|
);
|
|
1862
2752
|
}
|
|
1863
2753
|
|
|
2754
|
+
// src/graph-merge/valid-window.ts
|
|
2755
|
+
var WINDOW_NOT_APPLICABLE_DROP_REASON = "window-not-applicable";
|
|
2756
|
+
function earliestEnd(claims) {
|
|
2757
|
+
let earliest;
|
|
2758
|
+
for (const claim of claims) {
|
|
2759
|
+
if (earliest === void 0 || compareStrings(claim.validTo, earliest) < 0) {
|
|
2760
|
+
earliest = claim.validTo;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
return earliest;
|
|
2764
|
+
}
|
|
2765
|
+
function resolveEndClaims(claims, preferredBranchId) {
|
|
2766
|
+
const preferred = claims.filter(
|
|
2767
|
+
(claim) => claim.branchId === preferredBranchId
|
|
2768
|
+
);
|
|
2769
|
+
return earliestEnd(preferred.length > 0 ? preferred : claims);
|
|
2770
|
+
}
|
|
2771
|
+
function claimingBranches(claims) {
|
|
2772
|
+
return [...new Set(claims.map((claim) => claim.branchId))].sort(
|
|
2773
|
+
(left, right) => compareStrings(left, right)
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
function classifyDelta(base, fork) {
|
|
2777
|
+
const endCleared = fork.validTo === void 0 && base.validTo !== void 0;
|
|
2778
|
+
return {
|
|
2779
|
+
applicableEnd: fork.validTo !== void 0 && fork.validTo !== base.validTo ? fork.validTo : void 0,
|
|
2780
|
+
unapplicable: endCleared || fork.validFrom !== base.validFrom
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
function resolvePopulation(entity, deltas, deletions, preferredBranchId, dropItem) {
|
|
2784
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
2785
|
+
for (const delta of deltas) {
|
|
2786
|
+
const identity = mergeKey(delta.kind, delta.id);
|
|
2787
|
+
if (deletions.has(identity)) {
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
const bucket = byIdentity.get(identity);
|
|
2791
|
+
if (bucket === void 0) {
|
|
2792
|
+
byIdentity.set(identity, [delta]);
|
|
2793
|
+
} else {
|
|
2794
|
+
bucket.push(delta);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
const ends = /* @__PURE__ */ new Map();
|
|
2798
|
+
const credits = /* @__PURE__ */ new Map();
|
|
2799
|
+
const resolutions = [];
|
|
2800
|
+
const dropped = [];
|
|
2801
|
+
for (const [identity, group] of byIdentity) {
|
|
2802
|
+
const claims = [];
|
|
2803
|
+
let reportedUnapplicable = false;
|
|
2804
|
+
let targetEnd;
|
|
2805
|
+
for (const delta of group) {
|
|
2806
|
+
const { applicableEnd, unapplicable } = classifyDelta(
|
|
2807
|
+
delta.base,
|
|
2808
|
+
delta.fork
|
|
2809
|
+
);
|
|
2810
|
+
if (delta.branchId === preferredBranchId) {
|
|
2811
|
+
targetEnd ??= applicableEnd;
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
if (applicableEnd !== void 0) {
|
|
2815
|
+
claims.push({ branchId: delta.branchId, validTo: applicableEnd });
|
|
2816
|
+
}
|
|
2817
|
+
if (unapplicable && !reportedUnapplicable) {
|
|
2818
|
+
reportedUnapplicable = true;
|
|
2819
|
+
dropped.push(dropItem(delta.id));
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
const first = requireDefined(group[0]);
|
|
2823
|
+
if (targetEnd !== void 0) {
|
|
2824
|
+
if (claims.length > 0) {
|
|
2825
|
+
resolutions.push({
|
|
2826
|
+
entity,
|
|
2827
|
+
kind: first.kind,
|
|
2828
|
+
id: first.id,
|
|
2829
|
+
validTo: targetEnd,
|
|
2830
|
+
claimedBy: claimingBranches(claims),
|
|
2831
|
+
precedence: VALIDITY_END_TARGET_PRECEDENCE
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
const resolved = earliestEnd(claims);
|
|
2837
|
+
if (resolved === void 0) {
|
|
2838
|
+
continue;
|
|
2839
|
+
}
|
|
2840
|
+
ends.set(identity, resolved);
|
|
2841
|
+
credits.set(
|
|
2842
|
+
identity,
|
|
2843
|
+
claimingBranches(claims.filter((claim) => claim.validTo === resolved))
|
|
2844
|
+
);
|
|
2845
|
+
resolutions.push({
|
|
2846
|
+
entity,
|
|
2847
|
+
kind: first.kind,
|
|
2848
|
+
id: first.id,
|
|
2849
|
+
validTo: resolved,
|
|
2850
|
+
claimedBy: claimingBranches(claims)
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
ends,
|
|
2855
|
+
credits,
|
|
2856
|
+
resolutions: resolutions.sort(
|
|
2857
|
+
(left, right) => compareMergeKeys(
|
|
2858
|
+
mergeKey(left.kind, left.id),
|
|
2859
|
+
mergeKey(right.kind, right.id)
|
|
2860
|
+
)
|
|
2861
|
+
),
|
|
2862
|
+
dropped: dropped.sort((left, right) => compareStrings(left.id, right.id))
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
function resolveValidWindows(staging, nodeDeletions, edgeDeletions, preferredBranchId) {
|
|
2866
|
+
const nodes = resolvePopulation(
|
|
2867
|
+
"node",
|
|
2868
|
+
staging.windowedNodes.map((staged) => ({
|
|
2869
|
+
branchId: staged.branchId,
|
|
2870
|
+
kind: staged.node.kind,
|
|
2871
|
+
id: staged.node.id,
|
|
2872
|
+
base: staged.node.base,
|
|
2873
|
+
fork: staged.node.fork
|
|
2874
|
+
})),
|
|
2875
|
+
nodeDeletions,
|
|
2876
|
+
preferredBranchId,
|
|
2877
|
+
(id) => ({
|
|
2878
|
+
kind: "node",
|
|
2879
|
+
id,
|
|
2880
|
+
reason: WINDOW_NOT_APPLICABLE_DROP_REASON
|
|
2881
|
+
})
|
|
2882
|
+
);
|
|
2883
|
+
const edges = resolvePopulation(
|
|
2884
|
+
"edge",
|
|
2885
|
+
staging.windowedEdges.map((staged) => ({
|
|
2886
|
+
branchId: staged.branchId,
|
|
2887
|
+
kind: staged.edge.kind,
|
|
2888
|
+
id: staged.edge.id,
|
|
2889
|
+
base: staged.edge.base,
|
|
2890
|
+
fork: staged.edge.fork
|
|
2891
|
+
})),
|
|
2892
|
+
edgeDeletions,
|
|
2893
|
+
preferredBranchId,
|
|
2894
|
+
(id) => ({
|
|
2895
|
+
kind: "edge",
|
|
2896
|
+
id,
|
|
2897
|
+
reason: WINDOW_NOT_APPLICABLE_DROP_REASON
|
|
2898
|
+
})
|
|
2899
|
+
);
|
|
2900
|
+
return {
|
|
2901
|
+
nodeEnds: nodes.ends,
|
|
2902
|
+
edgeEnds: edges.ends,
|
|
2903
|
+
nodeCredits: nodes.credits,
|
|
2904
|
+
edgeCredits: edges.credits,
|
|
2905
|
+
resolutions: [...nodes.resolutions, ...edges.resolutions],
|
|
2906
|
+
dropped: [...nodes.dropped, ...edges.dropped]
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
|
|
1864
2910
|
// src/graph-merge/edge-repoint.ts
|
|
1865
2911
|
var ENDPOINT_DELETED_DROP_REASON = "edge:endpoint-deleted";
|
|
2912
|
+
var INHERITED_EDGE_ORIGIN = "inherited";
|
|
2913
|
+
var BRANCH_CREATED_EDGE_ORIGIN = "branch-created";
|
|
1866
2914
|
function buildCanonicalMap(clusters, canonicalOf) {
|
|
1867
2915
|
const map = /* @__PURE__ */ new Map();
|
|
1868
2916
|
for (const cluster of clusters) {
|
|
@@ -1882,16 +2930,97 @@ function dedupeKey(fromKey, type, toKey, props) {
|
|
|
1882
2930
|
function groupKey(fromKey, type, toKey) {
|
|
1883
2931
|
return JSON.stringify([fromKey, type, toKey]);
|
|
1884
2932
|
}
|
|
2933
|
+
function sourcePairKey(fromKey, toKey) {
|
|
2934
|
+
return JSON.stringify([fromKey, toKey]);
|
|
2935
|
+
}
|
|
2936
|
+
function rowsById(groupEdges) {
|
|
2937
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2938
|
+
for (const edge of groupEdges) {
|
|
2939
|
+
const row = rows.get(edge.staged.id);
|
|
2940
|
+
if (row === void 0) {
|
|
2941
|
+
rows.set(edge.staged.id, [edge]);
|
|
2942
|
+
} else {
|
|
2943
|
+
row.push(edge);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return rows;
|
|
2947
|
+
}
|
|
2948
|
+
function sourcePairByRow(rows) {
|
|
2949
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
2950
|
+
for (const [id, members] of rows) {
|
|
2951
|
+
const sorted = members.map((member) => member.sourcePair).sort((left, right) => compareStrings(left, right));
|
|
2952
|
+
pairs.set(id, requireDefined(sorted[0]));
|
|
2953
|
+
}
|
|
2954
|
+
return pairs;
|
|
2955
|
+
}
|
|
2956
|
+
function foldSets(groupEdges) {
|
|
2957
|
+
const rows = rowsById(groupEdges);
|
|
2958
|
+
const sourcePairs = sourcePairByRow(rows);
|
|
2959
|
+
const representatives = /* @__PURE__ */ new Map();
|
|
2960
|
+
for (const [id, pair] of sourcePairs) {
|
|
2961
|
+
const representative = representatives.get(pair);
|
|
2962
|
+
if (representative === void 0 || compareStrings(id, representative) < 0) {
|
|
2963
|
+
representatives.set(pair, id);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
const collapsed = [];
|
|
2967
|
+
const parallel = [];
|
|
2968
|
+
for (const id of [...rows.keys()].sort(
|
|
2969
|
+
(left, right) => compareStrings(left, right)
|
|
2970
|
+
)) {
|
|
2971
|
+
const members = requireDefined(rows.get(id));
|
|
2972
|
+
const pair = requireDefined(sourcePairs.get(id));
|
|
2973
|
+
if (representatives.get(pair) === id) {
|
|
2974
|
+
collapsed.push(...members);
|
|
2975
|
+
} else {
|
|
2976
|
+
parallel.push(members);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
return [collapsed, ...parallel];
|
|
2980
|
+
}
|
|
2981
|
+
function compareMembers(left, right, preferredBranchId) {
|
|
2982
|
+
const byId2 = compareStrings(left.staged.id, right.staged.id);
|
|
2983
|
+
if (byId2 !== 0) {
|
|
2984
|
+
return byId2;
|
|
2985
|
+
}
|
|
2986
|
+
const leftPreferred = left.staged.branchId === preferredBranchId;
|
|
2987
|
+
const rightPreferred = right.staged.branchId === preferredBranchId;
|
|
2988
|
+
if (leftPreferred !== rightPreferred) {
|
|
2989
|
+
return leftPreferred ? -1 : 1;
|
|
2990
|
+
}
|
|
2991
|
+
return compareStrings(left.staged.branchId, right.staged.branchId);
|
|
2992
|
+
}
|
|
1885
2993
|
function pickSurvivor(edges, preferredBranchId) {
|
|
2994
|
+
const inherited = edges.filter(
|
|
2995
|
+
(edge) => edge.staged.origin === INHERITED_EDGE_ORIGIN
|
|
2996
|
+
);
|
|
1886
2997
|
const preferred = preferredBranchId === void 0 ? [] : edges.filter((edge) => edge.staged.branchId === preferredBranchId);
|
|
1887
|
-
const candidates = preferred.length > 0 ? preferred : edges;
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2998
|
+
const candidates = inherited.length > 0 ? inherited : preferred.length > 0 ? preferred : edges;
|
|
2999
|
+
return requireDefined(
|
|
3000
|
+
[...candidates].sort(
|
|
3001
|
+
(left, right) => compareMembers(left, right, preferredBranchId)
|
|
3002
|
+
)[0]
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
function authoredProperty(edge, property) {
|
|
3006
|
+
const { props, baseProps } = edge.staged;
|
|
3007
|
+
if (!hasOwnKey(props, property)) {
|
|
3008
|
+
return false;
|
|
3009
|
+
}
|
|
3010
|
+
if (baseProps === void 0 || !hasOwnKey(baseProps, property)) {
|
|
3011
|
+
return true;
|
|
1893
3012
|
}
|
|
1894
|
-
return
|
|
3013
|
+
return canonicalValueKey(props[property]) !== canonicalValueKey(baseProps[property]);
|
|
3014
|
+
}
|
|
3015
|
+
function unauthoredValue(property, edges) {
|
|
3016
|
+
const carriers = edges.filter((edge) => hasOwnKey(edge.staged.props, property)).sort((left, right) => {
|
|
3017
|
+
const byId2 = compareStrings(left.staged.id, right.staged.id);
|
|
3018
|
+
return byId2 === 0 ? compareStrings(
|
|
3019
|
+
canonicalValueKey(left.staged.props[property]),
|
|
3020
|
+
canonicalValueKey(right.staged.props[property])
|
|
3021
|
+
) : byId2;
|
|
3022
|
+
});
|
|
3023
|
+
return requireDefined(carriers[0]).staged.props[property];
|
|
1895
3024
|
}
|
|
1896
3025
|
function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank, preferredBranchId) {
|
|
1897
3026
|
const propertyNames = /* @__PURE__ */ new Set();
|
|
@@ -1900,17 +3029,18 @@ function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank,
|
|
|
1900
3029
|
propertyNames.add(name);
|
|
1901
3030
|
}
|
|
1902
3031
|
}
|
|
1903
|
-
const props =
|
|
3032
|
+
const props = createDataKeyedBag();
|
|
1904
3033
|
const conflicts = [];
|
|
1905
|
-
const contributions = edges.map((edge) => edge.staged);
|
|
1906
3034
|
for (const property of [...propertyNames].sort(
|
|
1907
3035
|
(left, right) => compareStrings(left, right)
|
|
1908
3036
|
)) {
|
|
1909
|
-
const
|
|
3037
|
+
const claimants = edges.filter((edge) => authoredProperty(edge, property)).map((edge) => edge.staged);
|
|
3038
|
+
const values = collectConflictingValues(property, claimants);
|
|
1910
3039
|
if (values.length === 0) {
|
|
3040
|
+
props[property] = hasOwnKey(survivor.staged.props, property) ? survivor.staged.props[property] : unauthoredValue(property, edges);
|
|
1911
3041
|
continue;
|
|
1912
3042
|
}
|
|
1913
|
-
const survivorValue =
|
|
3043
|
+
const survivorValue = hasOwnKey(survivor.staged.props, property) ? survivor.staged.props[property] : requireDefined(values[0]).value;
|
|
1914
3044
|
const canonicalValue = values.find((value2) => value2.branchId === preferredBranchId)?.value ?? survivorValue;
|
|
1915
3045
|
const reportedValues = preferredBranchId === void 0 ? values : values.filter((value2) => value2.branchId !== preferredBranchId);
|
|
1916
3046
|
const { value, conflict } = resolvePropertyUnion(
|
|
@@ -1932,16 +3062,75 @@ function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank,
|
|
|
1932
3062
|
}
|
|
1933
3063
|
return { props, conflicts };
|
|
1934
3064
|
}
|
|
3065
|
+
function foldEdgeSet(foldSet, context, branchRank, preferredBranchId) {
|
|
3066
|
+
const survivor = pickSurvivor(foldSet, preferredBranchId);
|
|
3067
|
+
const survivorId = survivor.staged.id;
|
|
3068
|
+
const mergedIds = foldSet.map((edge) => edge.staged.id).sort((left, right) => compareStrings(left, right));
|
|
3069
|
+
const fromId = idOf(survivor.fromKey);
|
|
3070
|
+
const fromKind = kindOf(survivor.fromKey);
|
|
3071
|
+
const toId = idOf(survivor.toKey);
|
|
3072
|
+
const toKind = kindOf(survivor.toKey);
|
|
3073
|
+
const preferredSurvivorEnd = survivor.staged.branchId === preferredBranchId ? survivor.staged.validTo : void 0;
|
|
3074
|
+
const foldedEnd = preferredSurvivorEnd ?? resolveEndClaims(
|
|
3075
|
+
foldSet.filter((edge) => edge.staged.validTo !== void 0).map((edge) => ({
|
|
3076
|
+
branchId: edge.staged.branchId,
|
|
3077
|
+
validTo: requireDefined(edge.staged.validTo)
|
|
3078
|
+
})),
|
|
3079
|
+
preferredBranchId
|
|
3080
|
+
);
|
|
3081
|
+
const window = {
|
|
3082
|
+
...survivor.staged.validFrom === void 0 ? {} : { validFrom: survivor.staged.validFrom },
|
|
3083
|
+
...foldedEnd === void 0 ? {} : { validTo: foldedEnd }
|
|
3084
|
+
};
|
|
3085
|
+
const contentKeys = new Set(foldSet.map((edge) => edge.dedupeKey));
|
|
3086
|
+
if (contentKeys.size === 1) {
|
|
3087
|
+
return {
|
|
3088
|
+
edge: {
|
|
3089
|
+
id: survivorId,
|
|
3090
|
+
kind: survivor.staged.kind,
|
|
3091
|
+
fromId,
|
|
3092
|
+
toId,
|
|
3093
|
+
fromKind,
|
|
3094
|
+
toKind,
|
|
3095
|
+
props: survivor.staged.props,
|
|
3096
|
+
mergedIds,
|
|
3097
|
+
...window
|
|
3098
|
+
},
|
|
3099
|
+
conflicts: []
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
const { props, conflicts } = unionEdgeProps(
|
|
3103
|
+
survivorId,
|
|
3104
|
+
survivor.staged.kind,
|
|
3105
|
+
survivor,
|
|
3106
|
+
foldSet,
|
|
3107
|
+
context,
|
|
3108
|
+
branchRank,
|
|
3109
|
+
preferredBranchId
|
|
3110
|
+
);
|
|
3111
|
+
return {
|
|
3112
|
+
edge: {
|
|
3113
|
+
id: survivorId,
|
|
3114
|
+
kind: survivor.staged.kind,
|
|
3115
|
+
fromId,
|
|
3116
|
+
toId,
|
|
3117
|
+
fromKind,
|
|
3118
|
+
toKind,
|
|
3119
|
+
props,
|
|
3120
|
+
mergedIds,
|
|
3121
|
+
...window
|
|
3122
|
+
},
|
|
3123
|
+
conflicts
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
1935
3126
|
function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRank, weights, preferredBranchId) {
|
|
1936
3127
|
const dropped = [];
|
|
1937
|
-
const
|
|
1938
|
-
const dedupeKeyByGroup = /* @__PURE__ */ new Map();
|
|
3128
|
+
const liveByGroup = /* @__PURE__ */ new Map();
|
|
1939
3129
|
for (const staged of stagedEdges) {
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
);
|
|
1944
|
-
const toKey = repoint(mergeKey(staged.toKind, staged.toId), canonicalOf);
|
|
3130
|
+
const sourceFromKey = mergeKey(staged.fromKind, staged.fromId);
|
|
3131
|
+
const sourceToKey = mergeKey(staged.toKind, staged.toId);
|
|
3132
|
+
const fromKey = repoint(sourceFromKey, canonicalOf);
|
|
3133
|
+
const toKey = repoint(sourceToKey, canonicalOf);
|
|
1945
3134
|
if (deletedNodeIds.has(fromKey) || deletedNodeIds.has(toKey)) {
|
|
1946
3135
|
dropped.push({
|
|
1947
3136
|
kind: "edge",
|
|
@@ -1950,21 +3139,20 @@ function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRa
|
|
|
1950
3139
|
});
|
|
1951
3140
|
continue;
|
|
1952
3141
|
}
|
|
1953
|
-
const repointed = {
|
|
1954
|
-
|
|
1955
|
-
|
|
3142
|
+
const repointed = {
|
|
3143
|
+
staged,
|
|
3144
|
+
fromKey,
|
|
3145
|
+
toKey,
|
|
3146
|
+
sourcePair: sourcePairKey(sourceFromKey, sourceToKey),
|
|
3147
|
+
dedupeKey: dedupeKey(fromKey, staged.kind, toKey, staged.props)
|
|
3148
|
+
};
|
|
3149
|
+
const group = groupKey(fromKey, staged.kind, toKey);
|
|
3150
|
+
const bucket = liveByGroup.get(group);
|
|
1956
3151
|
if (bucket === void 0) {
|
|
1957
|
-
|
|
3152
|
+
liveByGroup.set(group, [repointed]);
|
|
1958
3153
|
} else {
|
|
1959
3154
|
bucket.push(repointed);
|
|
1960
3155
|
}
|
|
1961
|
-
const group = groupKey(fromKey, staged.kind, toKey);
|
|
1962
|
-
const keysForGroup = dedupeKeyByGroup.get(group);
|
|
1963
|
-
if (keysForGroup === void 0) {
|
|
1964
|
-
dedupeKeyByGroup.set(group, /* @__PURE__ */ new Set([key]));
|
|
1965
|
-
} else {
|
|
1966
|
-
keysForGroup.add(key);
|
|
1967
|
-
}
|
|
1968
3156
|
}
|
|
1969
3157
|
const context = {
|
|
1970
3158
|
policy,
|
|
@@ -1972,68 +3160,35 @@ function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRa
|
|
|
1972
3160
|
};
|
|
1973
3161
|
const merged = [];
|
|
1974
3162
|
const conflicts = [];
|
|
1975
|
-
const sortedGroups = [...
|
|
3163
|
+
const sortedGroups = [...liveByGroup.keys()].sort(
|
|
1976
3164
|
(left, right) => compareStrings(left, right)
|
|
1977
3165
|
);
|
|
1978
3166
|
for (const group of sortedGroups) {
|
|
1979
|
-
const
|
|
1980
|
-
const groupEdges
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
3167
|
+
const groupEdges = requireDefined(liveByGroup.get(group));
|
|
3168
|
+
for (const foldSet of foldSets(groupEdges)) {
|
|
3169
|
+
const folded = foldEdgeSet(
|
|
3170
|
+
foldSet,
|
|
3171
|
+
context,
|
|
3172
|
+
branchRank,
|
|
3173
|
+
preferredBranchId
|
|
3174
|
+
);
|
|
3175
|
+
merged.push(folded.edge);
|
|
3176
|
+
for (const conflict of folded.conflicts) {
|
|
3177
|
+
conflicts.push(conflict);
|
|
1984
3178
|
}
|
|
1985
3179
|
}
|
|
1986
|
-
const survivor = pickSurvivor(groupEdges, preferredBranchId);
|
|
1987
|
-
const survivorId = survivor.staged.id;
|
|
1988
|
-
const mergedIds = [...groupEdges].map((edge) => edge.staged.id).sort((left, right) => compareStrings(left, right));
|
|
1989
|
-
const fromId = idOf(survivor.fromKey);
|
|
1990
|
-
const fromKind = kindOf(survivor.fromKey);
|
|
1991
|
-
const toId = idOf(survivor.toKey);
|
|
1992
|
-
const toKind = kindOf(survivor.toKey);
|
|
1993
|
-
if (dedupeKeys.length === 1) {
|
|
1994
|
-
merged.push({
|
|
1995
|
-
id: survivorId,
|
|
1996
|
-
kind: survivor.staged.kind,
|
|
1997
|
-
fromId,
|
|
1998
|
-
toId,
|
|
1999
|
-
fromKind,
|
|
2000
|
-
toKind,
|
|
2001
|
-
props: survivor.staged.props,
|
|
2002
|
-
mergedIds
|
|
2003
|
-
});
|
|
2004
|
-
continue;
|
|
2005
|
-
}
|
|
2006
|
-
const { props, conflicts: groupConflicts } = unionEdgeProps(
|
|
2007
|
-
survivorId,
|
|
2008
|
-
survivor.staged.kind,
|
|
2009
|
-
survivor,
|
|
2010
|
-
groupEdges,
|
|
2011
|
-
context,
|
|
2012
|
-
branchRank,
|
|
2013
|
-
preferredBranchId
|
|
2014
|
-
);
|
|
2015
|
-
for (const conflict of groupConflicts) {
|
|
2016
|
-
conflicts.push(conflict);
|
|
2017
|
-
}
|
|
2018
|
-
merged.push({
|
|
2019
|
-
id: survivorId,
|
|
2020
|
-
kind: survivor.staged.kind,
|
|
2021
|
-
fromId,
|
|
2022
|
-
toId,
|
|
2023
|
-
fromKind,
|
|
2024
|
-
toKind,
|
|
2025
|
-
props,
|
|
2026
|
-
mergedIds
|
|
2027
|
-
});
|
|
2028
3180
|
}
|
|
2029
3181
|
const sortedEdges = merged.map((edge) => ({
|
|
2030
3182
|
edge,
|
|
2031
|
-
sortKey:
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
3183
|
+
sortKey: JSON.stringify([
|
|
3184
|
+
dedupeKey(
|
|
3185
|
+
mergeKey(edge.fromKind, edge.fromId),
|
|
3186
|
+
edge.kind,
|
|
3187
|
+
mergeKey(edge.toKind, edge.toId),
|
|
3188
|
+
edge.props
|
|
3189
|
+
),
|
|
3190
|
+
edge.id
|
|
3191
|
+
])
|
|
2037
3192
|
})).sort((left, right) => compareStrings(left.sortKey, right.sortKey)).map(({ edge }) => edge);
|
|
2038
3193
|
return {
|
|
2039
3194
|
edges: sortedEdges,
|
|
@@ -2085,7 +3240,7 @@ function validateResolveMap(resolve) {
|
|
|
2085
3240
|
return {};
|
|
2086
3241
|
}
|
|
2087
3242
|
const configs = resolve;
|
|
2088
|
-
const validated =
|
|
3243
|
+
const validated = createDataKeyedBag();
|
|
2089
3244
|
for (const [kind, config] of Object.entries(configs)) {
|
|
2090
3245
|
const parsed = resolveConfigScalarSchema.safeParse({
|
|
2091
3246
|
threshold: config.threshold
|
|
@@ -2113,7 +3268,7 @@ function validateResolveMap(resolve) {
|
|
|
2113
3268
|
}
|
|
2114
3269
|
validated[kind] = config;
|
|
2115
3270
|
}
|
|
2116
|
-
return validated;
|
|
3271
|
+
return { ...validated };
|
|
2117
3272
|
}
|
|
2118
3273
|
function validateProvenanceWeights(weights) {
|
|
2119
3274
|
for (const [branchId, weight] of weights) {
|
|
@@ -2168,7 +3323,9 @@ function normalizeMergeOptions(options = {}) {
|
|
|
2168
3323
|
...provenanceWeights === void 0 ? {} : { provenanceWeights }
|
|
2169
3324
|
};
|
|
2170
3325
|
}
|
|
2171
|
-
var
|
|
3326
|
+
var PROVENANCE_KIND = "Provenance";
|
|
3327
|
+
var PROVENANCE_OWNER_KIND = "ProvenanceOwner";
|
|
3328
|
+
var Provenance = defineNode(PROVENANCE_KIND, {
|
|
2172
3329
|
schema: z.object({
|
|
2173
3330
|
targetGraphId: z.string(),
|
|
2174
3331
|
role: z.enum(["node", "edge"]),
|
|
@@ -2178,11 +3335,31 @@ var Provenance = defineNode("Provenance", {
|
|
|
2178
3335
|
sourceId: z.string()
|
|
2179
3336
|
})
|
|
2180
3337
|
});
|
|
3338
|
+
var PROVENANCE_OWNER = "@nicia-ai/typegraph/merge-provenance";
|
|
3339
|
+
var PROVENANCE_OWNER_VERSION = 1;
|
|
3340
|
+
var PROVENANCE_OWNER_ID = "merge-provenance-owner";
|
|
3341
|
+
var ProvenanceOwner = defineNode(PROVENANCE_OWNER_KIND, {
|
|
3342
|
+
schema: z.object({
|
|
3343
|
+
owner: z.literal(PROVENANCE_OWNER),
|
|
3344
|
+
version: z.literal(PROVENANCE_OWNER_VERSION),
|
|
3345
|
+
targetGraphId: z.string()
|
|
3346
|
+
})
|
|
3347
|
+
});
|
|
2181
3348
|
function provenanceGraphId(targetGraphId) {
|
|
2182
3349
|
return `${targetGraphId}::merge-provenance`;
|
|
2183
3350
|
}
|
|
3351
|
+
function buildOwnedProvenanceGraph(targetGraphId) {
|
|
3352
|
+
return defineInternalGraph({
|
|
3353
|
+
id: provenanceGraphId(targetGraphId),
|
|
3354
|
+
nodes: {
|
|
3355
|
+
Provenance: { type: Provenance },
|
|
3356
|
+
ProvenanceOwner: { type: ProvenanceOwner }
|
|
3357
|
+
},
|
|
3358
|
+
edges: {}
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
2184
3361
|
function buildProvenanceGraph(targetGraphId) {
|
|
2185
|
-
return
|
|
3362
|
+
return defineInternalGraph({
|
|
2186
3363
|
id: provenanceGraphId(targetGraphId),
|
|
2187
3364
|
nodes: { Provenance: { type: Provenance } },
|
|
2188
3365
|
edges: {}
|
|
@@ -2190,26 +3367,276 @@ function buildProvenanceGraph(targetGraphId) {
|
|
|
2190
3367
|
}
|
|
2191
3368
|
async function openProvenanceStore(...args) {
|
|
2192
3369
|
const [backend, targetGraphId] = args.length === 1 ? [storeBackend(args[0]), args[0].graphId] : args;
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
3370
|
+
const graph = buildOwnedProvenanceGraph(targetGraphId);
|
|
3371
|
+
const preflight = await inspectSidecarGraphId(backend, graph, targetGraphId);
|
|
3372
|
+
if (preflight.state === "refuse") {
|
|
3373
|
+
throw provenanceGraphIdCollision(graph.id, targetGraphId, preflight.reason);
|
|
3374
|
+
}
|
|
3375
|
+
if (preflight.state !== "owned") {
|
|
3376
|
+
const claim = await claimSidecarOwnership(backend, graph, targetGraphId);
|
|
3377
|
+
if (claim.state === "refuse") {
|
|
3378
|
+
throw provenanceGraphIdCollision(graph.id, targetGraphId, claim.reason);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
const [store] = await createStoreWithSchema(graph, backend);
|
|
2197
3382
|
return store;
|
|
2198
3383
|
}
|
|
3384
|
+
async function claimSidecarOwnership(backend, graph, targetGraphId) {
|
|
3385
|
+
const fence = backend.schemaWriteTransaction;
|
|
3386
|
+
if (fence === void 0) {
|
|
3387
|
+
throw provenanceClaimUnfenced(graph.id, targetGraphId);
|
|
3388
|
+
}
|
|
3389
|
+
return fence(graph.id, async (tx) => {
|
|
3390
|
+
await drainUnfencedRowWriters(tx);
|
|
3391
|
+
const verdict = await inspectSidecarGraphId(tx, graph, targetGraphId);
|
|
3392
|
+
if (verdict.state === "refuse" || verdict.state === "owned") return verdict;
|
|
3393
|
+
await tx.insertNode({
|
|
3394
|
+
graphId: graph.id,
|
|
3395
|
+
kind: PROVENANCE_OWNER_KIND,
|
|
3396
|
+
id: PROVENANCE_OWNER_ID,
|
|
3397
|
+
props: {
|
|
3398
|
+
owner: PROVENANCE_OWNER,
|
|
3399
|
+
version: PROVENANCE_OWNER_VERSION,
|
|
3400
|
+
targetGraphId
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
return verdict;
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
async function drainUnfencedRowWriters(tx) {
|
|
3407
|
+
if (tx.dialect !== "postgres") return;
|
|
3408
|
+
const schema = createSqlSchema(tx.tableNames);
|
|
3409
|
+
await tx.executeStatement(
|
|
3410
|
+
asCompiledStatementSql(
|
|
3411
|
+
sql`LOCK TABLE ${schema.nodesTable}, ${schema.edgesTable} IN SHARE ROW EXCLUSIVE MODE`
|
|
3412
|
+
)
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
async function inspectSidecarGraphId(port, graph, targetGraphId) {
|
|
3416
|
+
const activeSchema = await port.getActiveSchema(graph.id);
|
|
3417
|
+
if (activeSchema === void 0 && !await hasRowsUnderGraphId(port, graph.id, "any")) {
|
|
3418
|
+
return { state: "create" };
|
|
3419
|
+
}
|
|
3420
|
+
const marker = await inspectOwnerMarker(port, graph.id, targetGraphId);
|
|
3421
|
+
if (marker === "foreign") {
|
|
3422
|
+
return { state: "refuse", reason: "corrupt-ownership-marker" };
|
|
3423
|
+
}
|
|
3424
|
+
if (marker === "ours") {
|
|
3425
|
+
return { state: "owned" };
|
|
3426
|
+
}
|
|
3427
|
+
if (activeSchema === void 0) {
|
|
3428
|
+
return { state: "refuse", reason: "application-graph" };
|
|
3429
|
+
}
|
|
3430
|
+
const ownedHash = await computeSchemaHash(
|
|
3431
|
+
serializeSchema(graph, activeSchema.version)
|
|
3432
|
+
);
|
|
3433
|
+
if (activeSchema.schema_hash === ownedHash) {
|
|
3434
|
+
return { state: "refuse", reason: "unowned-exact-schema-graph" };
|
|
3435
|
+
}
|
|
3436
|
+
const legacyHash = await computeSchemaHash(
|
|
3437
|
+
serializeSchema(buildProvenanceGraph(targetGraphId), activeSchema.version)
|
|
3438
|
+
);
|
|
3439
|
+
if (activeSchema.schema_hash !== legacyHash) {
|
|
3440
|
+
return { state: "refuse", reason: "application-graph" };
|
|
3441
|
+
}
|
|
3442
|
+
return LEGACY_SCHEMA_DISPOSITION[await inspectSidecarContents(port, graph.id, targetGraphId)];
|
|
3443
|
+
}
|
|
3444
|
+
var LEGACY_SCHEMA_DISPOSITION = {
|
|
3445
|
+
provenance: { state: "upgrade" },
|
|
3446
|
+
empty: { state: "refuse", reason: "empty-legacy-sidecar" },
|
|
3447
|
+
"unrecognized-provenance": {
|
|
3448
|
+
state: "refuse",
|
|
3449
|
+
reason: "unupgradeable-legacy-sidecar"
|
|
3450
|
+
},
|
|
3451
|
+
"foreign-rows": { state: "refuse", reason: "application-graph" }
|
|
3452
|
+
};
|
|
3453
|
+
var SIDECAR_REFUSALS = {
|
|
3454
|
+
"application-graph": {
|
|
3455
|
+
describe: (graphId) => `Graph id "${graphId}" is already used by an application graph and cannot host merge provenance.`,
|
|
3456
|
+
suggestion: () => "Rename the colliding application graph before enabling persisted merge provenance for this target."
|
|
3457
|
+
},
|
|
3458
|
+
"corrupt-ownership-marker": {
|
|
3459
|
+
describe: (graphId, targetGraphId) => `Graph id "${graphId}" holds a "${PROVENANCE_OWNER_KIND}" row that is not a valid ownership claim for target "${targetGraphId}" \u2014 it is soft-deleted, does not validate against the marker schema, names a different target, or sits under a different row id.`,
|
|
3460
|
+
suggestion: (graphId) => `Do not re-run: an unverifiable marker is never overwritten or resurrected, because it may be an application's row. Inspect the "${PROVENANCE_OWNER_KIND}" rows under graph id "${graphId}" and hard-delete the invalid one, or drop that graph id entirely \u2014 persisted provenance is derived, so re-running the merge rebuilds it.`
|
|
3461
|
+
},
|
|
3462
|
+
"empty-legacy-sidecar": {
|
|
3463
|
+
describe: (graphId) => `Graph id "${graphId}" holds an EMPTY provenance sidecar from a release that wrote no ownership marker, which cannot be told apart from an application graph of the same shape.`,
|
|
3464
|
+
suggestion: (graphId) => `Drop graph id "${graphId}" \u2014 it holds no provenance records, so nothing is lost \u2014 and re-run the merge to create an owned sidecar.`
|
|
3465
|
+
},
|
|
3466
|
+
"unupgradeable-legacy-sidecar": {
|
|
3467
|
+
describe: (graphId, targetGraphId) => `Graph id "${graphId}" holds a pre-marker provenance sidecar whose rows do not verify as provenance for target "${targetGraphId}", so it cannot be upgraded to an owned sidecar.`,
|
|
3468
|
+
suggestion: (graphId) => `Export anything you still need from graph id "${graphId}", then drop it: provenance is derived, and re-running the merge rebuilds an owned sidecar.`
|
|
3469
|
+
},
|
|
3470
|
+
"unowned-exact-schema-graph": {
|
|
3471
|
+
describe: (graphId) => `Graph id "${graphId}" carries the merge-provenance schema but no ownership marker, so this library did not create it.`,
|
|
3472
|
+
suggestion: (graphId) => `Drop graph id "${graphId}" (or rename the graph that occupies it) and re-run the merge: a sidecar this library owns writes its "${PROVENANCE_OWNER}" marker row BEFORE the schema, so a marker-less one carrying this schema was written either by an application \u2014 which is never adopted \u2014 or by a crash of an unreleased build between those two writes. Persisted provenance is derived, so re-running the merge rebuilds it.`
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
function provenanceGraphIdCollision(graphId, targetGraphId, reason) {
|
|
3476
|
+
const refusal = SIDECAR_REFUSALS[reason];
|
|
3477
|
+
return new ConfigurationError(
|
|
3478
|
+
refusal.describe(graphId, targetGraphId),
|
|
3479
|
+
{
|
|
3480
|
+
code: "GRAPH_MERGE_PROVENANCE_ID_COLLISION",
|
|
3481
|
+
reason,
|
|
3482
|
+
graphId,
|
|
3483
|
+
targetGraphId
|
|
3484
|
+
},
|
|
3485
|
+
{ suggestion: refusal.suggestion(graphId) }
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
function provenanceClaimUnfenced(graphId, targetGraphId) {
|
|
3489
|
+
return new ConfigurationError(
|
|
3490
|
+
`Claiming graph id "${graphId}" for merge provenance requires a transactional schema fence, which this backend does not provide.`,
|
|
3491
|
+
{
|
|
3492
|
+
code: "GRAPH_MERGE_PROVENANCE_CLAIM_UNFENCED",
|
|
3493
|
+
graphId,
|
|
3494
|
+
targetGraphId
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
suggestion: "Use a backend that implements schemaWriteTransaction (both bundled Drizzle backends do when transactions are enabled); without it the sidecar's emptiness check and its ownership write cannot be committed as one unit. An ALREADY-owned sidecar opens without a claim."
|
|
3498
|
+
}
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
function isOwnedMarkerRow(row, targetGraphId) {
|
|
3502
|
+
if (row.id !== PROVENANCE_OWNER_ID) return false;
|
|
3503
|
+
if (row.deleted_at !== void 0) return false;
|
|
3504
|
+
const parsed = ProvenanceOwner.schema.safeParse(parseRowProps(row.props));
|
|
3505
|
+
return parsed.success && parsed.data.targetGraphId === targetGraphId;
|
|
3506
|
+
}
|
|
3507
|
+
async function inspectOwnerMarker(port, graphId, targetGraphId) {
|
|
3508
|
+
let ours = false;
|
|
3509
|
+
for await (const row of readRowsOfKind(
|
|
3510
|
+
port,
|
|
3511
|
+
graphId,
|
|
3512
|
+
PROVENANCE_OWNER_KIND
|
|
3513
|
+
)) {
|
|
3514
|
+
if (!isOwnedMarkerRow(row, targetGraphId)) return "foreign";
|
|
3515
|
+
ours = true;
|
|
3516
|
+
}
|
|
3517
|
+
return ours ? "ours" : "absent";
|
|
3518
|
+
}
|
|
3519
|
+
var SIDECAR_PAGE_SIZE = 500;
|
|
3520
|
+
async function* readRowsOfKind(port, graphId, kind) {
|
|
3521
|
+
let after;
|
|
3522
|
+
for (; ; ) {
|
|
3523
|
+
const rows = await port.findNodesByKind({
|
|
3524
|
+
graphId,
|
|
3525
|
+
kind,
|
|
3526
|
+
temporalMode: "includeTombstones",
|
|
3527
|
+
excludeDeleted: false,
|
|
3528
|
+
orderBy: "id",
|
|
3529
|
+
...after === void 0 ? {} : { after },
|
|
3530
|
+
limit: SIDECAR_PAGE_SIZE
|
|
3531
|
+
});
|
|
3532
|
+
for (const row of rows) {
|
|
3533
|
+
yield row;
|
|
3534
|
+
}
|
|
3535
|
+
if (rows.length < SIDECAR_PAGE_SIZE) return;
|
|
3536
|
+
after = rows.at(-1)?.id;
|
|
3537
|
+
if (after === void 0) return;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
async function hasRowsUnderGraphId(port, graphId, scope) {
|
|
3541
|
+
const schema = createSqlSchema(port.tableNames);
|
|
3542
|
+
const kindFilter = scope === "any" ? sql.empty() : sql` AND kind <> ${PROVENANCE_KIND}`;
|
|
3543
|
+
if (await hasRowsInTable(port, schema.tables.nodes, graphId, kindFilter)) {
|
|
3544
|
+
return true;
|
|
3545
|
+
}
|
|
3546
|
+
if (await hasRowsInTable(port, schema.tables.edges, graphId, sql.empty())) {
|
|
3547
|
+
return true;
|
|
3548
|
+
}
|
|
3549
|
+
for (const tableName of secondaryRowTableNames(schema.tables)) {
|
|
3550
|
+
if (await hasRowsInSecondaryTable(port, tableName, graphId)) return true;
|
|
3551
|
+
}
|
|
3552
|
+
return false;
|
|
3553
|
+
}
|
|
3554
|
+
function secondaryRowTableNames(tables) {
|
|
3555
|
+
return [
|
|
3556
|
+
.../* @__PURE__ */ new Set([
|
|
3557
|
+
tables.recordedNodes,
|
|
3558
|
+
tables.recordedEdges,
|
|
3559
|
+
tables.recordedClock,
|
|
3560
|
+
tables.revisionOrigins,
|
|
3561
|
+
tables.identityAssertions,
|
|
3562
|
+
tables.recordedIdentityAssertions,
|
|
3563
|
+
tables.identityClosure,
|
|
3564
|
+
tables.identitySeparation,
|
|
3565
|
+
tables.fulltext,
|
|
3566
|
+
tables.uniques
|
|
3567
|
+
])
|
|
3568
|
+
];
|
|
3569
|
+
}
|
|
3570
|
+
async function hasRowsInTable(port, tableName, graphId, filter) {
|
|
3571
|
+
const rows = await port.execute(
|
|
3572
|
+
asCompiledRowsSql(
|
|
3573
|
+
sql`SELECT 1 AS present FROM ${sql.identifier(tableName)} WHERE graph_id = ${graphId}${filter} LIMIT 1`
|
|
3574
|
+
)
|
|
3575
|
+
);
|
|
3576
|
+
return rows.length > 0;
|
|
3577
|
+
}
|
|
3578
|
+
async function hasRowsInSecondaryTable(port, tableName, graphId) {
|
|
3579
|
+
const tableExists = port.tableExists;
|
|
3580
|
+
if (tableExists !== void 0) {
|
|
3581
|
+
return await tableExists(tableName) && await hasRowsInTable(port, tableName, graphId, sql.empty());
|
|
3582
|
+
}
|
|
3583
|
+
try {
|
|
3584
|
+
return await hasRowsInTable(port, tableName, graphId, sql.empty());
|
|
3585
|
+
} catch {
|
|
3586
|
+
return false;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
async function inspectSidecarContents(port, graphId, targetGraphId) {
|
|
3590
|
+
if (await hasRowsUnderGraphId(port, graphId, "foreign")) {
|
|
3591
|
+
return "foreign-rows";
|
|
3592
|
+
}
|
|
3593
|
+
let rowCount = 0;
|
|
3594
|
+
for await (const row of readRowsOfKind(port, graphId, PROVENANCE_KIND)) {
|
|
3595
|
+
if (!await isOwnedProvenanceRow(row, targetGraphId)) {
|
|
3596
|
+
return "unrecognized-provenance";
|
|
3597
|
+
}
|
|
3598
|
+
rowCount += 1;
|
|
3599
|
+
}
|
|
3600
|
+
return rowCount > 0 ? "provenance" : "empty";
|
|
3601
|
+
}
|
|
3602
|
+
async function isOwnedProvenanceRow(row, targetGraphId) {
|
|
3603
|
+
if (row.deleted_at !== void 0) return false;
|
|
3604
|
+
const parsed = Provenance.schema.safeParse(parseRowProps(row.props));
|
|
3605
|
+
if (!parsed.success || parsed.data.targetGraphId !== targetGraphId) {
|
|
3606
|
+
return false;
|
|
3607
|
+
}
|
|
3608
|
+
const expectedId = await provenanceNodeId(targetGraphId, {
|
|
3609
|
+
role: parsed.data.role,
|
|
3610
|
+
canonicalId: parsed.data.canonicalId,
|
|
3611
|
+
canonicalKind: parsed.data.canonicalKind,
|
|
3612
|
+
branchId: asBranchId(parsed.data.branchId),
|
|
3613
|
+
sourceId: parsed.data.sourceId
|
|
3614
|
+
});
|
|
3615
|
+
return row.id === expectedId;
|
|
3616
|
+
}
|
|
2199
3617
|
var ID_SEPARATOR = "\0";
|
|
2200
3618
|
var ID_DIGEST_BYTES = 16;
|
|
3619
|
+
function contributionKey(record) {
|
|
3620
|
+
return encodeProvenanceTuple([
|
|
3621
|
+
record.role,
|
|
3622
|
+
record.canonicalKind,
|
|
3623
|
+
record.canonicalId,
|
|
3624
|
+
record.branchId,
|
|
3625
|
+
record.sourceId
|
|
3626
|
+
]);
|
|
3627
|
+
}
|
|
3628
|
+
function encodeProvenanceTuple(values) {
|
|
3629
|
+
return values.some((value) => value.includes(ID_SEPARATOR)) ? encodeTupleKey(values) : values.join(ID_SEPARATOR);
|
|
3630
|
+
}
|
|
2201
3631
|
async function provenanceNodeId(targetGraphId, record) {
|
|
2202
|
-
const tuple = [
|
|
3632
|
+
const tuple = encodeProvenanceTuple([
|
|
2203
3633
|
targetGraphId,
|
|
2204
3634
|
record.role,
|
|
2205
|
-
// canonicalKind is part of node identity: two contributions to different-kind
|
|
2206
|
-
// canonicals that share a bare id (e.g. base Patient:x and Encounter:x) must NOT
|
|
2207
|
-
// hash to the same sidecar row and clobber each other (the (kind,id) identity model).
|
|
2208
3635
|
record.canonicalKind,
|
|
2209
3636
|
record.canonicalId,
|
|
2210
3637
|
record.branchId,
|
|
2211
3638
|
record.sourceId
|
|
2212
|
-
]
|
|
3639
|
+
]);
|
|
2213
3640
|
const digest = await sha256Hex(tuple, ID_DIGEST_BYTES);
|
|
2214
3641
|
return `prov_${digest}`;
|
|
2215
3642
|
}
|
|
@@ -2217,7 +3644,7 @@ async function persistProvenanceRecords(store, targetGraphId, records) {
|
|
|
2217
3644
|
if (records.length === 0) {
|
|
2218
3645
|
return 0;
|
|
2219
3646
|
}
|
|
2220
|
-
const
|
|
3647
|
+
const identified = await Promise.all(
|
|
2221
3648
|
records.map(async (record) => ({
|
|
2222
3649
|
id: await provenanceNodeId(targetGraphId, record),
|
|
2223
3650
|
props: {
|
|
@@ -2230,6 +3657,11 @@ async function persistProvenanceRecords(store, targetGraphId, records) {
|
|
|
2230
3657
|
}
|
|
2231
3658
|
}))
|
|
2232
3659
|
);
|
|
3660
|
+
const itemsById = /* @__PURE__ */ new Map();
|
|
3661
|
+
for (const item of identified) {
|
|
3662
|
+
itemsById.set(item.id, item);
|
|
3663
|
+
}
|
|
3664
|
+
const items = [...itemsById.values()];
|
|
2233
3665
|
await store.nodes.Provenance.bulkUpsertById(items);
|
|
2234
3666
|
return items.length;
|
|
2235
3667
|
}
|
|
@@ -2254,7 +3686,7 @@ function sortedBucketKeys(blocks) {
|
|
|
2254
3686
|
}
|
|
2255
3687
|
var NODE_SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
2256
3688
|
function nodeProps(node) {
|
|
2257
|
-
const props =
|
|
3689
|
+
const props = createDataKeyedBag();
|
|
2258
3690
|
for (const [key, value] of Object.entries(
|
|
2259
3691
|
node
|
|
2260
3692
|
)) {
|
|
@@ -2597,6 +4029,11 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2597
4029
|
const newEdges = [];
|
|
2598
4030
|
const modifiedEdges = [];
|
|
2599
4031
|
const deletedEdges = [];
|
|
4032
|
+
const windowedNodes = [];
|
|
4033
|
+
const windowedEdges = [];
|
|
4034
|
+
const newIdentityAssertions = [];
|
|
4035
|
+
const retractedIdentityAssertions = [];
|
|
4036
|
+
const baseIdentityAssertions = await storeRuntime(baseStore).readCurrentIdentityAssertions("state");
|
|
2600
4037
|
let targetNodeVersions = /* @__PURE__ */ new Map();
|
|
2601
4038
|
let targetEdgeSignatures = /* @__PURE__ */ new Map();
|
|
2602
4039
|
for (const branch2 of branches) {
|
|
@@ -2628,6 +4065,22 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2628
4065
|
for (const edge of diff.edges.deleted) {
|
|
2629
4066
|
deletedEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
|
|
2630
4067
|
}
|
|
4068
|
+
for (const node of diff.nodes.windowed) {
|
|
4069
|
+
windowedNodes.push({ branchId, node, kind: node.kind, id: node.id });
|
|
4070
|
+
}
|
|
4071
|
+
for (const edge of diff.edges.windowed) {
|
|
4072
|
+
windowedEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
|
|
4073
|
+
}
|
|
4074
|
+
for (const assertion of diff.identity.new) {
|
|
4075
|
+
newIdentityAssertions.push({ branchId, assertion });
|
|
4076
|
+
}
|
|
4077
|
+
for (const retraction of diff.identity.retracted) {
|
|
4078
|
+
retractedIdentityAssertions.push({
|
|
4079
|
+
branchId,
|
|
4080
|
+
assertion: retraction.assertion,
|
|
4081
|
+
cause: retraction.cause
|
|
4082
|
+
});
|
|
4083
|
+
}
|
|
2631
4084
|
}
|
|
2632
4085
|
return {
|
|
2633
4086
|
newNodesByKind: groupByKind(newNodes),
|
|
@@ -2644,6 +4097,25 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2644
4097
|
deletedEdges: [...deletedEdges].sort(
|
|
2645
4098
|
(left, right) => compareByKindIdBranch(left, right)
|
|
2646
4099
|
),
|
|
4100
|
+
windowedNodes: [...windowedNodes].sort(
|
|
4101
|
+
(left, right) => compareByKindIdBranch(left, right)
|
|
4102
|
+
),
|
|
4103
|
+
windowedEdges: [...windowedEdges].sort(
|
|
4104
|
+
(left, right) => compareByKindIdBranch(left, right)
|
|
4105
|
+
),
|
|
4106
|
+
newIdentityAssertions: newIdentityAssertions.toSorted((left, right) => {
|
|
4107
|
+
const byId2 = compareStrings(left.assertion.id, right.assertion.id);
|
|
4108
|
+
return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
|
|
4109
|
+
}),
|
|
4110
|
+
retractedIdentityAssertions: retractedIdentityAssertions.toSorted(
|
|
4111
|
+
(left, right) => {
|
|
4112
|
+
const byId2 = compareStrings(left.assertion.id, right.assertion.id);
|
|
4113
|
+
return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
|
|
4114
|
+
}
|
|
4115
|
+
),
|
|
4116
|
+
baseIdentityAssertions: baseIdentityAssertions.toSorted(
|
|
4117
|
+
(left, right) => compareStrings(left.id, right.id)
|
|
4118
|
+
),
|
|
2647
4119
|
targetNodeVersions,
|
|
2648
4120
|
targetEdgeSignatures
|
|
2649
4121
|
};
|
|
@@ -2759,7 +4231,6 @@ function reconcileTypes(clusters, closure, mode) {
|
|
|
2759
4231
|
}
|
|
2760
4232
|
|
|
2761
4233
|
// src/graph-merge/merge.ts
|
|
2762
|
-
var COMMITTED_TARGET_BRANCH = asBranchId("__committed_target__");
|
|
2763
4234
|
function asNode(staged) {
|
|
2764
4235
|
return {
|
|
2765
4236
|
kind: staged.node.kind,
|
|
@@ -2801,10 +4272,12 @@ async function precomputeEmbeddings(byKind, resolve, embedder) {
|
|
|
2801
4272
|
}
|
|
2802
4273
|
}
|
|
2803
4274
|
}
|
|
2804
|
-
const lookup = /* @__PURE__ */ new Map();
|
|
2805
4275
|
if (texts.size === 0) {
|
|
2806
|
-
return
|
|
4276
|
+
return /* @__PURE__ */ new Map();
|
|
2807
4277
|
}
|
|
4278
|
+
return embedSortedTexts(texts, embedder);
|
|
4279
|
+
}
|
|
4280
|
+
async function embedSortedTexts(texts, embedder) {
|
|
2808
4281
|
const orderedTexts = [...texts].sort(
|
|
2809
4282
|
(left, right) => compareStrings(left, right)
|
|
2810
4283
|
);
|
|
@@ -2818,6 +4291,7 @@ async function precomputeEmbeddings(byKind, resolve, embedder) {
|
|
|
2818
4291
|
}
|
|
2819
4292
|
);
|
|
2820
4293
|
}
|
|
4294
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
2821
4295
|
for (const [index, text] of orderedTexts.entries()) {
|
|
2822
4296
|
lookup.set(text, requireDefined(vectors[index]));
|
|
2823
4297
|
}
|
|
@@ -2841,24 +4315,7 @@ async function embedMissingPairTexts(base, pairs, strategy, embedder) {
|
|
|
2841
4315
|
if (missing.size === 0) {
|
|
2842
4316
|
return existing;
|
|
2843
4317
|
}
|
|
2844
|
-
|
|
2845
|
-
(left, right) => compareStrings(left, right)
|
|
2846
|
-
);
|
|
2847
|
-
const vectors = await embedder(orderedTexts);
|
|
2848
|
-
if (vectors.length !== orderedTexts.length) {
|
|
2849
|
-
throw new MergeError(
|
|
2850
|
-
`Embedder returned ${vectors.length} vectors for ${orderedTexts.length} texts; expected exactly one per text.`,
|
|
2851
|
-
{
|
|
2852
|
-
details: { texts: orderedTexts.length, vectors: vectors.length },
|
|
2853
|
-
suggestion: "Ensure MergeOptions.embedder returns one vector per input text, in order."
|
|
2854
|
-
}
|
|
2855
|
-
);
|
|
2856
|
-
}
|
|
2857
|
-
const augmented = new Map(existing);
|
|
2858
|
-
for (const [index, text] of orderedTexts.entries()) {
|
|
2859
|
-
augmented.set(text, requireDefined(vectors[index]));
|
|
2860
|
-
}
|
|
2861
|
-
return augmented;
|
|
4318
|
+
return new Map([...existing, ...await embedSortedTexts(missing, embedder)]);
|
|
2862
4319
|
}
|
|
2863
4320
|
async function generateAllCandidates(target, staging, options, introspectionKinds, ctx, useBaseSources, embedder) {
|
|
2864
4321
|
const allEdges = [];
|
|
@@ -2957,7 +4414,9 @@ function clusterMembersFor(cluster, newNodesById, baseMembersById) {
|
|
|
2957
4414
|
id: staged.node.id,
|
|
2958
4415
|
kind: staged.node.kind,
|
|
2959
4416
|
branchId: staged.branchId,
|
|
2960
|
-
props: staged.node.props
|
|
4417
|
+
props: staged.node.props,
|
|
4418
|
+
...staged.node.row.valid_from === void 0 ? {} : { validFrom: staged.node.row.valid_from },
|
|
4419
|
+
...staged.node.row.valid_to === void 0 ? {} : { validTo: staged.node.row.valid_to }
|
|
2961
4420
|
});
|
|
2962
4421
|
}
|
|
2963
4422
|
const base = baseMembersById.get(key);
|
|
@@ -2988,37 +4447,70 @@ function indexNewNodesById(staging) {
|
|
|
2988
4447
|
}
|
|
2989
4448
|
return index;
|
|
2990
4449
|
}
|
|
2991
|
-
function buildStagedEdges(staging, modifiedEdges) {
|
|
4450
|
+
function buildStagedEdges(staging, modifiedEdges, edgeValidityEnds, edgeDeletions) {
|
|
2992
4451
|
const staged = [];
|
|
4452
|
+
const stagedIdentities = /* @__PURE__ */ new Set();
|
|
4453
|
+
const windowOnlyCarried = /* @__PURE__ */ new Set();
|
|
2993
4454
|
for (const items of staging.newEdgesByKind.values()) {
|
|
2994
4455
|
for (const item of items) {
|
|
2995
4456
|
staged.push(toStagedEdge(item.branchId, item));
|
|
2996
4457
|
}
|
|
2997
4458
|
}
|
|
2998
4459
|
for (const item of modifiedEdges) {
|
|
4460
|
+
const identity = mergeKeyOf(item.edge);
|
|
4461
|
+
stagedIdentities.add(identity);
|
|
4462
|
+
const validTo = edgeValidityEnds.get(identity);
|
|
2999
4463
|
staged.push({
|
|
3000
4464
|
id: item.edge.id,
|
|
3001
4465
|
kind: item.edge.kind,
|
|
4466
|
+
origin: INHERITED_EDGE_ORIGIN,
|
|
3002
4467
|
fromId: item.edge.fromId,
|
|
3003
4468
|
toId: item.edge.toId,
|
|
3004
4469
|
fromKind: item.edge.fromKind,
|
|
3005
4470
|
toKind: item.edge.toKind,
|
|
3006
4471
|
props: item.edge.forkProps,
|
|
3007
|
-
|
|
4472
|
+
baseProps: item.edge.baseProps,
|
|
4473
|
+
branchId: item.branchId,
|
|
4474
|
+
...validTo === void 0 ? {} : { validTo }
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
for (const item of staging.windowedEdges) {
|
|
4478
|
+
const identity = mergeKeyOf(item.edge);
|
|
4479
|
+
const validTo = edgeValidityEnds.get(identity);
|
|
4480
|
+
if (validTo === void 0 || stagedIdentities.has(identity) || edgeDeletions.has(identity)) {
|
|
4481
|
+
continue;
|
|
4482
|
+
}
|
|
4483
|
+
stagedIdentities.add(identity);
|
|
4484
|
+
windowOnlyCarried.add(identity);
|
|
4485
|
+
staged.push({
|
|
4486
|
+
id: item.edge.id,
|
|
4487
|
+
kind: item.edge.kind,
|
|
4488
|
+
origin: INHERITED_EDGE_ORIGIN,
|
|
4489
|
+
fromId: item.edge.fromId,
|
|
4490
|
+
toId: item.edge.toId,
|
|
4491
|
+
fromKind: item.edge.fromKind,
|
|
4492
|
+
toKind: item.edge.toKind,
|
|
4493
|
+
props: item.edge.props,
|
|
4494
|
+
baseProps: item.edge.baseProps,
|
|
4495
|
+
branchId: item.branchId,
|
|
4496
|
+
validTo
|
|
3008
4497
|
});
|
|
3009
4498
|
}
|
|
3010
|
-
return staged;
|
|
4499
|
+
return { edges: staged, windowOnlyCarried };
|
|
3011
4500
|
}
|
|
3012
4501
|
function toStagedEdge(branchId, item) {
|
|
3013
4502
|
return {
|
|
3014
4503
|
id: item.edge.id,
|
|
3015
4504
|
kind: item.edge.kind,
|
|
4505
|
+
origin: BRANCH_CREATED_EDGE_ORIGIN,
|
|
3016
4506
|
fromId: item.edge.fromId,
|
|
3017
4507
|
toId: item.edge.toId,
|
|
3018
4508
|
fromKind: item.edge.fromKind,
|
|
3019
4509
|
toKind: item.edge.toKind,
|
|
3020
4510
|
props: item.edge.props,
|
|
3021
|
-
branchId
|
|
4511
|
+
branchId,
|
|
4512
|
+
...item.edge.row.valid_from === void 0 ? {} : { validFrom: item.edge.row.valid_from },
|
|
4513
|
+
...item.edge.row.valid_to === void 0 ? {} : { validTo: item.edge.row.valid_to }
|
|
3022
4514
|
};
|
|
3023
4515
|
}
|
|
3024
4516
|
function buildProvenanceIndex(records) {
|
|
@@ -3048,25 +4540,29 @@ function buildProvenanceIndex(records) {
|
|
|
3048
4540
|
byBranch: (branchId) => frozen.get(branchId) ?? { nodeIds: [], edgeIds: [] }
|
|
3049
4541
|
};
|
|
3050
4542
|
}
|
|
3051
|
-
function pushProvenance(records, role, branchId, canonicalId, canonicalKind, sourceId) {
|
|
3052
|
-
records.push({ role, canonicalId, canonicalKind, branchId, sourceId });
|
|
3053
|
-
}
|
|
3054
4543
|
var EMPTY_WEIGHTS = /* @__PURE__ */ new Map();
|
|
3055
|
-
function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, options, branchRank, subClassClosure, preferredBranchId) {
|
|
4544
|
+
function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, options, branchRank, subClassClosure, identityContext, storedIdentityRowsById, targetPeers, preferredBranchId) {
|
|
4545
|
+
const identity = planIdentityChanges(staging, storedIdentityRowsById);
|
|
3056
4546
|
const provenanceRecords = [];
|
|
4547
|
+
const recordedContributions = /* @__PURE__ */ new Set();
|
|
3057
4548
|
const weights = options.provenanceWeights ?? EMPTY_WEIGHTS;
|
|
3058
4549
|
const recordProvenance = (role, branchId, canonicalId, canonicalKind, sourceId) => {
|
|
3059
4550
|
if (branchId === preferredBranchId) {
|
|
3060
4551
|
return;
|
|
3061
4552
|
}
|
|
3062
|
-
|
|
3063
|
-
provenanceRecords,
|
|
4553
|
+
const record = {
|
|
3064
4554
|
role,
|
|
3065
|
-
branchId,
|
|
3066
4555
|
canonicalId,
|
|
3067
4556
|
canonicalKind,
|
|
4557
|
+
branchId,
|
|
3068
4558
|
sourceId
|
|
3069
|
-
|
|
4559
|
+
};
|
|
4560
|
+
const key = contributionKey(record);
|
|
4561
|
+
if (recordedContributions.has(key)) {
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
4564
|
+
recordedContributions.add(key);
|
|
4565
|
+
provenanceRecords.push(record);
|
|
3070
4566
|
};
|
|
3071
4567
|
const newNodesById = indexNewNodesById(staging);
|
|
3072
4568
|
const acceptedEndpointKeys = /* @__PURE__ */ new Set();
|
|
@@ -3159,6 +4655,34 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3159
4655
|
for (const deletion of deleteModify.nodeDeletions) {
|
|
3160
4656
|
nodeDeletions.set(mergeKey(deletion.kind, deletion.id), deletion.kind);
|
|
3161
4657
|
}
|
|
4658
|
+
const anyDeletionOverruled = staging.deletedNodes.some(
|
|
4659
|
+
(deletion) => !nodeDeletions.has(mergeKey(deletion.node.kind, deletion.node.id))
|
|
4660
|
+
);
|
|
4661
|
+
const stagedRetractionsById = /* @__PURE__ */ new Map();
|
|
4662
|
+
for (const staged of staging.retractedIdentityAssertions) {
|
|
4663
|
+
const contributions = stagedRetractionsById.get(staged.assertion.id) ?? [];
|
|
4664
|
+
contributions.push(staged);
|
|
4665
|
+
stagedRetractionsById.set(staged.assertion.id, contributions);
|
|
4666
|
+
}
|
|
4667
|
+
const overruledRetractionDrops = [];
|
|
4668
|
+
const survivingRetractions = anyDeletionOverruled ? identity.retractions.filter((retraction) => {
|
|
4669
|
+
const contributions = stagedRetractionsById.get(retraction.id) ?? [];
|
|
4670
|
+
const everyContributionOverruled = contributions.length > 0 && contributions.every(
|
|
4671
|
+
(staged) => staged.cause.kind === "cascade" && !nodeDeletions.has(
|
|
4672
|
+
mergeKey(
|
|
4673
|
+
staged.cause.deletedNode.kind,
|
|
4674
|
+
staged.cause.deletedNode.id
|
|
4675
|
+
)
|
|
4676
|
+
)
|
|
4677
|
+
);
|
|
4678
|
+
if (!everyContributionOverruled) return true;
|
|
4679
|
+
overruledRetractionDrops.push({
|
|
4680
|
+
kind: "identity",
|
|
4681
|
+
id: retraction.id,
|
|
4682
|
+
reason: RETRACTION_DELETION_OVERRULED_DROP_REASON
|
|
4683
|
+
});
|
|
4684
|
+
return false;
|
|
4685
|
+
}) : identity.retractions;
|
|
3162
4686
|
for (const modification of deleteModify.survivingModifications) {
|
|
3163
4687
|
recordProvenance(
|
|
3164
4688
|
"node",
|
|
@@ -3207,6 +4731,31 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3207
4731
|
for (const conflict of reconciledEdgeModifications.conflicts) {
|
|
3208
4732
|
propertyConflicts.push(conflict);
|
|
3209
4733
|
}
|
|
4734
|
+
const validWindows = resolveValidWindows(
|
|
4735
|
+
staging,
|
|
4736
|
+
new Set(nodeDeletions.keys()),
|
|
4737
|
+
new Set(edgeDeletions.keys()),
|
|
4738
|
+
preferredBranchId
|
|
4739
|
+
);
|
|
4740
|
+
const creditedNodeIdentities = /* @__PURE__ */ new Map();
|
|
4741
|
+
for (const record of provenanceRecords) {
|
|
4742
|
+
if (record.role !== "node" || record.sourceId !== record.canonicalId) {
|
|
4743
|
+
continue;
|
|
4744
|
+
}
|
|
4745
|
+
const identity2 = mergeKey(record.canonicalKind, record.canonicalId);
|
|
4746
|
+
const credited = creditedNodeIdentities.get(identity2) ?? /* @__PURE__ */ new Set();
|
|
4747
|
+
credited.add(record.branchId);
|
|
4748
|
+
creditedNodeIdentities.set(identity2, credited);
|
|
4749
|
+
}
|
|
4750
|
+
for (const [identity2, branchIds] of validWindows.nodeCredits) {
|
|
4751
|
+
for (const branchId of branchIds) {
|
|
4752
|
+
if (creditedNodeIdentities.get(identity2)?.has(branchId) === true) {
|
|
4753
|
+
continue;
|
|
4754
|
+
}
|
|
4755
|
+
const id = idOf(identity2);
|
|
4756
|
+
recordProvenance("node", branchId, id, kindOf(identity2), id);
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
3210
4759
|
const reconciliation = reconcileTypes(
|
|
3211
4760
|
reconcileInputs,
|
|
3212
4761
|
subClassClosure,
|
|
@@ -3220,18 +4769,57 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3220
4769
|
clusters,
|
|
3221
4770
|
(cluster) => pickClusterCanonical(cluster, entityByIdentity)
|
|
3222
4771
|
);
|
|
3223
|
-
const
|
|
4772
|
+
const identityRemap = remapIdentityAssertionEndpoints(
|
|
4773
|
+
identity.assertions,
|
|
4774
|
+
canonicalOf,
|
|
4775
|
+
reconciliation.retypeMap,
|
|
4776
|
+
storedIdentityRowsById
|
|
4777
|
+
);
|
|
4778
|
+
assertIdentityEndpointsNotDeleted(
|
|
4779
|
+
identityRemap.assertions,
|
|
4780
|
+
nodeDeletions,
|
|
4781
|
+
reconciliation.retypeMap
|
|
4782
|
+
);
|
|
4783
|
+
const identityNodeUniverse = [
|
|
4784
|
+
...canonicalEntities.filter(
|
|
4785
|
+
(entity) => !nodeDeletions.has(mergeKey(entity.kind, entity.canonicalId))
|
|
4786
|
+
).map((entity) => ({
|
|
4787
|
+
kind: reconciliation.retypeMap.get(
|
|
4788
|
+
mergeKey(entity.kind, entity.canonicalId)
|
|
4789
|
+
) ?? entity.kind,
|
|
4790
|
+
id: entity.canonicalId
|
|
4791
|
+
})),
|
|
4792
|
+
...targetPeers.filter(
|
|
4793
|
+
(node) => !nodeDeletions.has(mergeKey(node.kind, node.id))
|
|
4794
|
+
)
|
|
4795
|
+
];
|
|
4796
|
+
assertNoContradictoryIdentityClosure(
|
|
4797
|
+
identityRemap.assertions,
|
|
4798
|
+
survivingRetractions.map((retraction) => retraction.id),
|
|
4799
|
+
staging.baseIdentityAssertions,
|
|
4800
|
+
new Set(nodeDeletions.keys()),
|
|
4801
|
+
canonicalOf,
|
|
4802
|
+
reconciliation.retypeMap,
|
|
4803
|
+
identityContext,
|
|
4804
|
+
identityNodeUniverse
|
|
4805
|
+
);
|
|
4806
|
+
const { edges: stagedEdges, windowOnlyCarried } = buildStagedEdges(
|
|
3224
4807
|
staging,
|
|
3225
|
-
reconciledEdgeModifications.survivingModifications
|
|
4808
|
+
reconciledEdgeModifications.survivingModifications,
|
|
4809
|
+
validWindows.edgeEnds,
|
|
4810
|
+
edgeDeletions
|
|
3226
4811
|
);
|
|
3227
4812
|
const stagedEdgeBranches = /* @__PURE__ */ new Map();
|
|
3228
4813
|
for (const staged of stagedEdges) {
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
} else {
|
|
4814
|
+
const identity2 = mergeKeyOf(staged);
|
|
4815
|
+
const branches = stagedEdgeBranches.get(staged.id) ?? /* @__PURE__ */ new Set();
|
|
4816
|
+
if (!windowOnlyCarried.has(identity2)) {
|
|
3233
4817
|
branches.add(staged.branchId);
|
|
3234
4818
|
}
|
|
4819
|
+
for (const branchId of validWindows.edgeCredits.get(identity2) ?? []) {
|
|
4820
|
+
branches.add(branchId);
|
|
4821
|
+
}
|
|
4822
|
+
stagedEdgeBranches.set(staged.id, branches);
|
|
3235
4823
|
}
|
|
3236
4824
|
const deletedNodeIdSet = new Set(nodeDeletions.keys());
|
|
3237
4825
|
const repoint2 = repointEdges(
|
|
@@ -3254,7 +4842,11 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3254
4842
|
...deleteModify.dropped,
|
|
3255
4843
|
...edgeDeleteModify.dropped,
|
|
3256
4844
|
...repoint2.dropped,
|
|
3257
|
-
...reconciliation.dropped
|
|
4845
|
+
...reconciliation.dropped,
|
|
4846
|
+
...identity.dropped,
|
|
4847
|
+
...overruledRetractionDrops,
|
|
4848
|
+
...identityRemap.dropped,
|
|
4849
|
+
...validWindows.dropped
|
|
3258
4850
|
].sort(
|
|
3259
4851
|
(left, right) => compareStrings(`${left.kind}|${left.id}`, `${right.kind}|${right.id}`)
|
|
3260
4852
|
);
|
|
@@ -3271,6 +4863,10 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3271
4863
|
])
|
|
3272
4864
|
),
|
|
3273
4865
|
retypeMap: reconciliation.retypeMap,
|
|
4866
|
+
canonicalOf,
|
|
4867
|
+
nodeValidityEnds: validWindows.nodeEnds,
|
|
4868
|
+
edgeValidityEnds: validWindows.edgeEnds,
|
|
4869
|
+
validityEnds: validWindows.resolutions,
|
|
3274
4870
|
// Order report arrays by the composite (kind, id) identity — never a bare id
|
|
3275
4871
|
// or a `|`-joined string. A bare id ties two different-kind entities that
|
|
3276
4872
|
// share an id, and a `|` separator collides on caller-supplied ids/property
|
|
@@ -3316,7 +4912,9 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3316
4912
|
)
|
|
3317
4913
|
),
|
|
3318
4914
|
provenanceRecords,
|
|
3319
|
-
warnings: candidateWarnings
|
|
4915
|
+
warnings: candidateWarnings,
|
|
4916
|
+
identityAssertions: identityRemap.assertions,
|
|
4917
|
+
identityRetractions: survivingRetractions
|
|
3320
4918
|
};
|
|
3321
4919
|
}
|
|
3322
4920
|
function pickClusterCanonical(cluster, entityByIdentity) {
|
|
@@ -3331,78 +4929,125 @@ function pickClusterCanonical(cluster, entityByIdentity) {
|
|
|
3331
4929
|
);
|
|
3332
4930
|
}
|
|
3333
4931
|
function commitModificationProps(baseProps, forkProps) {
|
|
3334
|
-
const props =
|
|
4932
|
+
const props = createDataKeyedBag();
|
|
4933
|
+
for (const key of Object.keys(forkProps)) {
|
|
4934
|
+
props[key] = forkProps[key];
|
|
4935
|
+
}
|
|
3335
4936
|
for (const key of Object.keys(baseProps)) {
|
|
3336
|
-
if (!(key
|
|
4937
|
+
if (!hasOwnKey(forkProps, key)) {
|
|
3337
4938
|
props[key] = void 0;
|
|
3338
4939
|
}
|
|
3339
4940
|
}
|
|
3340
4941
|
return props;
|
|
3341
4942
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
4943
|
+
function finalEdgeEndpoint(plan, kind, id) {
|
|
4944
|
+
return { kind: plan.retypeMap.get(mergeKey(kind, id)) ?? kind, id };
|
|
4945
|
+
}
|
|
4946
|
+
function plannedNodeWrites(plan) {
|
|
3344
4947
|
const canonicalIdentities = /* @__PURE__ */ new Set();
|
|
3345
4948
|
for (const entity of plan.canonicalEntities) {
|
|
3346
4949
|
canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
|
|
3347
4950
|
}
|
|
3348
|
-
const
|
|
4951
|
+
const modificationsByIdentity = /* @__PURE__ */ new Map();
|
|
3349
4952
|
for (const modification of plan.survivingModifications) {
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
4953
|
+
modificationsByIdentity.set(
|
|
4954
|
+
mergeKeyOf(modification.node),
|
|
4955
|
+
modification.node
|
|
4956
|
+
);
|
|
3354
4957
|
}
|
|
4958
|
+
const writes = [];
|
|
4959
|
+
const written = /* @__PURE__ */ new Set();
|
|
3355
4960
|
for (const modification of plan.survivingModifications) {
|
|
3356
4961
|
const identity = mergeKeyOf(modification.node);
|
|
3357
4962
|
if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
|
|
3358
4963
|
continue;
|
|
3359
4964
|
}
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
4965
|
+
written.add(identity);
|
|
4966
|
+
const validTo = plan.nodeValidityEnds.get(identity);
|
|
4967
|
+
writes.push({
|
|
4968
|
+
identity,
|
|
4969
|
+
kind: modification.node.kind,
|
|
4970
|
+
id: modification.node.id,
|
|
4971
|
+
modification: modification.node,
|
|
4972
|
+
...validTo === void 0 ? {} : { validTo }
|
|
4973
|
+
});
|
|
3369
4974
|
}
|
|
3370
4975
|
for (const entity of plan.canonicalEntities) {
|
|
3371
|
-
const
|
|
3372
|
-
if (plan.nodeDeletions.has(
|
|
4976
|
+
const sourceIdentity = mergeKey(entity.kind, entity.canonicalId);
|
|
4977
|
+
if (plan.nodeDeletions.has(sourceIdentity)) {
|
|
3373
4978
|
continue;
|
|
3374
4979
|
}
|
|
3375
|
-
const kind = plan.retypeMap.get(
|
|
3376
|
-
const modification =
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
4980
|
+
const kind = plan.retypeMap.get(sourceIdentity) ?? entity.kind;
|
|
4981
|
+
const modification = modificationsByIdentity.get(sourceIdentity);
|
|
4982
|
+
written.add(sourceIdentity);
|
|
4983
|
+
const validTo = entity.validTo ?? plan.nodeValidityEnds.get(sourceIdentity);
|
|
4984
|
+
writes.push({
|
|
4985
|
+
identity: mergeKey(kind, entity.canonicalId),
|
|
4986
|
+
kind,
|
|
4987
|
+
id: entity.canonicalId,
|
|
4988
|
+
entity,
|
|
4989
|
+
...modification === void 0 ? {} : { modification },
|
|
4990
|
+
...entity.validFrom === void 0 ? {} : { validFrom: entity.validFrom },
|
|
4991
|
+
...validTo === void 0 ? {} : { validTo }
|
|
4992
|
+
});
|
|
3387
4993
|
}
|
|
4994
|
+
for (const identity of [...plan.nodeValidityEnds.keys()].sort(
|
|
4995
|
+
(left, right) => compareMergeKeys(left, right)
|
|
4996
|
+
)) {
|
|
4997
|
+
if (written.has(identity) || plan.nodeDeletions.has(identity)) {
|
|
4998
|
+
continue;
|
|
4999
|
+
}
|
|
5000
|
+
const kind = plan.retypeMap.get(identity) ?? kindOf(identity);
|
|
5001
|
+
writes.push({
|
|
5002
|
+
identity: mergeKey(kind, idOf(identity)),
|
|
5003
|
+
kind,
|
|
5004
|
+
id: idOf(identity),
|
|
5005
|
+
validTo: requireDefined(plan.nodeValidityEnds.get(identity))
|
|
5006
|
+
});
|
|
5007
|
+
}
|
|
5008
|
+
return writes;
|
|
5009
|
+
}
|
|
5010
|
+
function nodeWriteProps(write, modificationProps) {
|
|
5011
|
+
return {
|
|
5012
|
+
...write.entity?.props,
|
|
5013
|
+
...write.modification === void 0 ? void 0 : modificationProps(write.modification)
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
async function applyMergePlan(plan, nodesApi, edgesApi, target, txBackend) {
|
|
5017
|
+
const committedNodeIds = /* @__PURE__ */ new Set();
|
|
3388
5018
|
for (const [identity, kind] of plan.nodeDeletions) {
|
|
3389
5019
|
const collection = nodeCollection(nodesApi, kind);
|
|
3390
5020
|
await collection.delete(idOf(identity));
|
|
3391
5021
|
}
|
|
5022
|
+
for (const write of plannedNodeWrites(plan)) {
|
|
5023
|
+
const collection = nodeCollection(nodesApi, write.kind);
|
|
5024
|
+
await collection.upsertByIdFromRecord(
|
|
5025
|
+
write.id,
|
|
5026
|
+
nodeWriteProps(
|
|
5027
|
+
write,
|
|
5028
|
+
(modification) => commitModificationProps(modification.baseProps, modification.forkProps)
|
|
5029
|
+
),
|
|
5030
|
+
{
|
|
5031
|
+
...write.validFrom === void 0 ? {} : { validFrom: write.validFrom },
|
|
5032
|
+
...write.validTo === void 0 ? {} : { validTo: write.validTo }
|
|
5033
|
+
}
|
|
5034
|
+
);
|
|
5035
|
+
committedNodeIds.add(write.identity);
|
|
5036
|
+
}
|
|
3392
5037
|
for (const [identity, kind] of plan.edgeDeletions) {
|
|
3393
5038
|
await edgeCollection(edgesApi, kind).delete(idOf(identity));
|
|
3394
5039
|
}
|
|
3395
5040
|
const edgesByKind = /* @__PURE__ */ new Map();
|
|
3396
5041
|
for (const edge of plan.mergedEdges) {
|
|
3397
|
-
const fromKind = plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId));
|
|
3398
|
-
const toKind = plan.retypeMap.get(mergeKey(edge.toKind, edge.toId));
|
|
3399
5042
|
const edgeBaseProps = plan.inheritedEdgeBaseProps.get(edge.id);
|
|
3400
5043
|
const props = edgeBaseProps === void 0 ? edge.props : commitModificationProps(edgeBaseProps, edge.props);
|
|
3401
5044
|
const item = {
|
|
3402
5045
|
id: edge.id,
|
|
3403
|
-
from:
|
|
3404
|
-
to:
|
|
3405
|
-
props
|
|
5046
|
+
from: finalEdgeEndpoint(plan, edge.fromKind, edge.fromId),
|
|
5047
|
+
to: finalEdgeEndpoint(plan, edge.toKind, edge.toId),
|
|
5048
|
+
props,
|
|
5049
|
+
...edge.validFrom === void 0 ? {} : { validFrom: edge.validFrom },
|
|
5050
|
+
...edge.validTo === void 0 ? {} : { validTo: edge.validTo }
|
|
3406
5051
|
};
|
|
3407
5052
|
const existing = edgesByKind.get(edge.kind);
|
|
3408
5053
|
if (existing === void 0) {
|
|
@@ -3416,7 +5061,31 @@ async function applyMergePlan(plan, nodesApi, edgesApi) {
|
|
|
3416
5061
|
await edgeCollection(edgesApi, kind).bulkUpsertById(items);
|
|
3417
5062
|
committedEdges += items.length;
|
|
3418
5063
|
}
|
|
3419
|
-
|
|
5064
|
+
let appliedIdentity;
|
|
5065
|
+
try {
|
|
5066
|
+
appliedIdentity = await storeRuntime(target).applyIdentityMergeAtTarget(
|
|
5067
|
+
txBackend,
|
|
5068
|
+
plan.identityRetractions.map((retraction) => retraction.id),
|
|
5069
|
+
plan.identityAssertions
|
|
5070
|
+
);
|
|
5071
|
+
await assertMergedIdentityClassesConsistent(target, txBackend, plan);
|
|
5072
|
+
} catch (error) {
|
|
5073
|
+
throw translateIdentityCommitError(error);
|
|
5074
|
+
}
|
|
5075
|
+
return {
|
|
5076
|
+
nodes: committedNodeIds.size,
|
|
5077
|
+
edges: committedEdges,
|
|
5078
|
+
// ACTUAL ledger effects from the applier: rows created (idempotent
|
|
5079
|
+
// exact/pair matches the target already held are excluded — the normal
|
|
5080
|
+
// incremental case, where the target's own additions are staged back at
|
|
5081
|
+
// it) and rows ended (already-ended or unknown ids excluded). Any apply
|
|
5082
|
+
// failure aborts the whole merge, so the counts never describe a
|
|
5083
|
+
// partial commit.
|
|
5084
|
+
identity: {
|
|
5085
|
+
asserted: appliedIdentity.created,
|
|
5086
|
+
retracted: appliedIdentity.retracted
|
|
5087
|
+
}
|
|
5088
|
+
};
|
|
3420
5089
|
}
|
|
3421
5090
|
async function commitPlan(target, plan, expectedBaseVersion) {
|
|
3422
5091
|
if (!storeBackend(target).capabilities.transactions) {
|
|
@@ -3434,10 +5103,13 @@ async function commitPlan(target, plan, expectedBaseVersion) {
|
|
|
3434
5103
|
expectedBaseVersion
|
|
3435
5104
|
);
|
|
3436
5105
|
}
|
|
5106
|
+
await assertPlannedIdentityIdsFresh(target, transactionBackend(tx), plan);
|
|
3437
5107
|
return applyMergePlan(
|
|
3438
5108
|
plan,
|
|
3439
5109
|
tx.nodes,
|
|
3440
|
-
tx.edges
|
|
5110
|
+
tx.edges,
|
|
5111
|
+
target,
|
|
5112
|
+
transactionBackend(tx)
|
|
3441
5113
|
);
|
|
3442
5114
|
}, mergeCommitTransactionOptions(target))
|
|
3443
5115
|
);
|
|
@@ -3486,7 +5158,8 @@ async function assertTargetUnchanged(txBackend, target, expectedBaseVersion) {
|
|
|
3486
5158
|
const liveContent = await computeContentComponent(
|
|
3487
5159
|
txBackend,
|
|
3488
5160
|
target.graphId,
|
|
3489
|
-
target.graph
|
|
5161
|
+
target.graph,
|
|
5162
|
+
await storeRuntime(target).identityAssertionsAtTarget(txBackend, "state")
|
|
3490
5163
|
);
|
|
3491
5164
|
const expectedContent = contentComponentOf(expectedBaseVersion);
|
|
3492
5165
|
if (liveContent !== expectedContent) {
|
|
@@ -3540,11 +5213,41 @@ async function validateBaseVersions(target, branches) {
|
|
|
3540
5213
|
}
|
|
3541
5214
|
return ok(targetVersion);
|
|
3542
5215
|
}
|
|
3543
|
-
function tryNormalize(optionsInput) {
|
|
5216
|
+
function tryNormalize(optionsInput, refusedOptions = []) {
|
|
5217
|
+
const refusedOption = refusedOptions.find(
|
|
5218
|
+
(option) => hasOwnKey(optionsInput, option)
|
|
5219
|
+
);
|
|
5220
|
+
if (refusedOption !== void 0) {
|
|
5221
|
+
return err(
|
|
5222
|
+
new InvalidMergeOptionsError(
|
|
5223
|
+
`This merge operation does not accept options.${refusedOption}.`,
|
|
5224
|
+
{ details: { option: refusedOption } }
|
|
5225
|
+
)
|
|
5226
|
+
);
|
|
5227
|
+
}
|
|
3544
5228
|
try {
|
|
3545
5229
|
return ok(normalizeMergeOptions(optionsInput));
|
|
3546
5230
|
} catch (error) {
|
|
3547
|
-
return err(
|
|
5231
|
+
return err(
|
|
5232
|
+
new InvalidMergeOptionsError("Invalid merge options.", { cause: error })
|
|
5233
|
+
);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
async function tryOpenProvenanceStore(target) {
|
|
5237
|
+
try {
|
|
5238
|
+
return ok(await openProvenanceStore(target));
|
|
5239
|
+
} catch (error) {
|
|
5240
|
+
const code = error instanceof TypeGraphError ? error.details?.code : void 0;
|
|
5241
|
+
const details = {
|
|
5242
|
+
option: "persistProvenance",
|
|
5243
|
+
graphId: provenanceGraphId(target.graphId),
|
|
5244
|
+
targetGraphId: target.graphId,
|
|
5245
|
+
...typeof code === "string" ? { provenanceErrorCode: code } : {}
|
|
5246
|
+
};
|
|
5247
|
+
const message = `options.persistProvenance was requested but the merge-provenance sidecar cannot be opened: ${describeCause(error)}`;
|
|
5248
|
+
return err(
|
|
5249
|
+
code === "GRAPH_MERGE_PROVENANCE_ID_COLLISION" || code === "GRAPH_MERGE_PROVENANCE_CLAIM_UNFENCED" ? new InvalidMergeOptionsError(message, { details, cause: error }) : new MergeError(message, { details, cause: error })
|
|
5250
|
+
);
|
|
3548
5251
|
}
|
|
3549
5252
|
}
|
|
3550
5253
|
async function resolveMerge(store, target, branches, options, useBaseSources, incremental, expectedBaseVersion) {
|
|
@@ -3570,9 +5273,44 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3570
5273
|
targetBranchSeen = true;
|
|
3571
5274
|
}
|
|
3572
5275
|
}
|
|
5276
|
+
for (const branch2 of branches) {
|
|
5277
|
+
const branchSchemaRow = await storeBackend(branch2.store).getActiveSchema(
|
|
5278
|
+
branch2.store.graphId
|
|
5279
|
+
);
|
|
5280
|
+
let drifted;
|
|
5281
|
+
if ("schemaAnchor" in branch2) {
|
|
5282
|
+
const anchor = branch2.schemaAnchor;
|
|
5283
|
+
drifted = anchor === void 0 ? branchSchemaRow !== void 0 : branchSchemaRow?.version !== anchor.version || branchSchemaRow.schema_hash !== anchor.hash;
|
|
5284
|
+
} else {
|
|
5285
|
+
const sourceSchemaRow = await storeBackend(store).getActiveSchema(
|
|
5286
|
+
store.graphId
|
|
5287
|
+
);
|
|
5288
|
+
drifted = branchSchemaRow?.schema_hash !== sourceSchemaRow?.schema_hash;
|
|
5289
|
+
}
|
|
5290
|
+
if (drifted) {
|
|
5291
|
+
return err(
|
|
5292
|
+
new BaseVersionMismatchError(
|
|
5293
|
+
`Branch "${branch2.id}"'s store has a committed schema different from its at-fork state \u2014 a schema operation ran on the branch after forking. Merge carries data changes only; apply the schema change to the target first (or re-fork), then merge.`,
|
|
5294
|
+
{
|
|
5295
|
+
details: {
|
|
5296
|
+
branchId: branch2.id,
|
|
5297
|
+
branchSchemaVersion: branchSchemaRow?.version,
|
|
5298
|
+
branchSchemaHash: branchSchemaRow?.schema_hash,
|
|
5299
|
+
anchor: "schemaAnchor" in branch2 ? branch2.schemaAnchor : "absent"
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
)
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
const provenanceStore = options.persistProvenance ? await tryOpenProvenanceStore(target) : void 0;
|
|
5307
|
+
if (provenanceStore !== void 0 && isErr(provenanceStore)) {
|
|
5308
|
+
return provenanceStore;
|
|
5309
|
+
}
|
|
3573
5310
|
try {
|
|
3574
5311
|
const preferredBranchId = incremental?.targetBranchId;
|
|
3575
5312
|
const staging = await stageBranches(store, branches, preferredBranchId);
|
|
5313
|
+
const stagedNewByKind = newNodesByKind(staging);
|
|
3576
5314
|
const branchIds = branches.map((branch2) => branch2.id);
|
|
3577
5315
|
const branchOrder = preferredBranchId === void 0 ? options.branchOrder ?? [] : [
|
|
3578
5316
|
preferredBranchId,
|
|
@@ -3588,7 +5326,7 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3588
5326
|
}
|
|
3589
5327
|
const subClassClosure = buildSubClassClosure(introspection.ontology);
|
|
3590
5328
|
const embeddings = options.embedder === void 0 ? void 0 : await precomputeEmbeddings(
|
|
3591
|
-
|
|
5329
|
+
stagedNewByKind,
|
|
3592
5330
|
options.resolve,
|
|
3593
5331
|
options.embedder
|
|
3594
5332
|
);
|
|
@@ -3608,6 +5346,35 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3608
5346
|
if (isErr(candidates)) {
|
|
3609
5347
|
return err(candidates.error);
|
|
3610
5348
|
}
|
|
5349
|
+
const identityProbeIds = target.graph.identity === void 0 ? void 0 : [
|
|
5350
|
+
.../* @__PURE__ */ new Set([
|
|
5351
|
+
...[...stagedNewByKind.values()].flatMap(
|
|
5352
|
+
(entries) => entries.map((entry) => entry.node.id)
|
|
5353
|
+
),
|
|
5354
|
+
...staging.modifiedNodes.map((entry) => entry.node.id),
|
|
5355
|
+
...candidates.data.baseMembers.map((member) => member.id),
|
|
5356
|
+
...staging.newIdentityAssertions.flatMap((staged) => [
|
|
5357
|
+
staged.assertion.a.id,
|
|
5358
|
+
staged.assertion.b.id
|
|
5359
|
+
])
|
|
5360
|
+
])
|
|
5361
|
+
];
|
|
5362
|
+
const targetPeers = identityProbeIds === void 0 ? [] : await storeRuntime(target).liveNodesSharingIds(identityProbeIds);
|
|
5363
|
+
const stagedIdentityIds = [
|
|
5364
|
+
.../* @__PURE__ */ new Set([
|
|
5365
|
+
...staging.newIdentityAssertions.map((staged) => staged.assertion.id),
|
|
5366
|
+
...staging.retractedIdentityAssertions.map(
|
|
5367
|
+
(staged) => staged.assertion.id
|
|
5368
|
+
)
|
|
5369
|
+
])
|
|
5370
|
+
];
|
|
5371
|
+
const storedIdentityRowsById = stagedIdentityIds.length === 0 || target.graph.identity === void 0 ? NO_STORED_ASSERTIONS : await storeRuntime(target).identityAssertionRowsByIds(
|
|
5372
|
+
stagedIdentityIds
|
|
5373
|
+
);
|
|
5374
|
+
const identityContext = {
|
|
5375
|
+
sameIdAcrossKinds: target.graph.identity?.sameIdAcrossKinds,
|
|
5376
|
+
areDisjoint: (left, right) => target.registry.areDisjoint(left, right)
|
|
5377
|
+
};
|
|
3611
5378
|
const plan = planMerge(
|
|
3612
5379
|
staging,
|
|
3613
5380
|
candidates.data.edges,
|
|
@@ -3616,12 +5383,63 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3616
5383
|
options,
|
|
3617
5384
|
branchRank,
|
|
3618
5385
|
subClassClosure,
|
|
5386
|
+
identityContext,
|
|
5387
|
+
storedIdentityRowsById,
|
|
5388
|
+
targetPeers,
|
|
3619
5389
|
preferredBranchId
|
|
3620
5390
|
);
|
|
5391
|
+
if (plan.identityAssertions.length > 0) {
|
|
5392
|
+
assertOneIdOneTruth(
|
|
5393
|
+
plan.identityAssertions,
|
|
5394
|
+
await storeRuntime(target).identityAssertionRowsByIds(
|
|
5395
|
+
plan.identityAssertions.map((assertion) => assertion.id)
|
|
5396
|
+
)
|
|
5397
|
+
);
|
|
5398
|
+
}
|
|
5399
|
+
let identityGuard;
|
|
5400
|
+
if (identityProbeIds !== void 0 && incremental !== void 0) {
|
|
5401
|
+
const freshPeers = await storeRuntime(target).liveNodesSharingIds(identityProbeIds);
|
|
5402
|
+
const built = await buildIdentityPeerProbe(
|
|
5403
|
+
target,
|
|
5404
|
+
identityProbeIds,
|
|
5405
|
+
freshPeers,
|
|
5406
|
+
plan,
|
|
5407
|
+
target.graph.identity?.sameIdAcrossKinds ?? "ignore"
|
|
5408
|
+
);
|
|
5409
|
+
const canonicalIdentityOf = plan.canonicalOf;
|
|
5410
|
+
assertNoContradictoryIdentityClosure(
|
|
5411
|
+
plan.identityAssertions,
|
|
5412
|
+
plan.identityRetractions.map((retraction) => retraction.id),
|
|
5413
|
+
// The FRESH target ledger, not the pre-planning staging capture —
|
|
5414
|
+
// a `different` committed in the window must invalidate the plan
|
|
5415
|
+
// here, as a typed conflict, before it can become the baseline.
|
|
5416
|
+
// Connectivity is REBUILT from this deletion-filtered ledger (plus
|
|
5417
|
+
// the fold unions inside the checker) rather than from the old
|
|
5418
|
+
// closure's member lists: filtering a deleted member out of a class
|
|
5419
|
+
// does not model the post-deletion state — deleting a bridge ends
|
|
5420
|
+
// its assertions and SPLITS the class, so pre-linking the filtered
|
|
5421
|
+
// remainder would falsely reject a plan that deletes the bridge and
|
|
5422
|
+
// asserts the ends different.
|
|
5423
|
+
built.ledger,
|
|
5424
|
+
new Set(plan.nodeDeletions.keys()),
|
|
5425
|
+
canonicalIdentityOf,
|
|
5426
|
+
plan.retypeMap,
|
|
5427
|
+
identityContext,
|
|
5428
|
+
// The snapshot's (deletion-filtered) class MEMBERS join the universe
|
|
5429
|
+
// as unlinked refs: their same-id fold links at ids outside the probe
|
|
5430
|
+
// range are re-derived by the checker's fold union, and their
|
|
5431
|
+
// assertion links come from the fresh ledger — never from the old
|
|
5432
|
+
// class shape itself.
|
|
5433
|
+
[...built.probe.seeds, ...built.snapshot.groups.flat()]
|
|
5434
|
+
);
|
|
5435
|
+
identityGuard = built.probe;
|
|
5436
|
+
}
|
|
3621
5437
|
const merged = incremental === void 0 ? await commitPlan(target, plan, expectedBaseVersion) : await commitIncrementalPlan(target, plan, {
|
|
3622
|
-
stagedNewByKind
|
|
5438
|
+
stagedNewByKind,
|
|
3623
5439
|
options,
|
|
3624
5440
|
introspectionKinds,
|
|
5441
|
+
forkPoint: incremental.forkPoint,
|
|
5442
|
+
...identityGuard === void 0 ? {} : { identityPeerProbe: identityGuard },
|
|
3625
5443
|
// The committed (kind, id) keys the base sources matched at PLAN time;
|
|
3626
5444
|
// anything NEW the in-tx re-probe surfaces is a window write.
|
|
3627
5445
|
plannedBaseMatchKeys: new Set(
|
|
@@ -3633,11 +5451,10 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3633
5451
|
const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
|
|
3634
5452
|
const warnings = [...plan.warnings];
|
|
3635
5453
|
let provenancePersisted;
|
|
3636
|
-
if (
|
|
5454
|
+
if (provenanceStore !== void 0 && !isErr(provenanceStore)) {
|
|
3637
5455
|
try {
|
|
3638
|
-
const provenanceStore = await openProvenanceStore(target);
|
|
3639
5456
|
const count = await persistProvenanceRecords(
|
|
3640
|
-
provenanceStore,
|
|
5457
|
+
provenanceStore.data,
|
|
3641
5458
|
target.graphId,
|
|
3642
5459
|
plan.provenanceRecords
|
|
3643
5460
|
);
|
|
@@ -3658,6 +5475,7 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3658
5475
|
deleteModifyConflicts: plan.deleteModifyConflicts,
|
|
3659
5476
|
typeReconciliations: plan.typeReconciliations,
|
|
3660
5477
|
dropped: plan.dropped,
|
|
5478
|
+
validityEnds: plan.validityEnds,
|
|
3661
5479
|
baseAmbiguities: plan.baseAmbiguities,
|
|
3662
5480
|
provenance,
|
|
3663
5481
|
warnings,
|
|
@@ -3751,7 +5569,7 @@ function analyzeRevalidatingWrite(schema, current, planned) {
|
|
|
3751
5569
|
};
|
|
3752
5570
|
}
|
|
3753
5571
|
function nodeProps2(node) {
|
|
3754
|
-
const props =
|
|
5572
|
+
const props = createDataKeyedBag();
|
|
3755
5573
|
for (const [key, value] of Object.entries(node)) {
|
|
3756
5574
|
if (key === "id" || key === "kind" || key === "meta") continue;
|
|
3757
5575
|
props[key] = value;
|
|
@@ -3759,7 +5577,7 @@ function nodeProps2(node) {
|
|
|
3759
5577
|
return props;
|
|
3760
5578
|
}
|
|
3761
5579
|
function edgeProps(edge) {
|
|
3762
|
-
const props =
|
|
5580
|
+
const props = createDataKeyedBag();
|
|
3763
5581
|
for (const [key, value] of Object.entries(edge)) {
|
|
3764
5582
|
if (key === "id" || key === "kind" || key === "fromKind" || key === "fromId" || key === "toKind" || key === "toId" || key === "meta") {
|
|
3765
5583
|
continue;
|
|
@@ -3769,70 +5587,14 @@ function edgeProps(edge) {
|
|
|
3769
5587
|
return props;
|
|
3770
5588
|
}
|
|
3771
5589
|
var INCLUDE_TOMBSTONES = { temporalMode: "includeTombstones" };
|
|
3772
|
-
function finalEdgeFrom(edge, plan) {
|
|
3773
|
-
return {
|
|
3774
|
-
kind: plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId)) ?? edge.fromKind,
|
|
3775
|
-
id: edge.fromId
|
|
3776
|
-
};
|
|
3777
|
-
}
|
|
3778
|
-
function finalEdgeTo(edge, plan) {
|
|
3779
|
-
return {
|
|
3780
|
-
kind: plan.retypeMap.get(mergeKey(edge.toKind, edge.toId)) ?? edge.toKind,
|
|
3781
|
-
id: edge.toId
|
|
3782
|
-
};
|
|
3783
|
-
}
|
|
3784
5590
|
function assertValidRevalidatingWrite(analysis, message, details) {
|
|
3785
5591
|
if (analysis.status === "invalid") {
|
|
3786
5592
|
throw new MergeError(message, { details });
|
|
3787
5593
|
}
|
|
3788
5594
|
}
|
|
3789
|
-
function describeCause(cause) {
|
|
3790
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
3791
|
-
}
|
|
3792
|
-
function plannedNodeWrites(plan) {
|
|
3793
|
-
const canonicalIdentities = /* @__PURE__ */ new Set();
|
|
3794
|
-
for (const entity of plan.canonicalEntities) {
|
|
3795
|
-
canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
|
|
3796
|
-
}
|
|
3797
|
-
const survivingModProps = /* @__PURE__ */ new Map();
|
|
3798
|
-
for (const modification of plan.survivingModifications) {
|
|
3799
|
-
survivingModProps.set(
|
|
3800
|
-
mergeKeyOf(modification.node),
|
|
3801
|
-
modification.node.forkProps
|
|
3802
|
-
);
|
|
3803
|
-
}
|
|
3804
|
-
const writes = [];
|
|
3805
|
-
for (const modification of plan.survivingModifications) {
|
|
3806
|
-
const identity = mergeKeyOf(modification.node);
|
|
3807
|
-
if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
|
|
3808
|
-
continue;
|
|
3809
|
-
}
|
|
3810
|
-
writes.push({
|
|
3811
|
-
identity,
|
|
3812
|
-
kind: modification.node.kind,
|
|
3813
|
-
id: modification.node.id,
|
|
3814
|
-
props: modification.node.forkProps
|
|
3815
|
-
});
|
|
3816
|
-
}
|
|
3817
|
-
for (const entity of plan.canonicalEntities) {
|
|
3818
|
-
const sourceIdentity = mergeKey(entity.kind, entity.canonicalId);
|
|
3819
|
-
if (plan.nodeDeletions.has(sourceIdentity)) {
|
|
3820
|
-
continue;
|
|
3821
|
-
}
|
|
3822
|
-
const kind = plan.retypeMap.get(sourceIdentity) ?? entity.kind;
|
|
3823
|
-
const forkProps = survivingModProps.get(sourceIdentity);
|
|
3824
|
-
writes.push({
|
|
3825
|
-
identity: mergeKey(kind, entity.canonicalId),
|
|
3826
|
-
kind,
|
|
3827
|
-
id: entity.canonicalId,
|
|
3828
|
-
props: forkProps === void 0 ? entity.props : { ...entity.props, ...forkProps }
|
|
3829
|
-
});
|
|
3830
|
-
}
|
|
3831
|
-
return writes;
|
|
3832
|
-
}
|
|
3833
5595
|
function edgeWriteSignature(edge, plan) {
|
|
3834
|
-
const from =
|
|
3835
|
-
const to =
|
|
5596
|
+
const from = finalEdgeEndpoint(plan, edge.fromKind, edge.fromId);
|
|
5597
|
+
const to = finalEdgeEndpoint(plan, edge.toKind, edge.toId);
|
|
3836
5598
|
return JSON.stringify([
|
|
3837
5599
|
edge.kind,
|
|
3838
5600
|
from.kind,
|
|
@@ -3874,7 +5636,7 @@ async function validateIncrementalNodeWrites(target, nodesApi, plan) {
|
|
|
3874
5636
|
const analysis = analyzeRevalidatingWrite(
|
|
3875
5637
|
nodeSchemaFor(target, write.kind),
|
|
3876
5638
|
nodeProps2(current),
|
|
3877
|
-
write.
|
|
5639
|
+
nodeWriteProps(write, (modification) => modification.forkProps)
|
|
3878
5640
|
);
|
|
3879
5641
|
assertValidRevalidatingWrite(
|
|
3880
5642
|
analysis,
|
|
@@ -3975,8 +5737,8 @@ async function validateIncrementalEdgeWrites(target, edgesApi, plan) {
|
|
|
3975
5737
|
{ details: { id: edge.id, committedKind: current.kind } }
|
|
3976
5738
|
);
|
|
3977
5739
|
}
|
|
3978
|
-
const from =
|
|
3979
|
-
const to =
|
|
5740
|
+
const from = finalEdgeEndpoint(plan, edge.fromKind, edge.fromId);
|
|
5741
|
+
const to = finalEdgeEndpoint(plan, edge.toKind, edge.toId);
|
|
3980
5742
|
if (current.fromId !== from.id || current.fromKind !== from.kind || current.toId !== to.id || current.toKind !== to.kind) {
|
|
3981
5743
|
throw new MergeError(
|
|
3982
5744
|
`mergeIncremental() would overwrite committed edge "${edge.id}" (kind "${edge.kind}") with different endpoints.`,
|
|
@@ -4148,6 +5910,20 @@ async function assertInheritedEdgesUnchanged(edgesApi, guard, plan) {
|
|
|
4148
5910
|
)
|
|
4149
5911
|
});
|
|
4150
5912
|
}
|
|
5913
|
+
async function assertForkPointUnchanged(precondition) {
|
|
5914
|
+
const liveVersion = await computeBaseVersion(precondition.store);
|
|
5915
|
+
if (liveVersion === precondition.version) return;
|
|
5916
|
+
throw new BaseVersionMismatchError(
|
|
5917
|
+
"The mergeIncremental() fork point was modified between the fork-point precondition and the commit transaction; every branch diff was computed against the previous fork-point state, so the resolved plan was not applied.",
|
|
5918
|
+
{
|
|
5919
|
+
details: {
|
|
5920
|
+
expectedForkPointBase: precondition.version,
|
|
5921
|
+
liveForkPointBase: liveVersion
|
|
5922
|
+
},
|
|
5923
|
+
suggestion: "Keep the fork-point store immutable for the duration of the merge (it is the diff reference, not a merge target), then re-run mergeIncremental()."
|
|
5924
|
+
}
|
|
5925
|
+
);
|
|
5926
|
+
}
|
|
4151
5927
|
async function commitIncrementalPlan(target, plan, guard) {
|
|
4152
5928
|
if (!storeBackend(target).capabilities.transactions) {
|
|
4153
5929
|
throw new MergeError(
|
|
@@ -4160,28 +5936,48 @@ async function commitIncrementalPlan(target, plan, guard) {
|
|
|
4160
5936
|
if (target.revisionTrackingEnabled) {
|
|
4161
5937
|
await lockRecordedGraphWrite(transactionBackend(tx), target.graphId);
|
|
4162
5938
|
}
|
|
5939
|
+
await assertForkPointUnchanged(guard.forkPoint);
|
|
4163
5940
|
const nodesApi = tx.nodes;
|
|
4164
5941
|
const edgesApi = tx.edges;
|
|
4165
5942
|
await assertBaseResolutionStable(tx, guard);
|
|
5943
|
+
await assertPlannedIdentityIdsFresh(target, transactionBackend(tx), plan);
|
|
5944
|
+
await assertIdentityPeersStable(
|
|
5945
|
+
target,
|
|
5946
|
+
transactionBackend(tx),
|
|
5947
|
+
guard.identityPeerProbe,
|
|
5948
|
+
plan
|
|
5949
|
+
);
|
|
4166
5950
|
await assertInheritedTargetUnchanged(nodesApi, edgesApi, guard, plan);
|
|
4167
5951
|
await validateIncrementalNodeWrites(target, nodesApi, plan);
|
|
4168
5952
|
await validateIncrementalEdgeWrites(target, edgesApi, plan);
|
|
4169
|
-
return applyMergePlan(
|
|
5953
|
+
return applyMergePlan(
|
|
5954
|
+
plan,
|
|
5955
|
+
nodesApi,
|
|
5956
|
+
edgesApi,
|
|
5957
|
+
target,
|
|
5958
|
+
transactionBackend(tx)
|
|
5959
|
+
);
|
|
4170
5960
|
}, mergeCommitTransactionOptions(target))
|
|
4171
5961
|
);
|
|
4172
5962
|
}
|
|
4173
5963
|
async function mergeIncremental(args) {
|
|
4174
5964
|
const { forkPoint, target, branches } = args;
|
|
4175
|
-
const normalized = tryNormalize(args.options ?? {});
|
|
5965
|
+
const normalized = tryNormalize(args.options ?? {}, ["target"]);
|
|
4176
5966
|
if (isErr(normalized)) {
|
|
4177
5967
|
return err(normalized.error);
|
|
4178
5968
|
}
|
|
4179
5969
|
const options = normalized.data;
|
|
4180
5970
|
if (options.onBasePropertyConflict !== "flag") {
|
|
4181
5971
|
return err(
|
|
4182
|
-
new
|
|
5972
|
+
new InvalidMergeOptionsError(
|
|
4183
5973
|
'mergeIncremental() requires onBasePropertyConflict: "flag" (keep-base); a non-keep-base policy could overwrite a newer committed base value with a stale branch value.',
|
|
4184
|
-
{
|
|
5974
|
+
{
|
|
5975
|
+
details: {
|
|
5976
|
+
option: "onBasePropertyConflict",
|
|
5977
|
+
accepted: "flag",
|
|
5978
|
+
received: options.onBasePropertyConflict
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
4185
5981
|
)
|
|
4186
5982
|
);
|
|
4187
5983
|
}
|
|
@@ -4213,10 +6009,16 @@ async function mergeIncremental(args) {
|
|
|
4213
6009
|
[targetBranch, ...branches],
|
|
4214
6010
|
options,
|
|
4215
6011
|
true,
|
|
4216
|
-
{
|
|
6012
|
+
{
|
|
6013
|
+
targetBranchId: COMMITTED_TARGET_BRANCH,
|
|
6014
|
+
// The fork-point precondition just validated, carried to the commit so
|
|
6015
|
+
// it is re-established there rather than assumed to have held for the
|
|
6016
|
+
// whole of planning (see `assertForkPointUnchanged`).
|
|
6017
|
+
forkPoint: { store: forkPoint, version: forkVersion }
|
|
6018
|
+
}
|
|
4217
6019
|
);
|
|
4218
6020
|
}
|
|
4219
6021
|
|
|
4220
|
-
export { BaseVersionMismatchError, BranchError, MERGE_ERROR_CODES, MERGE_OPTION_DEFAULTS, MergeConflictError, MergeError, SimilarityUnavailableError, asBaseVersion, asBranchId, branch, cloneWorkingCopyStrategy, computeBaseVersion, merge, mergeIncremental, normalizeMergeOptions, openProvenanceStore, persistProvenanceRecords, provenanceGraphId, readProvenance };
|
|
6022
|
+
export { BaseVersionMismatchError, BranchError, IdentityMergeConflictError, InvalidMergeOptionsError, MERGE_ERROR_CODES, MERGE_OPTION_DEFAULTS, MergeConflictError, MergeError, SimilarityUnavailableError, VALIDITY_END_TARGET_PRECEDENCE, asBaseVersion, asBranchId, branch, cloneWorkingCopyStrategy, computeBaseVersion, merge, mergeIncremental, normalizeMergeOptions, openProvenanceStore, persistProvenanceRecords, provenanceGraphId, readProvenance };
|
|
4221
6023
|
//# sourceMappingURL=index.js.map
|
|
4222
6024
|
//# sourceMappingURL=index.js.map
|