@nicia-ai/typegraph 0.44.0 → 0.46.0
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 -43
- 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 -39
- 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 -44
- 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 -40
- 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-XMYJY7NI.js → chunk-2LG2XB2M.js} +3 -3
- package/dist/{chunk-XMYJY7NI.js.map → chunk-2LG2XB2M.js.map} +1 -1
- package/dist/{chunk-QGKILWGZ.cjs → chunk-2NA5DYLO.cjs} +4 -4
- package/dist/{chunk-QGKILWGZ.cjs.map → chunk-2NA5DYLO.cjs.map} +1 -1
- package/dist/{chunk-5MUSU4ZP.cjs → chunk-3DHZCOT7.cjs} +20 -20
- package/dist/chunk-3DHZCOT7.cjs.map +1 -0
- package/dist/{chunk-OVUUTCPJ.cjs → chunk-4UGOKMI5.cjs} +268 -150
- package/dist/chunk-4UGOKMI5.cjs.map +1 -0
- package/dist/{chunk-FRHHDHKH.js → chunk-5262ZMTR.js} +26 -5
- package/dist/chunk-5262ZMTR.js.map +1 -0
- package/dist/{chunk-HOIFFY4M.js → chunk-5763DULU.js} +248 -76
- package/dist/chunk-5763DULU.js.map +1 -0
- package/dist/{chunk-7G32LIQM.js → chunk-5UVRGXD3.js} +277 -7
- package/dist/chunk-5UVRGXD3.js.map +1 -0
- package/dist/{chunk-IIT5K7SK.cjs → chunk-62ZP23AW.cjs} +15 -15
- package/dist/{chunk-IIT5K7SK.cjs.map → chunk-62ZP23AW.cjs.map} +1 -1
- package/dist/{chunk-S4KQ5LV4.js → chunk-775A4ZMN.js} +40 -6
- package/dist/chunk-775A4ZMN.js.map +1 -0
- package/dist/{chunk-QYWINYEA.cjs → chunk-7BQXPCGN.cjs} +830 -170
- 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-7GT72YHH.cjs +1979 -0
- package/dist/chunk-7GT72YHH.cjs.map +1 -0
- package/dist/{chunk-SQRD2CYF.js → chunk-7VQXLT5O.js} +717 -76
- package/dist/chunk-7VQXLT5O.js.map +1 -0
- package/dist/{chunk-BCP7LLOY.js → chunk-7Z5OR7KO.js} +2706 -625
- package/dist/chunk-7Z5OR7KO.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-FMMIX6N4.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-PAW2XUZN.js → chunk-ASTI57H3.js} +3 -3
- package/dist/{chunk-PAW2XUZN.js.map → chunk-ASTI57H3.js.map} +1 -1
- package/dist/{chunk-FMGEZBAQ.js → chunk-BKCUOV7A.js} +27 -5
- package/dist/chunk-BKCUOV7A.js.map +1 -0
- package/dist/chunk-BSFJMMV3.cjs +268 -0
- package/dist/chunk-BSFJMMV3.cjs.map +1 -0
- package/dist/{chunk-63KCKSSH.js → chunk-C23WSN5E.js} +209 -24
- package/dist/chunk-C23WSN5E.js.map +1 -0
- package/dist/{chunk-JRP3VGHS.js → chunk-CBTIUB76.js} +3 -3
- package/dist/{chunk-JRP3VGHS.js.map → chunk-CBTIUB76.js.map} +1 -1
- package/dist/chunk-CNV4TXJG.js +58 -0
- package/dist/chunk-CNV4TXJG.js.map +1 -0
- package/dist/{chunk-QB6VMOJ2.cjs → chunk-CXBHOOIQ.cjs} +45 -10
- package/dist/chunk-CXBHOOIQ.cjs.map +1 -0
- package/dist/{chunk-46GDII45.js → chunk-DCQVHAYQ.js} +5 -5
- package/dist/{chunk-46GDII45.js.map → chunk-DCQVHAYQ.js.map} +1 -1
- package/dist/chunk-DLNROMQ7.js +306 -0
- package/dist/chunk-DLNROMQ7.js.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-BNUFUFAU.js → chunk-HFHTMECK.js} +7 -7
- package/dist/chunk-HFHTMECK.js.map +1 -0
- package/dist/{chunk-TFMVL4QH.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-JIVMTW7W.js +3393 -0
- package/dist/chunk-JIVMTW7W.js.map +1 -0
- package/dist/{chunk-2Y4IFKW7.js → chunk-JXA2ZP2Q.js} +6 -17
- 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-K5ELX2GT.js +248 -0
- package/dist/chunk-K5ELX2GT.js.map +1 -0
- package/dist/chunk-LBJKZXDE.cjs +257 -0
- package/dist/chunk-LBJKZXDE.cjs.map +1 -0
- package/dist/{chunk-FGO5JFVJ.cjs → chunk-M4BDU5RX.cjs} +12 -12
- package/dist/{chunk-FGO5JFVJ.cjs.map → chunk-M4BDU5RX.cjs.map} +1 -1
- package/dist/{chunk-MIJKEVV7.js → chunk-MSY7FSX4.js} +37 -4
- package/dist/chunk-MSY7FSX4.js.map +1 -0
- package/dist/{chunk-ST3LPB2V.cjs → chunk-MUK4BU7K.cjs} +788 -146
- package/dist/chunk-MUK4BU7K.cjs.map +1 -0
- package/dist/{chunk-MCHP7RHZ.cjs → chunk-MXJDTH5B.cjs} +64 -41
- package/dist/chunk-MXJDTH5B.cjs.map +1 -0
- package/dist/{chunk-2VLBGQBY.cjs → chunk-N3257XE6.cjs} +724 -577
- package/dist/chunk-N3257XE6.cjs.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-EE7N5XXQ.cjs → chunk-NK5Q7UNE.cjs} +10 -21
- package/dist/chunk-NK5Q7UNE.cjs.map +1 -0
- package/dist/{chunk-PHXITZE4.js → chunk-NMWDXFPH.js} +4 -4
- package/dist/{chunk-PHXITZE4.js.map → chunk-NMWDXFPH.js.map} +1 -1
- package/dist/{chunk-FSXTPBRN.js → chunk-NQ6BRJBE.js} +416 -850
- package/dist/chunk-NQ6BRJBE.js.map +1 -0
- package/dist/chunk-OH5E5KIO.js +2057 -0
- package/dist/chunk-OH5E5KIO.js.map +1 -0
- package/dist/chunk-ONU3HIED.cjs +2082 -0
- package/dist/chunk-ONU3HIED.cjs.map +1 -0
- package/dist/{chunk-STQ3BSED.cjs → chunk-PNN7RHVR.cjs} +66 -44
- package/dist/chunk-PNN7RHVR.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-ACSVZPK7.cjs → chunk-PWLJ77UV.cjs} +5 -3
- package/dist/chunk-PWLJ77UV.cjs.map +1 -0
- package/dist/chunk-Q7Q4WSLJ.cjs +328 -0
- package/dist/chunk-Q7Q4WSLJ.cjs.map +1 -0
- package/dist/{chunk-BEFM74L2.js → chunk-QLRESYPU.js} +637 -493
- package/dist/chunk-QLRESYPU.js.map +1 -0
- package/dist/{chunk-SBPC5IP3.js → chunk-QWX5YA3K.js} +784 -202
- package/dist/chunk-QWX5YA3K.js.map +1 -0
- package/dist/{chunk-TCRIQMBU.js → chunk-RJS7DECT.js} +8 -9
- package/dist/chunk-RJS7DECT.js.map +1 -0
- package/dist/{chunk-6JZUSDY2.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-IJTLXNGJ.js → chunk-T4XNKYXB.js} +780 -127
- package/dist/chunk-T4XNKYXB.js.map +1 -0
- package/dist/{chunk-PDGEXGS6.cjs → chunk-TQR4KBY2.cjs} +354 -182
- package/dist/chunk-TQR4KBY2.cjs.map +1 -0
- package/dist/chunk-TTAITEPJ.cjs +755 -0
- package/dist/chunk-TTAITEPJ.cjs.map +1 -0
- package/dist/{chunk-NN3QSOVS.cjs → chunk-U5SON7R2.cjs} +27 -27
- package/dist/{chunk-NN3QSOVS.cjs.map → chunk-U5SON7R2.cjs.map} +1 -1
- package/dist/{chunk-A2I2XO52.cjs → chunk-U63GDNRC.cjs} +32 -26
- package/dist/chunk-U63GDNRC.cjs.map +1 -0
- package/dist/{chunk-YNULOIR5.cjs → chunk-UDISNXDL.cjs} +64 -43
- package/dist/chunk-UDISNXDL.cjs.map +1 -0
- package/dist/{chunk-GWEDD3DH.js → chunk-UV3SN74I.js} +5 -5
- package/dist/{chunk-GWEDD3DH.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-EFELJVLA.js → chunk-VGTJPTIC.js} +28 -5
- package/dist/chunk-VGTJPTIC.js.map +1 -0
- package/dist/chunk-VQH4VAWD.cjs +12 -0
- package/dist/{chunk-EPTSMFH7.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-WEVQJ5LH.js +736 -0
- package/dist/chunk-WEVQJ5LH.js.map +1 -0
- package/dist/{chunk-3PRKZW6D.cjs → chunk-XBYVV3MP.cjs} +60 -5
- package/dist/chunk-XBYVV3MP.cjs.map +1 -0
- package/dist/{chunk-GFRGG2US.cjs → chunk-XCAGZAKP.cjs} +7 -7
- package/dist/{chunk-GFRGG2US.cjs.map → chunk-XCAGZAKP.cjs.map} +1 -1
- package/dist/{chunk-XR4YZFKW.cjs → chunk-XDF2UPEQ.cjs} +3864 -1783
- package/dist/chunk-XDF2UPEQ.cjs.map +1 -0
- package/dist/{chunk-7V2FGGI2.cjs → chunk-XMQSEJCF.cjs} +632 -1080
- package/dist/chunk-XMQSEJCF.cjs.map +1 -0
- package/dist/chunk-XNLIZYRZ.cjs +3432 -0
- package/dist/chunk-XNLIZYRZ.cjs.map +1 -0
- package/dist/{chunk-66UVLHQA.cjs → chunk-XNSI2FJO.cjs} +99 -100
- package/dist/chunk-XNSI2FJO.cjs.map +1 -0
- package/dist/chunk-XQSCL77R.cjs +60 -0
- package/dist/chunk-XQSCL77R.cjs.map +1 -0
- package/dist/{chunk-SVJ5GEXW.cjs → chunk-XUO3KQFO.cjs} +2 -2
- package/dist/chunk-XUO3KQFO.cjs.map +1 -0
- package/dist/{chunk-HJH3RMXG.cjs → chunk-YAAM3M2S.cjs} +10 -10
- package/dist/{chunk-HJH3RMXG.cjs.map → chunk-YAAM3M2S.cjs.map} +1 -1
- package/dist/{chunk-ATLN63JA.js → chunk-YBFHZUXX.js} +6 -6
- package/dist/{chunk-ATLN63JA.js.map → chunk-YBFHZUXX.js.map} +1 -1
- package/dist/{chunk-K3BUWSFR.js → chunk-YDFDBE6D.js} +119 -6
- package/dist/chunk-YDFDBE6D.js.map +1 -0
- package/dist/{chunk-OSZPLZIV.cjs → chunk-YVWGEQJE.cjs} +318 -132
- 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 -41
- package/dist/core/index.d.cts +4 -5
- package/dist/core/index.d.ts +4 -5
- package/dist/core/index.js +8 -9
- package/dist/{ddl-eWg7hx6R.d.cts → ddl-BJcOp4WZ.d.cts} +3 -3
- package/dist/{ddl-D5369kOY.d.ts → ddl-BXfGHbPN.d.ts} +3 -3
- package/dist/graph-extension/index.cjs +26 -23
- 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 -8
- package/dist/graph-merge/index.cjs +2459 -655
- 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 -468
- package/dist/graph-merge/index.js.map +1 -1
- package/dist/{index-By89Sezh.d.ts → index-BSHvKfok.d.ts} +221 -10
- package/dist/{index-DcQ3Sxs-.d.cts → index-DxaYCv8Y.d.cts} +221 -10
- package/dist/index.cjs +330 -269
- 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 -50
- 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 -50
- 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 -24
- package/dist/interchange/index.js.map +1 -1
- package/dist/{manager-B0KjKE93.d.ts → manager-CfZAimti.d.ts} +36 -5
- package/dist/{manager-Cn0NFIhN.d.cts → manager-HGk6E_HW.d.cts} +36 -5
- package/dist/{materialize-indexes-BD4V3svD.d.ts → materialize-indexes-6A7dWGtb.d.cts} +10 -2
- package/dist/{materialize-indexes-DVRhd5h4.d.cts → materialize-indexes-BMJP6Tms.d.ts} +10 -2
- package/dist/{postgres-DaEWvN4x.d.cts → postgres-DFYWENdR.d.cts} +1961 -439
- package/dist/{postgres-u0a0HN2D.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 -49
- package/dist/schema/index.d.cts +9 -16
- package/dist/schema/index.d.ts +9 -16
- package/dist/schema/index.js +23 -22
- package/dist/{searchable-CnYEancP.d.ts → searchable-9OQoPuRs.d.ts} +1 -2
- package/dist/{searchable-BT7j9_IK.d.cts → searchable-CIje5XYD.d.cts} +1 -2
- package/dist/{sqlite-BrMoSsI7.d.cts → sqlite-DerU5tc7.d.ts} +2029 -347
- package/dist/{sqlite-CHhZUyO5.d.ts → sqlite-DitnISa9.d.cts} +2029 -347
- package/dist/{store-view-Bvdku0Ht.d.ts → store-DawaWWjJ.d.ts} +3145 -2556
- package/dist/{store-view-w-md0nQ4.d.cts → store-RwTJx8LX.d.cts} +3145 -2556
- package/dist/{temporal-BCORwUau.d.cts → temporal-Bn-Fi8FJ.d.cts} +1 -1
- package/dist/{temporal--x9T8K4S.d.ts → temporal-BtM4UALv.d.ts} +1 -1
- package/dist/{types-6-WJmSD4.d.cts → types-CoIx8HX5.d.cts} +1 -1
- package/dist/{types-Cjvkj0jA.d.ts → types-HkveCCx0.d.cts} +866 -21
- package/dist/{types-Cjvkj0jA.d.cts → types-HkveCCx0.d.ts} +866 -21
- package/dist/{types-COODtTJU.d.ts → types-KFfk-8Ah.d.ts} +1 -1
- package/dist/{where-CUQmCR6d.d.cts → where--fk7_0kv.d.cts} +1 -1
- package/dist/{where-5xVawAwL.d.ts → where-ws5hf-Ov.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2BIG7U6E.cjs +0 -83
- package/dist/chunk-2BIG7U6E.cjs.map +0 -1
- package/dist/chunk-2L54C2C2.js +0 -144
- package/dist/chunk-2L54C2C2.js.map +0 -1
- package/dist/chunk-2VLBGQBY.cjs.map +0 -1
- package/dist/chunk-2WDD2NO3.js +0 -59
- package/dist/chunk-2WDD2NO3.js.map +0 -1
- package/dist/chunk-2Y4IFKW7.js.map +0 -1
- package/dist/chunk-35S6P5ZU.js +0 -27
- package/dist/chunk-35S6P5ZU.js.map +0 -1
- package/dist/chunk-3PRKZW6D.cjs.map +0 -1
- package/dist/chunk-4MB3AQAQ.js.map +0 -1
- package/dist/chunk-4SZ6XPTG.cjs +0 -12
- package/dist/chunk-5MUSU4ZP.cjs.map +0 -1
- package/dist/chunk-63KCKSSH.js.map +0 -1
- package/dist/chunk-66UVLHQA.cjs.map +0 -1
- package/dist/chunk-6JZUSDY2.cjs.map +0 -1
- package/dist/chunk-764JHTLT.cjs +0 -15
- package/dist/chunk-764JHTLT.cjs.map +0 -1
- package/dist/chunk-7G32LIQM.js.map +0 -1
- package/dist/chunk-7V2FGGI2.cjs.map +0 -1
- package/dist/chunk-A2I2XO52.cjs.map +0 -1
- package/dist/chunk-A4PBRX4C.cjs +0 -100
- package/dist/chunk-A4PBRX4C.cjs.map +0 -1
- package/dist/chunk-ACSVZPK7.cjs.map +0 -1
- package/dist/chunk-ALFUGRYB.cjs +0 -1282
- package/dist/chunk-ALFUGRYB.cjs.map +0 -1
- package/dist/chunk-B75XRV3O.js +0 -125
- package/dist/chunk-B75XRV3O.js.map +0 -1
- package/dist/chunk-BCP7LLOY.js.map +0 -1
- package/dist/chunk-BEFM74L2.js.map +0 -1
- package/dist/chunk-BNUFUFAU.js.map +0 -1
- package/dist/chunk-C4F7VJ5D.cjs.map +0 -1
- package/dist/chunk-CPEL3YIR.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-EE7N5XXQ.cjs.map +0 -1
- package/dist/chunk-EFELJVLA.js.map +0 -1
- package/dist/chunk-EPTSMFH7.cjs +0 -12
- 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-FMGEZBAQ.js.map +0 -1
- package/dist/chunk-FMMIX6N4.js.map +0 -1
- package/dist/chunk-FRHHDHKH.js.map +0 -1
- package/dist/chunk-FSXTPBRN.js.map +0 -1
- package/dist/chunk-G6Q5UM7U.js.map +0 -1
- package/dist/chunk-GYA4JRP5.js.map +0 -1
- package/dist/chunk-HOHAJ2NR.cjs +0 -61
- package/dist/chunk-HOHAJ2NR.cjs.map +0 -1
- package/dist/chunk-HOIFFY4M.js.map +0 -1
- package/dist/chunk-IIMQJYAM.js +0 -81
- package/dist/chunk-IIMQJYAM.js.map +0 -1
- package/dist/chunk-IJTLXNGJ.js.map +0 -1
- package/dist/chunk-ITE4IXAK.js +0 -25
- package/dist/chunk-ITE4IXAK.js.map +0 -1
- package/dist/chunk-K3BUWSFR.js.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-MCHP7RHZ.cjs.map +0 -1
- package/dist/chunk-MIJKEVV7.js.map +0 -1
- package/dist/chunk-NCX65HBY.cjs +0 -1396
- package/dist/chunk-NCX65HBY.cjs.map +0 -1
- package/dist/chunk-OSZPLZIV.cjs.map +0 -1
- package/dist/chunk-OVUUTCPJ.cjs.map +0 -1
- package/dist/chunk-PDGEXGS6.cjs.map +0 -1
- package/dist/chunk-QB6VMOJ2.cjs.map +0 -1
- package/dist/chunk-QMQLUA66.js +0 -12
- package/dist/chunk-QMQLUA66.js.map +0 -1
- package/dist/chunk-QYWINYEA.cjs.map +0 -1
- package/dist/chunk-S4KQ5LV4.js.map +0 -1
- package/dist/chunk-SBPC5IP3.js.map +0 -1
- package/dist/chunk-SJDMVITW.cjs.map +0 -1
- package/dist/chunk-SNXVDZAJ.js.map +0 -1
- package/dist/chunk-SQRD2CYF.js.map +0 -1
- package/dist/chunk-ST3LPB2V.cjs.map +0 -1
- package/dist/chunk-STQ3BSED.cjs.map +0 -1
- package/dist/chunk-SVJ5GEXW.cjs.map +0 -1
- package/dist/chunk-TCRIQMBU.js.map +0 -1
- package/dist/chunk-TDXIBCGY.cjs.map +0 -1
- package/dist/chunk-TFMVL4QH.js.map +0 -1
- package/dist/chunk-TRF4EBJT.cjs +0 -30
- package/dist/chunk-TRF4EBJT.cjs.map +0 -1
- package/dist/chunk-TVLKA4AY.js +0 -94
- package/dist/chunk-TVLKA4AY.js.map +0 -1
- package/dist/chunk-UR7W5YQK.js.map +0 -1
- package/dist/chunk-VITTKBWF.js +0 -1263
- package/dist/chunk-VITTKBWF.js.map +0 -1
- package/dist/chunk-WBUFJ7BS.cjs +0 -131
- package/dist/chunk-WBUFJ7BS.cjs.map +0 -1
- package/dist/chunk-XR4YZFKW.cjs.map +0 -1
- package/dist/chunk-YNULOIR5.cjs.map +0 -1
- package/dist/chunk-ZDH3DX3C.cjs +0 -30
- package/dist/chunk-ZDH3DX3C.cjs.map +0 -1
- package/dist/chunk-ZTAJL4DT.cjs +0 -85
- package/dist/chunk-ZTAJL4DT.cjs.map +0 -1
- package/dist/define-graph-DVF-oN1i.d.cts +0 -173
- package/dist/define-graph-SvYXOl78.d.ts +0 -173
|
@@ -1,39 +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-TFMVL4QH.js';
|
|
1
|
+
import { createStoreWithSchema } from '../chunk-7Z5OR7KO.js';
|
|
2
|
+
import '../chunk-NMWDXFPH.js';
|
|
3
|
+
import { importGraphStream, exportGraphStream, importGraph, exportGraph } from '../chunk-OH5E5KIO.js';
|
|
4
|
+
import '../chunk-QWX5YA3K.js';
|
|
5
|
+
import { storeBackend, storeRuntime, transactionBackend } from '../chunk-WEVQJ5LH.js';
|
|
6
|
+
import { computeSchemaHash, serializeSchema } from '../chunk-QLRESYPU.js';
|
|
7
|
+
import { defineNode } from '../chunk-JXA2ZP2Q.js';
|
|
8
|
+
import { encodeTupleKey, normalizeIdentityPair, identityReferenceKey, identityAssertionSemanticKey } from '../chunk-JIVMTW7W.js';
|
|
9
|
+
import { generateId, compareStrings, compareCodePoints, lockRecordedGraphWrite, readRevisionOrigin, readRecordedClock } from '../chunk-NQ6BRJBE.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-DLNROMQ7.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 '../chunk-
|
|
35
|
-
import {
|
|
36
|
-
import '../chunk-G6Q5UM7U.js';
|
|
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';
|
|
37
34
|
import { z } from 'zod';
|
|
38
35
|
|
|
39
36
|
// src/graph-merge/canonical-props.ts
|
|
@@ -102,8 +99,834 @@ function compareMergeKeys(left, right) {
|
|
|
102
99
|
return leftKind < rightKind ? -1 : leftKind > rightKind ? 1 : 0;
|
|
103
100
|
}
|
|
104
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
|
+
|
|
105
918
|
// src/graph-merge/state-diff.ts
|
|
106
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
|
+
}
|
|
107
930
|
async function enumerateAllNodes(backend, graphId, kind) {
|
|
108
931
|
const collected = [];
|
|
109
932
|
let after;
|
|
@@ -164,6 +987,7 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
164
987
|
const created = [];
|
|
165
988
|
const modified = [];
|
|
166
989
|
const deleted = [];
|
|
990
|
+
const windowed = [];
|
|
167
991
|
for (const forkRow of forkRows) {
|
|
168
992
|
if (!isLive(forkRow)) {
|
|
169
993
|
continue;
|
|
@@ -189,6 +1013,16 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
189
1013
|
row: forkRow
|
|
190
1014
|
});
|
|
191
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
|
+
}
|
|
192
1026
|
}
|
|
193
1027
|
for (const baseRow of baseRows) {
|
|
194
1028
|
if (!isLive(baseRow)) {
|
|
@@ -196,10 +1030,14 @@ function diffNodeKind(kind, baseRows, forkRows) {
|
|
|
196
1030
|
}
|
|
197
1031
|
const forkRow = forkIndex.get(baseRow.id);
|
|
198
1032
|
if (forkRow === void 0 || !isLive(forkRow)) {
|
|
199
|
-
deleted.push({
|
|
1033
|
+
deleted.push({
|
|
1034
|
+
id: baseRow.id,
|
|
1035
|
+
kind,
|
|
1036
|
+
deletedAt: forkRow === void 0 ? void 0 : canonicalizeDatabaseTimestamp(forkRow.deleted_at)
|
|
1037
|
+
});
|
|
200
1038
|
}
|
|
201
1039
|
}
|
|
202
|
-
return { new: created, modified, deleted };
|
|
1040
|
+
return { new: created, modified, deleted, windowed };
|
|
203
1041
|
}
|
|
204
1042
|
function diffEdgeKind(kind, baseRows, forkRows) {
|
|
205
1043
|
const baseIndex = indexById(baseRows);
|
|
@@ -207,6 +1045,7 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
207
1045
|
const created = [];
|
|
208
1046
|
const modified = [];
|
|
209
1047
|
const deleted = [];
|
|
1048
|
+
const windowed = [];
|
|
210
1049
|
for (const forkRow of forkRows) {
|
|
211
1050
|
if (!isLive(forkRow)) {
|
|
212
1051
|
continue;
|
|
@@ -240,6 +1079,22 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
240
1079
|
row: forkRow
|
|
241
1080
|
});
|
|
242
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
|
+
}
|
|
243
1098
|
}
|
|
244
1099
|
for (const baseRow of baseRows) {
|
|
245
1100
|
if (!isLive(baseRow)) {
|
|
@@ -250,7 +1105,51 @@ function diffEdgeKind(kind, baseRows, forkRows) {
|
|
|
250
1105
|
deleted.push({ id: baseRow.id, kind });
|
|
251
1106
|
}
|
|
252
1107
|
}
|
|
253
|
-
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
|
+
}));
|
|
254
1153
|
}
|
|
255
1154
|
function byId(left, right) {
|
|
256
1155
|
return compareStrings(left.id, right.id);
|
|
@@ -259,9 +1158,20 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
259
1158
|
const graph = baseStore.graph;
|
|
260
1159
|
const nodeKinds = getNodeKinds(graph);
|
|
261
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
|
+
);
|
|
262
1171
|
const newNodes = [];
|
|
263
1172
|
const modifiedNodes = [];
|
|
264
1173
|
const deletedNodes = [];
|
|
1174
|
+
const windowedNodes = [];
|
|
265
1175
|
const forkNodeVersions = /* @__PURE__ */ new Map();
|
|
266
1176
|
for (const kind of nodeKinds) {
|
|
267
1177
|
const baseRows = await enumerateAllNodes(
|
|
@@ -289,10 +1199,14 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
289
1199
|
for (const entry of delta.deleted) {
|
|
290
1200
|
deletedNodes.push(entry);
|
|
291
1201
|
}
|
|
1202
|
+
for (const entry of delta.windowed) {
|
|
1203
|
+
windowedNodes.push(entry);
|
|
1204
|
+
}
|
|
292
1205
|
}
|
|
293
1206
|
const newEdges = [];
|
|
294
1207
|
const modifiedEdges = [];
|
|
295
1208
|
const deletedEdges = [];
|
|
1209
|
+
const windowedEdges = [];
|
|
296
1210
|
const forkEdgeSignatures = /* @__PURE__ */ new Map();
|
|
297
1211
|
for (const kind of edgeKinds) {
|
|
298
1212
|
const baseRows = await enumerateAllEdges(
|
|
@@ -330,17 +1244,46 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
|
|
|
330
1244
|
for (const entry of delta.deleted) {
|
|
331
1245
|
deletedEdges.push(entry);
|
|
332
1246
|
}
|
|
1247
|
+
for (const entry of delta.windowed) {
|
|
1248
|
+
windowedEdges.push(entry);
|
|
1249
|
+
}
|
|
333
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
|
+
);
|
|
334
1260
|
return {
|
|
335
1261
|
nodes: {
|
|
336
1262
|
new: newNodes.sort((left, right) => byId(left, right)),
|
|
337
1263
|
modified: modifiedNodes.sort((left, right) => byId(left, right)),
|
|
338
|
-
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))
|
|
339
1266
|
},
|
|
340
1267
|
edges: {
|
|
341
1268
|
new: newEdges.sort((left, right) => byId(left, right)),
|
|
342
1269
|
modified: modifiedEdges.sort((left, right) => byId(left, right)),
|
|
343
|
-
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
|
|
344
1287
|
},
|
|
345
1288
|
forkNodeVersions,
|
|
346
1289
|
forkEdgeSignatures
|
|
@@ -354,9 +1297,11 @@ function asBranchId(value) {
|
|
|
354
1297
|
function asBaseVersion(value) {
|
|
355
1298
|
return value;
|
|
356
1299
|
}
|
|
1300
|
+
var VALIDITY_END_TARGET_PRECEDENCE = "target";
|
|
357
1301
|
|
|
358
1302
|
// src/graph-merge/base-version.ts
|
|
359
1303
|
var TOKEN_SEPARATOR = "\0";
|
|
1304
|
+
var SCHEMA_VERSION_TAG = "#s";
|
|
360
1305
|
var REVISION_COMPONENT_PREFIX = "revision:";
|
|
361
1306
|
var REVISION_COMPONENT_SEPARATOR = ":";
|
|
362
1307
|
var INITIAL_REVISION = "initial";
|
|
@@ -377,7 +1322,7 @@ function byDigestEntry(left, right) {
|
|
|
377
1322
|
return byKind === 0 ? compareStrings(left.id, right.id) : byKind;
|
|
378
1323
|
}
|
|
379
1324
|
var CONTENT_FINGERPRINT_BYTES = 16;
|
|
380
|
-
async function computeContentComponent(backend, graphId, graph) {
|
|
1325
|
+
async function computeContentComponent(backend, graphId, graph, identityAssertions) {
|
|
381
1326
|
const nodeKinds = getNodeKinds(graph);
|
|
382
1327
|
const edgeKinds = getEdgeKinds(graph);
|
|
383
1328
|
const nodeDigest = [];
|
|
@@ -417,30 +1362,41 @@ async function computeContentComponent(backend, graphId, graph) {
|
|
|
417
1362
|
return sha256Hex(
|
|
418
1363
|
canonicalizeProps({
|
|
419
1364
|
nodes: nodeDigest.sort((left, right) => byDigestEntry(left, right)),
|
|
420
|
-
edges: edgeDigest.sort((left, right) => byDigestEntry(left, right))
|
|
1365
|
+
edges: edgeDigest.sort((left, right) => byDigestEntry(left, right)),
|
|
1366
|
+
...identityAssertions.length === 0 ? {} : { identity: identityAssertions }
|
|
421
1367
|
}),
|
|
422
1368
|
CONTENT_FINGERPRINT_BYTES
|
|
423
1369
|
);
|
|
424
1370
|
}
|
|
425
1371
|
async function computeBaseVersion(store) {
|
|
426
1372
|
if (store.revisionTrackingEnabled) {
|
|
427
|
-
const [schemaComponent2, origin, revision] = await Promise.all([
|
|
1373
|
+
const [schemaComponent2, origin, revision, activeVersion] = await Promise.all([
|
|
428
1374
|
computeSchemaComponent(store),
|
|
429
1375
|
store.revisionOriginNow(),
|
|
430
|
-
store.revisionNow()
|
|
1376
|
+
store.revisionNow(),
|
|
1377
|
+
readActiveSchemaVersion(storeBackend(store), store.graphId)
|
|
431
1378
|
]);
|
|
432
1379
|
return asBaseVersion(
|
|
433
|
-
`${schemaComponent2}${TOKEN_SEPARATOR}${revisionComponent(origin, revision)}`
|
|
1380
|
+
`${schemaComponent2}${SCHEMA_VERSION_TAG}${activeVersion}${TOKEN_SEPARATOR}${revisionComponent(origin, revision)}`
|
|
434
1381
|
);
|
|
435
1382
|
}
|
|
436
1383
|
const [schemaComponent, contentComponent] = await Promise.all([
|
|
437
1384
|
computeSchemaComponent(store),
|
|
438
|
-
|
|
1385
|
+
computeStoreContentComponent(store)
|
|
439
1386
|
]);
|
|
440
1387
|
return asBaseVersion(
|
|
441
1388
|
`${schemaComponent}${TOKEN_SEPARATOR}${contentComponent}`
|
|
442
1389
|
);
|
|
443
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
|
+
}
|
|
444
1400
|
function revisionComponent(origin, revision) {
|
|
445
1401
|
return `${REVISION_COMPONENT_PREFIX}${origin}${REVISION_COMPONENT_SEPARATOR}${revision ?? INITIAL_REVISION}`;
|
|
446
1402
|
}
|
|
@@ -470,70 +1426,6 @@ function contentComponentOf(version) {
|
|
|
470
1426
|
return separatorIndex === -1 ? version : version.slice(separatorIndex + 1);
|
|
471
1427
|
}
|
|
472
1428
|
|
|
473
|
-
// src/graph-merge/errors.ts
|
|
474
|
-
var MERGE_ERROR_CODES = {
|
|
475
|
-
merge: "GRAPH_MERGE_ERROR",
|
|
476
|
-
branch: "GRAPH_MERGE_BRANCH_ERROR",
|
|
477
|
-
similarityUnavailable: "GRAPH_MERGE_SIMILARITY_UNAVAILABLE",
|
|
478
|
-
conflict: "GRAPH_MERGE_CONFLICT",
|
|
479
|
-
baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH"
|
|
480
|
-
};
|
|
481
|
-
function toTypeGraphErrorOptions(category, options) {
|
|
482
|
-
return {
|
|
483
|
-
category,
|
|
484
|
-
...options.details === void 0 ? {} : { details: options.details },
|
|
485
|
-
...options.suggestion === void 0 ? {} : { suggestion: options.suggestion },
|
|
486
|
-
...options.cause === void 0 ? {} : { cause: options.cause }
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
var MergeError = class extends TypeGraphError {
|
|
490
|
-
constructor(message, options = {}) {
|
|
491
|
-
super(
|
|
492
|
-
message,
|
|
493
|
-
MERGE_ERROR_CODES.merge,
|
|
494
|
-
toTypeGraphErrorOptions("system", options)
|
|
495
|
-
);
|
|
496
|
-
this.name = "MergeError";
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
var BranchError = class extends TypeGraphError {
|
|
500
|
-
constructor(message, options = {}) {
|
|
501
|
-
super(
|
|
502
|
-
message,
|
|
503
|
-
MERGE_ERROR_CODES.branch,
|
|
504
|
-
toTypeGraphErrorOptions("system", options)
|
|
505
|
-
);
|
|
506
|
-
this.name = "BranchError";
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
var SimilarityUnavailableError = class extends MergeError {
|
|
510
|
-
code = MERGE_ERROR_CODES.similarityUnavailable;
|
|
511
|
-
constructor(message, options = {}) {
|
|
512
|
-
super(message, {
|
|
513
|
-
...options,
|
|
514
|
-
suggestion: options.suggestion ?? "Pass MergeOptions.embedder (a local model), or use a fulltext/custom similarity strategy."
|
|
515
|
-
});
|
|
516
|
-
this.name = "SimilarityUnavailableError";
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
var MergeConflictError = class extends MergeError {
|
|
520
|
-
code = MERGE_ERROR_CODES.conflict;
|
|
521
|
-
constructor(message, options = {}) {
|
|
522
|
-
super(message, options);
|
|
523
|
-
this.name = "MergeConflictError";
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
var BaseVersionMismatchError = class extends MergeError {
|
|
527
|
-
code = MERGE_ERROR_CODES.baseVersionMismatch;
|
|
528
|
-
constructor(message, options = {}) {
|
|
529
|
-
super(message, {
|
|
530
|
-
...options,
|
|
531
|
-
suggestion: options.suggestion ?? "Re-branch from the current target so the branch base matches before merging."
|
|
532
|
-
});
|
|
533
|
-
this.name = "BaseVersionMismatchError";
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
|
|
537
1429
|
// src/graph-merge/working-copy.ts
|
|
538
1430
|
var CLONE_IMPORT_BATCH_SIZE = 1e3;
|
|
539
1431
|
function cloneWorkingCopyStrategy(makeBackend) {
|
|
@@ -550,20 +1442,29 @@ function cloneWorkingCopyStrategy(makeBackend) {
|
|
|
550
1442
|
revisionTracking: baseStore.revisionTrackingEnabled
|
|
551
1443
|
}
|
|
552
1444
|
);
|
|
553
|
-
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(
|
|
554
1461
|
freshStore,
|
|
555
|
-
exportGraphStream(baseStore,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
onConflict: "error",
|
|
563
|
-
onUnknownProperty: "error",
|
|
564
|
-
validateReferences: true,
|
|
565
|
-
batchSize: CLONE_IMPORT_BATCH_SIZE
|
|
566
|
-
}
|
|
1462
|
+
exportGraphStream(baseStore, exportOptions),
|
|
1463
|
+
importOptions
|
|
1464
|
+
) : await importGraph(
|
|
1465
|
+
freshStore,
|
|
1466
|
+
await exportGraph(baseStore, exportOptions),
|
|
1467
|
+
importOptions
|
|
567
1468
|
);
|
|
568
1469
|
if (!result.success) {
|
|
569
1470
|
throw new BranchError(
|
|
@@ -590,7 +1491,13 @@ async function branch(baseStore, makeBackend, options, strategy) {
|
|
|
590
1491
|
const id = options?.id ?? asBranchId(generateId());
|
|
591
1492
|
const workingCopyStrategy = strategy ?? cloneWorkingCopyStrategy(makeBackend);
|
|
592
1493
|
const store = await workingCopyStrategy.create(baseStore);
|
|
593
|
-
|
|
1494
|
+
const schemaAnchor = await readSchemaAnchor(store);
|
|
1495
|
+
return ok({
|
|
1496
|
+
id,
|
|
1497
|
+
base,
|
|
1498
|
+
store,
|
|
1499
|
+
...schemaAnchor === void 0 ? { schemaAnchor: void 0 } : { schemaAnchor }
|
|
1500
|
+
});
|
|
594
1501
|
} catch (error) {
|
|
595
1502
|
return err(
|
|
596
1503
|
new BranchError("Failed to create working-copy branch of base store", {
|
|
@@ -599,6 +1506,18 @@ async function branch(baseStore, makeBackend, options, strategy) {
|
|
|
599
1506
|
);
|
|
600
1507
|
}
|
|
601
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
|
+
}
|
|
602
1521
|
|
|
603
1522
|
// src/graph-merge/blocking.ts
|
|
604
1523
|
var UNBLOCKED_BUCKET_KEY = "unblocked";
|
|
@@ -611,7 +1530,7 @@ function isUniqueBucketKey(key) {
|
|
|
611
1530
|
function constraintSignature(node, constraint) {
|
|
612
1531
|
const props = node;
|
|
613
1532
|
for (const field of constraint.fields) {
|
|
614
|
-
const value = props
|
|
1533
|
+
const value = readOwnProperty(props, field);
|
|
615
1534
|
if (value === void 0 || value === null) {
|
|
616
1535
|
return void 0;
|
|
617
1536
|
}
|
|
@@ -735,7 +1654,7 @@ function collectConflictingValues(property, contributions) {
|
|
|
735
1654
|
const seen = /* @__PURE__ */ new Set();
|
|
736
1655
|
const values = [];
|
|
737
1656
|
for (const { branchId, props } of contributions) {
|
|
738
|
-
if (!(property
|
|
1657
|
+
if (!hasOwnKey(props, property)) {
|
|
739
1658
|
continue;
|
|
740
1659
|
}
|
|
741
1660
|
const value = props[property];
|
|
@@ -808,6 +1727,9 @@ function resolvePropertyUnion(input, context, branchRank) {
|
|
|
808
1727
|
|
|
809
1728
|
// src/graph-merge/canonicalize.ts
|
|
810
1729
|
var BASE_PROVENANCE_BRANCH = asBranchId("__committed_base__");
|
|
1730
|
+
var COMMITTED_TARGET_BRANCH = asBranchId(
|
|
1731
|
+
"__committed_target__"
|
|
1732
|
+
);
|
|
811
1733
|
function pickClusterSurvivor(members, cluster, canonicalOverride, preferKind) {
|
|
812
1734
|
const byKey = (left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right));
|
|
813
1735
|
const baseMembers = members.filter((member) => member.origin === "base").sort(byKey);
|
|
@@ -840,11 +1762,11 @@ function findPreferredMember(members, property, preferredBranchId) {
|
|
|
840
1762
|
return void 0;
|
|
841
1763
|
}
|
|
842
1764
|
return members.find(
|
|
843
|
-
(member) => member.branchId === preferredBranchId &&
|
|
1765
|
+
(member) => member.branchId === preferredBranchId && hasOwnKey(member.props, property)
|
|
844
1766
|
);
|
|
845
1767
|
}
|
|
846
1768
|
function memberPropertyValue(member, property) {
|
|
847
|
-
if (!(
|
|
1769
|
+
if (!hasOwnKey(member.props, property)) {
|
|
848
1770
|
return void 0;
|
|
849
1771
|
}
|
|
850
1772
|
return member.props[property];
|
|
@@ -872,7 +1794,7 @@ function unionProperties(canonicalMember, members, context, baseContext, branchR
|
|
|
872
1794
|
propertyNames.add(name);
|
|
873
1795
|
}
|
|
874
1796
|
}
|
|
875
|
-
const props =
|
|
1797
|
+
const props = createDataKeyedBag();
|
|
876
1798
|
const conflicts = [];
|
|
877
1799
|
for (const property of [...propertyNames].sort(
|
|
878
1800
|
(left, right) => compareStrings(left, right)
|
|
@@ -889,7 +1811,7 @@ function unionProperties(canonicalMember, members, context, baseContext, branchR
|
|
|
889
1811
|
preferredBranchId
|
|
890
1812
|
);
|
|
891
1813
|
const baseInvolved = hasBaseMember && members.some(
|
|
892
|
-
(member) => member.origin === "base" &&
|
|
1814
|
+
(member) => member.origin === "base" && hasOwnKey(member.props, property)
|
|
893
1815
|
);
|
|
894
1816
|
const reportedValues = values.filter(
|
|
895
1817
|
(value2) => value2.branchId !== BASE_PROVENANCE_BRANCH && value2.branchId !== preferredBranchId
|
|
@@ -930,89 +1852,56 @@ function canonicalizeCluster(cluster, members, policy, branchRank, weights, cano
|
|
|
930
1852
|
policy: basePolicy,
|
|
931
1853
|
...weights === void 0 ? {} : { weights }
|
|
932
1854
|
};
|
|
933
|
-
const { props, conflicts } = unionProperties(
|
|
934
|
-
survivor,
|
|
935
|
-
members,
|
|
936
|
-
context,
|
|
937
|
-
baseContext,
|
|
938
|
-
branchRank,
|
|
939
|
-
preferredBranchId
|
|
940
|
-
);
|
|
941
|
-
const memberIds = [
|
|
942
|
-
...new Set(cluster.members.map((member) => idOf(member)))
|
|
943
|
-
].sort((left, right) => compareStrings(left, right));
|
|
944
|
-
const branchOrigins = [
|
|
945
|
-
...new Set(
|
|
946
|
-
members.map((member) => member.branchId).filter(
|
|
947
|
-
(branchId) => branchId !== BASE_PROVENANCE_BRANCH && branchId !== preferredBranchId
|
|
948
|
-
)
|
|
949
|
-
)
|
|
950
|
-
].sort((left, right) => compareStrings(left, right));
|
|
951
|
-
const resolution = {
|
|
952
|
-
canonicalId,
|
|
953
|
-
memberIds,
|
|
954
|
-
kind,
|
|
955
|
-
branchOrigins
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
this.parent.set(value, value);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
/** Returns the representative of `value`'s set, compressing the path to it. */
|
|
974
|
-
find(value) {
|
|
975
|
-
this.add(value);
|
|
976
|
-
let root = value;
|
|
977
|
-
while (this.parent.get(root) !== root) {
|
|
978
|
-
root = requireDefined(this.parent.get(root));
|
|
979
|
-
}
|
|
980
|
-
let cursor = value;
|
|
981
|
-
while (cursor !== root) {
|
|
982
|
-
const next = requireDefined(this.parent.get(cursor));
|
|
983
|
-
this.parent.set(cursor, root);
|
|
984
|
-
cursor = next;
|
|
985
|
-
}
|
|
986
|
-
return root;
|
|
987
|
-
}
|
|
988
|
-
/** Merges the sets of `left` and `right`; the `compare`-minimal root wins. */
|
|
989
|
-
union(left, right) {
|
|
990
|
-
const leftRoot = this.find(left);
|
|
991
|
-
const rightRoot = this.find(right);
|
|
992
|
-
if (leftRoot === rightRoot) {
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
if (this.compare(leftRoot, rightRoot) <= 0) {
|
|
996
|
-
this.parent.set(rightRoot, leftRoot);
|
|
997
|
-
} else {
|
|
998
|
-
this.parent.set(leftRoot, rightRoot);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
/** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
|
|
1002
|
-
members() {
|
|
1003
|
-
return [...this.parent.keys()];
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
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
|
+
}
|
|
1006
1892
|
|
|
1007
1893
|
// src/graph-merge/closures.ts
|
|
1008
1894
|
var SUB_CLASS_OF_META_EDGE = "subClassOf";
|
|
1009
|
-
var
|
|
1895
|
+
var EQUIVALENCE_META_EDGES = /* @__PURE__ */ new Set([
|
|
1896
|
+
META_EDGE_EQUIVALENT_TO,
|
|
1897
|
+
META_EDGE_SAME_AS
|
|
1898
|
+
]);
|
|
1010
1899
|
function buildSubClassClosure(ontology) {
|
|
1011
1900
|
const unionFind = new UnionFind(compareStrings);
|
|
1012
1901
|
for (const relation of ontology) {
|
|
1013
1902
|
unionFind.add(relation.from);
|
|
1014
1903
|
unionFind.add(relation.to);
|
|
1015
|
-
if (relation.metaEdge
|
|
1904
|
+
if (EQUIVALENCE_META_EDGES.has(relation.metaEdge)) {
|
|
1016
1905
|
unionFind.union(relation.from, relation.to);
|
|
1017
1906
|
}
|
|
1018
1907
|
}
|
|
@@ -1074,7 +1963,7 @@ function clampScore(value) {
|
|
|
1074
1963
|
return value;
|
|
1075
1964
|
}
|
|
1076
1965
|
function readField(node, field) {
|
|
1077
|
-
return node
|
|
1966
|
+
return readOwnProperty(node, field);
|
|
1078
1967
|
}
|
|
1079
1968
|
function fieldText(node, fields) {
|
|
1080
1969
|
const parts = [];
|
|
@@ -1612,7 +2501,7 @@ function groupByEntity(items, entityOf) {
|
|
|
1612
2501
|
}
|
|
1613
2502
|
return grouped;
|
|
1614
2503
|
}
|
|
1615
|
-
function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntity,
|
|
2504
|
+
function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntity, dropDeleted, policy, branchRank, preferredBranchId) {
|
|
1616
2505
|
const modifiedByKey = groupByEntity(modified, modifiedEntity);
|
|
1617
2506
|
const deletedByKey = groupByEntity(deleted, deletedEntity);
|
|
1618
2507
|
const survivingModifications = [];
|
|
@@ -1657,7 +2546,7 @@ function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntit
|
|
|
1657
2546
|
});
|
|
1658
2547
|
if (effectivePolicy === "deleteWins") {
|
|
1659
2548
|
deletions.push({ id, kind });
|
|
1660
|
-
dropped.push(
|
|
2549
|
+
dropped.push(dropDeleted(id));
|
|
1661
2550
|
continue;
|
|
1662
2551
|
}
|
|
1663
2552
|
for (const modification of modifications) {
|
|
@@ -1747,7 +2636,7 @@ function resolveDeleteModify(staging, policy, branchRank, preferredBranchId) {
|
|
|
1747
2636
|
staging.deletedNodes,
|
|
1748
2637
|
(item) => item.node,
|
|
1749
2638
|
(item) => item.node,
|
|
1750
|
-
"node",
|
|
2639
|
+
(id) => ({ kind: "node", id, reason: DELETED_NODE_DROP_REASON }),
|
|
1751
2640
|
policy,
|
|
1752
2641
|
branchRank,
|
|
1753
2642
|
preferredBranchId
|
|
@@ -1760,7 +2649,7 @@ function resolveDeleteModify(staging, policy, branchRank, preferredBranchId) {
|
|
|
1760
2649
|
};
|
|
1761
2650
|
}
|
|
1762
2651
|
function threeWayMergeProps(entityId, kind, baseProps, contributions, context, branchRank, preferredBranchId) {
|
|
1763
|
-
const merged =
|
|
2652
|
+
const merged = createDataKeyedBag();
|
|
1764
2653
|
const conflicts = [];
|
|
1765
2654
|
const propertyNames = new Set(Object.keys(baseProps));
|
|
1766
2655
|
for (const contribution of contributions) {
|
|
@@ -1769,10 +2658,10 @@ function threeWayMergeProps(entityId, kind, baseProps, contributions, context, b
|
|
|
1769
2658
|
}
|
|
1770
2659
|
}
|
|
1771
2660
|
for (const property of [...propertyNames].sort(compareStrings)) {
|
|
1772
|
-
const baseHas = property
|
|
2661
|
+
const baseHas = hasOwnKey(baseProps, property);
|
|
1773
2662
|
const baseKey = baseHas ? canonicalValueKey(requireDefined(baseProps[property])) : void 0;
|
|
1774
2663
|
const changed = contributions.filter((contribution) => {
|
|
1775
|
-
if (!(
|
|
2664
|
+
if (!hasOwnKey(contribution.forkProps, property)) {
|
|
1776
2665
|
return false;
|
|
1777
2666
|
}
|
|
1778
2667
|
const value2 = contribution.forkProps[property];
|
|
@@ -1784,7 +2673,7 @@ function threeWayMergeProps(entityId, kind, baseProps, contributions, context, b
|
|
|
1784
2673
|
const values = collectConflictingValues(property, changed);
|
|
1785
2674
|
if (values.length === 0) {
|
|
1786
2675
|
const deletedByFork = baseHas && contributions.some(
|
|
1787
|
-
(contribution) => !(
|
|
2676
|
+
(contribution) => !hasOwnKey(contribution.forkProps, property)
|
|
1788
2677
|
);
|
|
1789
2678
|
if (baseHas && !deletedByFork) {
|
|
1790
2679
|
merged[property] = requireDefined(baseProps[property]);
|
|
@@ -1835,7 +2724,7 @@ function resolveEdgeDeleteModify(staging, policy, branchRank, preferredBranchId)
|
|
|
1835
2724
|
staging.deletedEdges,
|
|
1836
2725
|
(item) => item.edge,
|
|
1837
2726
|
(item) => item.edge,
|
|
1838
|
-
"edge",
|
|
2727
|
+
(id) => ({ kind: "edge", id, reason: DELETED_NODE_DROP_REASON }),
|
|
1839
2728
|
policy,
|
|
1840
2729
|
branchRank,
|
|
1841
2730
|
preferredBranchId
|
|
@@ -1862,8 +2751,166 @@ function reconcileEdgeModifications(survivingModifications, policy, branchRank,
|
|
|
1862
2751
|
);
|
|
1863
2752
|
}
|
|
1864
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
|
+
|
|
1865
2910
|
// src/graph-merge/edge-repoint.ts
|
|
1866
2911
|
var ENDPOINT_DELETED_DROP_REASON = "edge:endpoint-deleted";
|
|
2912
|
+
var INHERITED_EDGE_ORIGIN = "inherited";
|
|
2913
|
+
var BRANCH_CREATED_EDGE_ORIGIN = "branch-created";
|
|
1867
2914
|
function buildCanonicalMap(clusters, canonicalOf) {
|
|
1868
2915
|
const map = /* @__PURE__ */ new Map();
|
|
1869
2916
|
for (const cluster of clusters) {
|
|
@@ -1883,16 +2930,97 @@ function dedupeKey(fromKey, type, toKey, props) {
|
|
|
1883
2930
|
function groupKey(fromKey, type, toKey) {
|
|
1884
2931
|
return JSON.stringify([fromKey, type, toKey]);
|
|
1885
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
|
+
}
|
|
1886
2993
|
function pickSurvivor(edges, preferredBranchId) {
|
|
2994
|
+
const inherited = edges.filter(
|
|
2995
|
+
(edge) => edge.staged.origin === INHERITED_EDGE_ORIGIN
|
|
2996
|
+
);
|
|
1887
2997
|
const preferred = preferredBranchId === void 0 ? [] : edges.filter((edge) => edge.staged.branchId === preferredBranchId);
|
|
1888
|
-
const candidates = preferred.length > 0 ? preferred : edges;
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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;
|
|
1894
3012
|
}
|
|
1895
|
-
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];
|
|
1896
3024
|
}
|
|
1897
3025
|
function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank, preferredBranchId) {
|
|
1898
3026
|
const propertyNames = /* @__PURE__ */ new Set();
|
|
@@ -1901,17 +3029,18 @@ function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank,
|
|
|
1901
3029
|
propertyNames.add(name);
|
|
1902
3030
|
}
|
|
1903
3031
|
}
|
|
1904
|
-
const props =
|
|
3032
|
+
const props = createDataKeyedBag();
|
|
1905
3033
|
const conflicts = [];
|
|
1906
|
-
const contributions = edges.map((edge) => edge.staged);
|
|
1907
3034
|
for (const property of [...propertyNames].sort(
|
|
1908
3035
|
(left, right) => compareStrings(left, right)
|
|
1909
3036
|
)) {
|
|
1910
|
-
const
|
|
3037
|
+
const claimants = edges.filter((edge) => authoredProperty(edge, property)).map((edge) => edge.staged);
|
|
3038
|
+
const values = collectConflictingValues(property, claimants);
|
|
1911
3039
|
if (values.length === 0) {
|
|
3040
|
+
props[property] = hasOwnKey(survivor.staged.props, property) ? survivor.staged.props[property] : unauthoredValue(property, edges);
|
|
1912
3041
|
continue;
|
|
1913
3042
|
}
|
|
1914
|
-
const survivorValue =
|
|
3043
|
+
const survivorValue = hasOwnKey(survivor.staged.props, property) ? survivor.staged.props[property] : requireDefined(values[0]).value;
|
|
1915
3044
|
const canonicalValue = values.find((value2) => value2.branchId === preferredBranchId)?.value ?? survivorValue;
|
|
1916
3045
|
const reportedValues = preferredBranchId === void 0 ? values : values.filter((value2) => value2.branchId !== preferredBranchId);
|
|
1917
3046
|
const { value, conflict } = resolvePropertyUnion(
|
|
@@ -1933,16 +3062,75 @@ function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank,
|
|
|
1933
3062
|
}
|
|
1934
3063
|
return { props, conflicts };
|
|
1935
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
|
+
}
|
|
1936
3126
|
function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRank, weights, preferredBranchId) {
|
|
1937
3127
|
const dropped = [];
|
|
1938
|
-
const
|
|
1939
|
-
const dedupeKeyByGroup = /* @__PURE__ */ new Map();
|
|
3128
|
+
const liveByGroup = /* @__PURE__ */ new Map();
|
|
1940
3129
|
for (const staged of stagedEdges) {
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
);
|
|
1945
|
-
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);
|
|
1946
3134
|
if (deletedNodeIds.has(fromKey) || deletedNodeIds.has(toKey)) {
|
|
1947
3135
|
dropped.push({
|
|
1948
3136
|
kind: "edge",
|
|
@@ -1951,21 +3139,20 @@ function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRa
|
|
|
1951
3139
|
});
|
|
1952
3140
|
continue;
|
|
1953
3141
|
}
|
|
1954
|
-
const repointed = {
|
|
1955
|
-
|
|
1956
|
-
|
|
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);
|
|
1957
3151
|
if (bucket === void 0) {
|
|
1958
|
-
|
|
3152
|
+
liveByGroup.set(group, [repointed]);
|
|
1959
3153
|
} else {
|
|
1960
3154
|
bucket.push(repointed);
|
|
1961
3155
|
}
|
|
1962
|
-
const group = groupKey(fromKey, staged.kind, toKey);
|
|
1963
|
-
const keysForGroup = dedupeKeyByGroup.get(group);
|
|
1964
|
-
if (keysForGroup === void 0) {
|
|
1965
|
-
dedupeKeyByGroup.set(group, /* @__PURE__ */ new Set([key]));
|
|
1966
|
-
} else {
|
|
1967
|
-
keysForGroup.add(key);
|
|
1968
|
-
}
|
|
1969
3156
|
}
|
|
1970
3157
|
const context = {
|
|
1971
3158
|
policy,
|
|
@@ -1973,68 +3160,35 @@ function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRa
|
|
|
1973
3160
|
};
|
|
1974
3161
|
const merged = [];
|
|
1975
3162
|
const conflicts = [];
|
|
1976
|
-
const sortedGroups = [...
|
|
3163
|
+
const sortedGroups = [...liveByGroup.keys()].sort(
|
|
1977
3164
|
(left, right) => compareStrings(left, right)
|
|
1978
3165
|
);
|
|
1979
3166
|
for (const group of sortedGroups) {
|
|
1980
|
-
const
|
|
1981
|
-
const groupEdges
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
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);
|
|
1985
3178
|
}
|
|
1986
3179
|
}
|
|
1987
|
-
const survivor = pickSurvivor(groupEdges, preferredBranchId);
|
|
1988
|
-
const survivorId = survivor.staged.id;
|
|
1989
|
-
const mergedIds = [...groupEdges].map((edge) => edge.staged.id).sort((left, right) => compareStrings(left, right));
|
|
1990
|
-
const fromId = idOf(survivor.fromKey);
|
|
1991
|
-
const fromKind = kindOf(survivor.fromKey);
|
|
1992
|
-
const toId = idOf(survivor.toKey);
|
|
1993
|
-
const toKind = kindOf(survivor.toKey);
|
|
1994
|
-
if (dedupeKeys.length === 1) {
|
|
1995
|
-
merged.push({
|
|
1996
|
-
id: survivorId,
|
|
1997
|
-
kind: survivor.staged.kind,
|
|
1998
|
-
fromId,
|
|
1999
|
-
toId,
|
|
2000
|
-
fromKind,
|
|
2001
|
-
toKind,
|
|
2002
|
-
props: survivor.staged.props,
|
|
2003
|
-
mergedIds
|
|
2004
|
-
});
|
|
2005
|
-
continue;
|
|
2006
|
-
}
|
|
2007
|
-
const { props, conflicts: groupConflicts } = unionEdgeProps(
|
|
2008
|
-
survivorId,
|
|
2009
|
-
survivor.staged.kind,
|
|
2010
|
-
survivor,
|
|
2011
|
-
groupEdges,
|
|
2012
|
-
context,
|
|
2013
|
-
branchRank,
|
|
2014
|
-
preferredBranchId
|
|
2015
|
-
);
|
|
2016
|
-
for (const conflict of groupConflicts) {
|
|
2017
|
-
conflicts.push(conflict);
|
|
2018
|
-
}
|
|
2019
|
-
merged.push({
|
|
2020
|
-
id: survivorId,
|
|
2021
|
-
kind: survivor.staged.kind,
|
|
2022
|
-
fromId,
|
|
2023
|
-
toId,
|
|
2024
|
-
fromKind,
|
|
2025
|
-
toKind,
|
|
2026
|
-
props,
|
|
2027
|
-
mergedIds
|
|
2028
|
-
});
|
|
2029
3180
|
}
|
|
2030
3181
|
const sortedEdges = merged.map((edge) => ({
|
|
2031
3182
|
edge,
|
|
2032
|
-
sortKey:
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
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
|
+
])
|
|
2038
3192
|
})).sort((left, right) => compareStrings(left.sortKey, right.sortKey)).map(({ edge }) => edge);
|
|
2039
3193
|
return {
|
|
2040
3194
|
edges: sortedEdges,
|
|
@@ -2086,7 +3240,7 @@ function validateResolveMap(resolve) {
|
|
|
2086
3240
|
return {};
|
|
2087
3241
|
}
|
|
2088
3242
|
const configs = resolve;
|
|
2089
|
-
const validated =
|
|
3243
|
+
const validated = createDataKeyedBag();
|
|
2090
3244
|
for (const [kind, config] of Object.entries(configs)) {
|
|
2091
3245
|
const parsed = resolveConfigScalarSchema.safeParse({
|
|
2092
3246
|
threshold: config.threshold
|
|
@@ -2114,7 +3268,7 @@ function validateResolveMap(resolve) {
|
|
|
2114
3268
|
}
|
|
2115
3269
|
validated[kind] = config;
|
|
2116
3270
|
}
|
|
2117
|
-
return validated;
|
|
3271
|
+
return { ...validated };
|
|
2118
3272
|
}
|
|
2119
3273
|
function validateProvenanceWeights(weights) {
|
|
2120
3274
|
for (const [branchId, weight] of weights) {
|
|
@@ -2169,7 +3323,9 @@ function normalizeMergeOptions(options = {}) {
|
|
|
2169
3323
|
...provenanceWeights === void 0 ? {} : { provenanceWeights }
|
|
2170
3324
|
};
|
|
2171
3325
|
}
|
|
2172
|
-
var
|
|
3326
|
+
var PROVENANCE_KIND = "Provenance";
|
|
3327
|
+
var PROVENANCE_OWNER_KIND = "ProvenanceOwner";
|
|
3328
|
+
var Provenance = defineNode(PROVENANCE_KIND, {
|
|
2173
3329
|
schema: z.object({
|
|
2174
3330
|
targetGraphId: z.string(),
|
|
2175
3331
|
role: z.enum(["node", "edge"]),
|
|
@@ -2179,11 +3335,31 @@ var Provenance = defineNode("Provenance", {
|
|
|
2179
3335
|
sourceId: z.string()
|
|
2180
3336
|
})
|
|
2181
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
|
+
});
|
|
2182
3348
|
function provenanceGraphId(targetGraphId) {
|
|
2183
3349
|
return `${targetGraphId}::merge-provenance`;
|
|
2184
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
|
+
}
|
|
2185
3361
|
function buildProvenanceGraph(targetGraphId) {
|
|
2186
|
-
return
|
|
3362
|
+
return defineInternalGraph({
|
|
2187
3363
|
id: provenanceGraphId(targetGraphId),
|
|
2188
3364
|
nodes: { Provenance: { type: Provenance } },
|
|
2189
3365
|
edges: {}
|
|
@@ -2191,26 +3367,276 @@ function buildProvenanceGraph(targetGraphId) {
|
|
|
2191
3367
|
}
|
|
2192
3368
|
async function openProvenanceStore(...args) {
|
|
2193
3369
|
const [backend, targetGraphId] = args.length === 1 ? [storeBackend(args[0]), args[0].graphId] : args;
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
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);
|
|
2198
3382
|
return store;
|
|
2199
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
|
+
}
|
|
2200
3617
|
var ID_SEPARATOR = "\0";
|
|
2201
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
|
+
}
|
|
2202
3631
|
async function provenanceNodeId(targetGraphId, record) {
|
|
2203
|
-
const tuple = [
|
|
3632
|
+
const tuple = encodeProvenanceTuple([
|
|
2204
3633
|
targetGraphId,
|
|
2205
3634
|
record.role,
|
|
2206
|
-
// canonicalKind is part of node identity: two contributions to different-kind
|
|
2207
|
-
// canonicals that share a bare id (e.g. base Patient:x and Encounter:x) must NOT
|
|
2208
|
-
// hash to the same sidecar row and clobber each other (the (kind,id) identity model).
|
|
2209
3635
|
record.canonicalKind,
|
|
2210
3636
|
record.canonicalId,
|
|
2211
3637
|
record.branchId,
|
|
2212
3638
|
record.sourceId
|
|
2213
|
-
]
|
|
3639
|
+
]);
|
|
2214
3640
|
const digest = await sha256Hex(tuple, ID_DIGEST_BYTES);
|
|
2215
3641
|
return `prov_${digest}`;
|
|
2216
3642
|
}
|
|
@@ -2218,7 +3644,7 @@ async function persistProvenanceRecords(store, targetGraphId, records) {
|
|
|
2218
3644
|
if (records.length === 0) {
|
|
2219
3645
|
return 0;
|
|
2220
3646
|
}
|
|
2221
|
-
const
|
|
3647
|
+
const identified = await Promise.all(
|
|
2222
3648
|
records.map(async (record) => ({
|
|
2223
3649
|
id: await provenanceNodeId(targetGraphId, record),
|
|
2224
3650
|
props: {
|
|
@@ -2231,6 +3657,11 @@ async function persistProvenanceRecords(store, targetGraphId, records) {
|
|
|
2231
3657
|
}
|
|
2232
3658
|
}))
|
|
2233
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()];
|
|
2234
3665
|
await store.nodes.Provenance.bulkUpsertById(items);
|
|
2235
3666
|
return items.length;
|
|
2236
3667
|
}
|
|
@@ -2255,7 +3686,7 @@ function sortedBucketKeys(blocks) {
|
|
|
2255
3686
|
}
|
|
2256
3687
|
var NODE_SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
2257
3688
|
function nodeProps(node) {
|
|
2258
|
-
const props =
|
|
3689
|
+
const props = createDataKeyedBag();
|
|
2259
3690
|
for (const [key, value] of Object.entries(
|
|
2260
3691
|
node
|
|
2261
3692
|
)) {
|
|
@@ -2598,6 +4029,11 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2598
4029
|
const newEdges = [];
|
|
2599
4030
|
const modifiedEdges = [];
|
|
2600
4031
|
const deletedEdges = [];
|
|
4032
|
+
const windowedNodes = [];
|
|
4033
|
+
const windowedEdges = [];
|
|
4034
|
+
const newIdentityAssertions = [];
|
|
4035
|
+
const retractedIdentityAssertions = [];
|
|
4036
|
+
const baseIdentityAssertions = await storeRuntime(baseStore).readCurrentIdentityAssertions("state");
|
|
2601
4037
|
let targetNodeVersions = /* @__PURE__ */ new Map();
|
|
2602
4038
|
let targetEdgeSignatures = /* @__PURE__ */ new Map();
|
|
2603
4039
|
for (const branch2 of branches) {
|
|
@@ -2629,6 +4065,22 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2629
4065
|
for (const edge of diff.edges.deleted) {
|
|
2630
4066
|
deletedEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
|
|
2631
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
|
+
}
|
|
2632
4084
|
}
|
|
2633
4085
|
return {
|
|
2634
4086
|
newNodesByKind: groupByKind(newNodes),
|
|
@@ -2645,6 +4097,25 @@ async function stageBranches(baseStore, branches, captureTargetStateFor) {
|
|
|
2645
4097
|
deletedEdges: [...deletedEdges].sort(
|
|
2646
4098
|
(left, right) => compareByKindIdBranch(left, right)
|
|
2647
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
|
+
),
|
|
2648
4119
|
targetNodeVersions,
|
|
2649
4120
|
targetEdgeSignatures
|
|
2650
4121
|
};
|
|
@@ -2760,7 +4231,6 @@ function reconcileTypes(clusters, closure, mode) {
|
|
|
2760
4231
|
}
|
|
2761
4232
|
|
|
2762
4233
|
// src/graph-merge/merge.ts
|
|
2763
|
-
var COMMITTED_TARGET_BRANCH = asBranchId("__committed_target__");
|
|
2764
4234
|
function asNode(staged) {
|
|
2765
4235
|
return {
|
|
2766
4236
|
kind: staged.node.kind,
|
|
@@ -2802,10 +4272,12 @@ async function precomputeEmbeddings(byKind, resolve, embedder) {
|
|
|
2802
4272
|
}
|
|
2803
4273
|
}
|
|
2804
4274
|
}
|
|
2805
|
-
const lookup = /* @__PURE__ */ new Map();
|
|
2806
4275
|
if (texts.size === 0) {
|
|
2807
|
-
return
|
|
4276
|
+
return /* @__PURE__ */ new Map();
|
|
2808
4277
|
}
|
|
4278
|
+
return embedSortedTexts(texts, embedder);
|
|
4279
|
+
}
|
|
4280
|
+
async function embedSortedTexts(texts, embedder) {
|
|
2809
4281
|
const orderedTexts = [...texts].sort(
|
|
2810
4282
|
(left, right) => compareStrings(left, right)
|
|
2811
4283
|
);
|
|
@@ -2819,6 +4291,7 @@ async function precomputeEmbeddings(byKind, resolve, embedder) {
|
|
|
2819
4291
|
}
|
|
2820
4292
|
);
|
|
2821
4293
|
}
|
|
4294
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
2822
4295
|
for (const [index, text] of orderedTexts.entries()) {
|
|
2823
4296
|
lookup.set(text, requireDefined(vectors[index]));
|
|
2824
4297
|
}
|
|
@@ -2842,24 +4315,7 @@ async function embedMissingPairTexts(base, pairs, strategy, embedder) {
|
|
|
2842
4315
|
if (missing.size === 0) {
|
|
2843
4316
|
return existing;
|
|
2844
4317
|
}
|
|
2845
|
-
|
|
2846
|
-
(left, right) => compareStrings(left, right)
|
|
2847
|
-
);
|
|
2848
|
-
const vectors = await embedder(orderedTexts);
|
|
2849
|
-
if (vectors.length !== orderedTexts.length) {
|
|
2850
|
-
throw new MergeError(
|
|
2851
|
-
`Embedder returned ${vectors.length} vectors for ${orderedTexts.length} texts; expected exactly one per text.`,
|
|
2852
|
-
{
|
|
2853
|
-
details: { texts: orderedTexts.length, vectors: vectors.length },
|
|
2854
|
-
suggestion: "Ensure MergeOptions.embedder returns one vector per input text, in order."
|
|
2855
|
-
}
|
|
2856
|
-
);
|
|
2857
|
-
}
|
|
2858
|
-
const augmented = new Map(existing);
|
|
2859
|
-
for (const [index, text] of orderedTexts.entries()) {
|
|
2860
|
-
augmented.set(text, requireDefined(vectors[index]));
|
|
2861
|
-
}
|
|
2862
|
-
return augmented;
|
|
4318
|
+
return new Map([...existing, ...await embedSortedTexts(missing, embedder)]);
|
|
2863
4319
|
}
|
|
2864
4320
|
async function generateAllCandidates(target, staging, options, introspectionKinds, ctx, useBaseSources, embedder) {
|
|
2865
4321
|
const allEdges = [];
|
|
@@ -2958,7 +4414,9 @@ function clusterMembersFor(cluster, newNodesById, baseMembersById) {
|
|
|
2958
4414
|
id: staged.node.id,
|
|
2959
4415
|
kind: staged.node.kind,
|
|
2960
4416
|
branchId: staged.branchId,
|
|
2961
|
-
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 }
|
|
2962
4420
|
});
|
|
2963
4421
|
}
|
|
2964
4422
|
const base = baseMembersById.get(key);
|
|
@@ -2989,37 +4447,70 @@ function indexNewNodesById(staging) {
|
|
|
2989
4447
|
}
|
|
2990
4448
|
return index;
|
|
2991
4449
|
}
|
|
2992
|
-
function buildStagedEdges(staging, modifiedEdges) {
|
|
4450
|
+
function buildStagedEdges(staging, modifiedEdges, edgeValidityEnds, edgeDeletions) {
|
|
2993
4451
|
const staged = [];
|
|
4452
|
+
const stagedIdentities = /* @__PURE__ */ new Set();
|
|
4453
|
+
const windowOnlyCarried = /* @__PURE__ */ new Set();
|
|
2994
4454
|
for (const items of staging.newEdgesByKind.values()) {
|
|
2995
4455
|
for (const item of items) {
|
|
2996
4456
|
staged.push(toStagedEdge(item.branchId, item));
|
|
2997
4457
|
}
|
|
2998
4458
|
}
|
|
2999
4459
|
for (const item of modifiedEdges) {
|
|
4460
|
+
const identity = mergeKeyOf(item.edge);
|
|
4461
|
+
stagedIdentities.add(identity);
|
|
4462
|
+
const validTo = edgeValidityEnds.get(identity);
|
|
3000
4463
|
staged.push({
|
|
3001
4464
|
id: item.edge.id,
|
|
3002
4465
|
kind: item.edge.kind,
|
|
4466
|
+
origin: INHERITED_EDGE_ORIGIN,
|
|
3003
4467
|
fromId: item.edge.fromId,
|
|
3004
4468
|
toId: item.edge.toId,
|
|
3005
4469
|
fromKind: item.edge.fromKind,
|
|
3006
4470
|
toKind: item.edge.toKind,
|
|
3007
4471
|
props: item.edge.forkProps,
|
|
3008
|
-
|
|
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
|
|
3009
4497
|
});
|
|
3010
4498
|
}
|
|
3011
|
-
return staged;
|
|
4499
|
+
return { edges: staged, windowOnlyCarried };
|
|
3012
4500
|
}
|
|
3013
4501
|
function toStagedEdge(branchId, item) {
|
|
3014
4502
|
return {
|
|
3015
4503
|
id: item.edge.id,
|
|
3016
4504
|
kind: item.edge.kind,
|
|
4505
|
+
origin: BRANCH_CREATED_EDGE_ORIGIN,
|
|
3017
4506
|
fromId: item.edge.fromId,
|
|
3018
4507
|
toId: item.edge.toId,
|
|
3019
4508
|
fromKind: item.edge.fromKind,
|
|
3020
4509
|
toKind: item.edge.toKind,
|
|
3021
4510
|
props: item.edge.props,
|
|
3022
|
-
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 }
|
|
3023
4514
|
};
|
|
3024
4515
|
}
|
|
3025
4516
|
function buildProvenanceIndex(records) {
|
|
@@ -3049,25 +4540,29 @@ function buildProvenanceIndex(records) {
|
|
|
3049
4540
|
byBranch: (branchId) => frozen.get(branchId) ?? { nodeIds: [], edgeIds: [] }
|
|
3050
4541
|
};
|
|
3051
4542
|
}
|
|
3052
|
-
function pushProvenance(records, role, branchId, canonicalId, canonicalKind, sourceId) {
|
|
3053
|
-
records.push({ role, canonicalId, canonicalKind, branchId, sourceId });
|
|
3054
|
-
}
|
|
3055
4543
|
var EMPTY_WEIGHTS = /* @__PURE__ */ new Map();
|
|
3056
|
-
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);
|
|
3057
4546
|
const provenanceRecords = [];
|
|
4547
|
+
const recordedContributions = /* @__PURE__ */ new Set();
|
|
3058
4548
|
const weights = options.provenanceWeights ?? EMPTY_WEIGHTS;
|
|
3059
4549
|
const recordProvenance = (role, branchId, canonicalId, canonicalKind, sourceId) => {
|
|
3060
4550
|
if (branchId === preferredBranchId) {
|
|
3061
4551
|
return;
|
|
3062
4552
|
}
|
|
3063
|
-
|
|
3064
|
-
provenanceRecords,
|
|
4553
|
+
const record = {
|
|
3065
4554
|
role,
|
|
3066
|
-
branchId,
|
|
3067
4555
|
canonicalId,
|
|
3068
4556
|
canonicalKind,
|
|
4557
|
+
branchId,
|
|
3069
4558
|
sourceId
|
|
3070
|
-
|
|
4559
|
+
};
|
|
4560
|
+
const key = contributionKey(record);
|
|
4561
|
+
if (recordedContributions.has(key)) {
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
4564
|
+
recordedContributions.add(key);
|
|
4565
|
+
provenanceRecords.push(record);
|
|
3071
4566
|
};
|
|
3072
4567
|
const newNodesById = indexNewNodesById(staging);
|
|
3073
4568
|
const acceptedEndpointKeys = /* @__PURE__ */ new Set();
|
|
@@ -3160,6 +4655,34 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3160
4655
|
for (const deletion of deleteModify.nodeDeletions) {
|
|
3161
4656
|
nodeDeletions.set(mergeKey(deletion.kind, deletion.id), deletion.kind);
|
|
3162
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;
|
|
3163
4686
|
for (const modification of deleteModify.survivingModifications) {
|
|
3164
4687
|
recordProvenance(
|
|
3165
4688
|
"node",
|
|
@@ -3208,6 +4731,31 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3208
4731
|
for (const conflict of reconciledEdgeModifications.conflicts) {
|
|
3209
4732
|
propertyConflicts.push(conflict);
|
|
3210
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
|
+
}
|
|
3211
4759
|
const reconciliation = reconcileTypes(
|
|
3212
4760
|
reconcileInputs,
|
|
3213
4761
|
subClassClosure,
|
|
@@ -3221,18 +4769,57 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3221
4769
|
clusters,
|
|
3222
4770
|
(cluster) => pickClusterCanonical(cluster, entityByIdentity)
|
|
3223
4771
|
);
|
|
3224
|
-
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(
|
|
3225
4807
|
staging,
|
|
3226
|
-
reconciledEdgeModifications.survivingModifications
|
|
4808
|
+
reconciledEdgeModifications.survivingModifications,
|
|
4809
|
+
validWindows.edgeEnds,
|
|
4810
|
+
edgeDeletions
|
|
3227
4811
|
);
|
|
3228
4812
|
const stagedEdgeBranches = /* @__PURE__ */ new Map();
|
|
3229
4813
|
for (const staged of stagedEdges) {
|
|
3230
|
-
const
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
} else {
|
|
4814
|
+
const identity2 = mergeKeyOf(staged);
|
|
4815
|
+
const branches = stagedEdgeBranches.get(staged.id) ?? /* @__PURE__ */ new Set();
|
|
4816
|
+
if (!windowOnlyCarried.has(identity2)) {
|
|
3234
4817
|
branches.add(staged.branchId);
|
|
3235
4818
|
}
|
|
4819
|
+
for (const branchId of validWindows.edgeCredits.get(identity2) ?? []) {
|
|
4820
|
+
branches.add(branchId);
|
|
4821
|
+
}
|
|
4822
|
+
stagedEdgeBranches.set(staged.id, branches);
|
|
3236
4823
|
}
|
|
3237
4824
|
const deletedNodeIdSet = new Set(nodeDeletions.keys());
|
|
3238
4825
|
const repoint2 = repointEdges(
|
|
@@ -3255,7 +4842,11 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3255
4842
|
...deleteModify.dropped,
|
|
3256
4843
|
...edgeDeleteModify.dropped,
|
|
3257
4844
|
...repoint2.dropped,
|
|
3258
|
-
...reconciliation.dropped
|
|
4845
|
+
...reconciliation.dropped,
|
|
4846
|
+
...identity.dropped,
|
|
4847
|
+
...overruledRetractionDrops,
|
|
4848
|
+
...identityRemap.dropped,
|
|
4849
|
+
...validWindows.dropped
|
|
3259
4850
|
].sort(
|
|
3260
4851
|
(left, right) => compareStrings(`${left.kind}|${left.id}`, `${right.kind}|${right.id}`)
|
|
3261
4852
|
);
|
|
@@ -3272,6 +4863,10 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3272
4863
|
])
|
|
3273
4864
|
),
|
|
3274
4865
|
retypeMap: reconciliation.retypeMap,
|
|
4866
|
+
canonicalOf,
|
|
4867
|
+
nodeValidityEnds: validWindows.nodeEnds,
|
|
4868
|
+
edgeValidityEnds: validWindows.edgeEnds,
|
|
4869
|
+
validityEnds: validWindows.resolutions,
|
|
3275
4870
|
// Order report arrays by the composite (kind, id) identity — never a bare id
|
|
3276
4871
|
// or a `|`-joined string. A bare id ties two different-kind entities that
|
|
3277
4872
|
// share an id, and a `|` separator collides on caller-supplied ids/property
|
|
@@ -3317,7 +4912,9 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
|
|
|
3317
4912
|
)
|
|
3318
4913
|
),
|
|
3319
4914
|
provenanceRecords,
|
|
3320
|
-
warnings: candidateWarnings
|
|
4915
|
+
warnings: candidateWarnings,
|
|
4916
|
+
identityAssertions: identityRemap.assertions,
|
|
4917
|
+
identityRetractions: survivingRetractions
|
|
3321
4918
|
};
|
|
3322
4919
|
}
|
|
3323
4920
|
function pickClusterCanonical(cluster, entityByIdentity) {
|
|
@@ -3332,78 +4929,125 @@ function pickClusterCanonical(cluster, entityByIdentity) {
|
|
|
3332
4929
|
);
|
|
3333
4930
|
}
|
|
3334
4931
|
function commitModificationProps(baseProps, forkProps) {
|
|
3335
|
-
const props =
|
|
4932
|
+
const props = createDataKeyedBag();
|
|
4933
|
+
for (const key of Object.keys(forkProps)) {
|
|
4934
|
+
props[key] = forkProps[key];
|
|
4935
|
+
}
|
|
3336
4936
|
for (const key of Object.keys(baseProps)) {
|
|
3337
|
-
if (!(key
|
|
4937
|
+
if (!hasOwnKey(forkProps, key)) {
|
|
3338
4938
|
props[key] = void 0;
|
|
3339
4939
|
}
|
|
3340
4940
|
}
|
|
3341
4941
|
return props;
|
|
3342
4942
|
}
|
|
3343
|
-
|
|
3344
|
-
|
|
4943
|
+
function finalEdgeEndpoint(plan, kind, id) {
|
|
4944
|
+
return { kind: plan.retypeMap.get(mergeKey(kind, id)) ?? kind, id };
|
|
4945
|
+
}
|
|
4946
|
+
function plannedNodeWrites(plan) {
|
|
3345
4947
|
const canonicalIdentities = /* @__PURE__ */ new Set();
|
|
3346
4948
|
for (const entity of plan.canonicalEntities) {
|
|
3347
4949
|
canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
|
|
3348
4950
|
}
|
|
3349
|
-
const
|
|
4951
|
+
const modificationsByIdentity = /* @__PURE__ */ new Map();
|
|
3350
4952
|
for (const modification of plan.survivingModifications) {
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
4953
|
+
modificationsByIdentity.set(
|
|
4954
|
+
mergeKeyOf(modification.node),
|
|
4955
|
+
modification.node
|
|
4956
|
+
);
|
|
3355
4957
|
}
|
|
4958
|
+
const writes = [];
|
|
4959
|
+
const written = /* @__PURE__ */ new Set();
|
|
3356
4960
|
for (const modification of plan.survivingModifications) {
|
|
3357
4961
|
const identity = mergeKeyOf(modification.node);
|
|
3358
4962
|
if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
|
|
3359
4963
|
continue;
|
|
3360
4964
|
}
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
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
|
+
});
|
|
3370
4974
|
}
|
|
3371
4975
|
for (const entity of plan.canonicalEntities) {
|
|
3372
|
-
const
|
|
3373
|
-
if (plan.nodeDeletions.has(
|
|
4976
|
+
const sourceIdentity = mergeKey(entity.kind, entity.canonicalId);
|
|
4977
|
+
if (plan.nodeDeletions.has(sourceIdentity)) {
|
|
3374
4978
|
continue;
|
|
3375
4979
|
}
|
|
3376
|
-
const kind = plan.retypeMap.get(
|
|
3377
|
-
const modification =
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
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
|
+
});
|
|
3388
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();
|
|
3389
5018
|
for (const [identity, kind] of plan.nodeDeletions) {
|
|
3390
5019
|
const collection = nodeCollection(nodesApi, kind);
|
|
3391
5020
|
await collection.delete(idOf(identity));
|
|
3392
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
|
+
}
|
|
3393
5037
|
for (const [identity, kind] of plan.edgeDeletions) {
|
|
3394
5038
|
await edgeCollection(edgesApi, kind).delete(idOf(identity));
|
|
3395
5039
|
}
|
|
3396
5040
|
const edgesByKind = /* @__PURE__ */ new Map();
|
|
3397
5041
|
for (const edge of plan.mergedEdges) {
|
|
3398
|
-
const fromKind = plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId));
|
|
3399
|
-
const toKind = plan.retypeMap.get(mergeKey(edge.toKind, edge.toId));
|
|
3400
5042
|
const edgeBaseProps = plan.inheritedEdgeBaseProps.get(edge.id);
|
|
3401
5043
|
const props = edgeBaseProps === void 0 ? edge.props : commitModificationProps(edgeBaseProps, edge.props);
|
|
3402
5044
|
const item = {
|
|
3403
5045
|
id: edge.id,
|
|
3404
|
-
from:
|
|
3405
|
-
to:
|
|
3406
|
-
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 }
|
|
3407
5051
|
};
|
|
3408
5052
|
const existing = edgesByKind.get(edge.kind);
|
|
3409
5053
|
if (existing === void 0) {
|
|
@@ -3417,7 +5061,31 @@ async function applyMergePlan(plan, nodesApi, edgesApi) {
|
|
|
3417
5061
|
await edgeCollection(edgesApi, kind).bulkUpsertById(items);
|
|
3418
5062
|
committedEdges += items.length;
|
|
3419
5063
|
}
|
|
3420
|
-
|
|
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
|
+
};
|
|
3421
5089
|
}
|
|
3422
5090
|
async function commitPlan(target, plan, expectedBaseVersion) {
|
|
3423
5091
|
if (!storeBackend(target).capabilities.transactions) {
|
|
@@ -3435,10 +5103,13 @@ async function commitPlan(target, plan, expectedBaseVersion) {
|
|
|
3435
5103
|
expectedBaseVersion
|
|
3436
5104
|
);
|
|
3437
5105
|
}
|
|
5106
|
+
await assertPlannedIdentityIdsFresh(target, transactionBackend(tx), plan);
|
|
3438
5107
|
return applyMergePlan(
|
|
3439
5108
|
plan,
|
|
3440
5109
|
tx.nodes,
|
|
3441
|
-
tx.edges
|
|
5110
|
+
tx.edges,
|
|
5111
|
+
target,
|
|
5112
|
+
transactionBackend(tx)
|
|
3442
5113
|
);
|
|
3443
5114
|
}, mergeCommitTransactionOptions(target))
|
|
3444
5115
|
);
|
|
@@ -3487,7 +5158,8 @@ async function assertTargetUnchanged(txBackend, target, expectedBaseVersion) {
|
|
|
3487
5158
|
const liveContent = await computeContentComponent(
|
|
3488
5159
|
txBackend,
|
|
3489
5160
|
target.graphId,
|
|
3490
|
-
target.graph
|
|
5161
|
+
target.graph,
|
|
5162
|
+
await storeRuntime(target).identityAssertionsAtTarget(txBackend, "state")
|
|
3491
5163
|
);
|
|
3492
5164
|
const expectedContent = contentComponentOf(expectedBaseVersion);
|
|
3493
5165
|
if (liveContent !== expectedContent) {
|
|
@@ -3541,11 +5213,41 @@ async function validateBaseVersions(target, branches) {
|
|
|
3541
5213
|
}
|
|
3542
5214
|
return ok(targetVersion);
|
|
3543
5215
|
}
|
|
3544
|
-
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
|
+
}
|
|
3545
5228
|
try {
|
|
3546
5229
|
return ok(normalizeMergeOptions(optionsInput));
|
|
3547
5230
|
} catch (error) {
|
|
3548
|
-
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
|
+
);
|
|
3549
5251
|
}
|
|
3550
5252
|
}
|
|
3551
5253
|
async function resolveMerge(store, target, branches, options, useBaseSources, incremental, expectedBaseVersion) {
|
|
@@ -3571,9 +5273,44 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3571
5273
|
targetBranchSeen = true;
|
|
3572
5274
|
}
|
|
3573
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
|
+
}
|
|
3574
5310
|
try {
|
|
3575
5311
|
const preferredBranchId = incremental?.targetBranchId;
|
|
3576
5312
|
const staging = await stageBranches(store, branches, preferredBranchId);
|
|
5313
|
+
const stagedNewByKind = newNodesByKind(staging);
|
|
3577
5314
|
const branchIds = branches.map((branch2) => branch2.id);
|
|
3578
5315
|
const branchOrder = preferredBranchId === void 0 ? options.branchOrder ?? [] : [
|
|
3579
5316
|
preferredBranchId,
|
|
@@ -3589,7 +5326,7 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3589
5326
|
}
|
|
3590
5327
|
const subClassClosure = buildSubClassClosure(introspection.ontology);
|
|
3591
5328
|
const embeddings = options.embedder === void 0 ? void 0 : await precomputeEmbeddings(
|
|
3592
|
-
|
|
5329
|
+
stagedNewByKind,
|
|
3593
5330
|
options.resolve,
|
|
3594
5331
|
options.embedder
|
|
3595
5332
|
);
|
|
@@ -3609,6 +5346,35 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3609
5346
|
if (isErr(candidates)) {
|
|
3610
5347
|
return err(candidates.error);
|
|
3611
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
|
+
};
|
|
3612
5378
|
const plan = planMerge(
|
|
3613
5379
|
staging,
|
|
3614
5380
|
candidates.data.edges,
|
|
@@ -3617,12 +5383,63 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3617
5383
|
options,
|
|
3618
5384
|
branchRank,
|
|
3619
5385
|
subClassClosure,
|
|
5386
|
+
identityContext,
|
|
5387
|
+
storedIdentityRowsById,
|
|
5388
|
+
targetPeers,
|
|
3620
5389
|
preferredBranchId
|
|
3621
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
|
+
}
|
|
3622
5437
|
const merged = incremental === void 0 ? await commitPlan(target, plan, expectedBaseVersion) : await commitIncrementalPlan(target, plan, {
|
|
3623
|
-
stagedNewByKind
|
|
5438
|
+
stagedNewByKind,
|
|
3624
5439
|
options,
|
|
3625
5440
|
introspectionKinds,
|
|
5441
|
+
forkPoint: incremental.forkPoint,
|
|
5442
|
+
...identityGuard === void 0 ? {} : { identityPeerProbe: identityGuard },
|
|
3626
5443
|
// The committed (kind, id) keys the base sources matched at PLAN time;
|
|
3627
5444
|
// anything NEW the in-tx re-probe surfaces is a window write.
|
|
3628
5445
|
plannedBaseMatchKeys: new Set(
|
|
@@ -3634,11 +5451,10 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3634
5451
|
const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
|
|
3635
5452
|
const warnings = [...plan.warnings];
|
|
3636
5453
|
let provenancePersisted;
|
|
3637
|
-
if (
|
|
5454
|
+
if (provenanceStore !== void 0 && !isErr(provenanceStore)) {
|
|
3638
5455
|
try {
|
|
3639
|
-
const provenanceStore = await openProvenanceStore(target);
|
|
3640
5456
|
const count = await persistProvenanceRecords(
|
|
3641
|
-
provenanceStore,
|
|
5457
|
+
provenanceStore.data,
|
|
3642
5458
|
target.graphId,
|
|
3643
5459
|
plan.provenanceRecords
|
|
3644
5460
|
);
|
|
@@ -3659,6 +5475,7 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
|
|
|
3659
5475
|
deleteModifyConflicts: plan.deleteModifyConflicts,
|
|
3660
5476
|
typeReconciliations: plan.typeReconciliations,
|
|
3661
5477
|
dropped: plan.dropped,
|
|
5478
|
+
validityEnds: plan.validityEnds,
|
|
3662
5479
|
baseAmbiguities: plan.baseAmbiguities,
|
|
3663
5480
|
provenance,
|
|
3664
5481
|
warnings,
|
|
@@ -3752,7 +5569,7 @@ function analyzeRevalidatingWrite(schema, current, planned) {
|
|
|
3752
5569
|
};
|
|
3753
5570
|
}
|
|
3754
5571
|
function nodeProps2(node) {
|
|
3755
|
-
const props =
|
|
5572
|
+
const props = createDataKeyedBag();
|
|
3756
5573
|
for (const [key, value] of Object.entries(node)) {
|
|
3757
5574
|
if (key === "id" || key === "kind" || key === "meta") continue;
|
|
3758
5575
|
props[key] = value;
|
|
@@ -3760,7 +5577,7 @@ function nodeProps2(node) {
|
|
|
3760
5577
|
return props;
|
|
3761
5578
|
}
|
|
3762
5579
|
function edgeProps(edge) {
|
|
3763
|
-
const props =
|
|
5580
|
+
const props = createDataKeyedBag();
|
|
3764
5581
|
for (const [key, value] of Object.entries(edge)) {
|
|
3765
5582
|
if (key === "id" || key === "kind" || key === "fromKind" || key === "fromId" || key === "toKind" || key === "toId" || key === "meta") {
|
|
3766
5583
|
continue;
|
|
@@ -3770,70 +5587,14 @@ function edgeProps(edge) {
|
|
|
3770
5587
|
return props;
|
|
3771
5588
|
}
|
|
3772
5589
|
var INCLUDE_TOMBSTONES = { temporalMode: "includeTombstones" };
|
|
3773
|
-
function finalEdgeFrom(edge, plan) {
|
|
3774
|
-
return {
|
|
3775
|
-
kind: plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId)) ?? edge.fromKind,
|
|
3776
|
-
id: edge.fromId
|
|
3777
|
-
};
|
|
3778
|
-
}
|
|
3779
|
-
function finalEdgeTo(edge, plan) {
|
|
3780
|
-
return {
|
|
3781
|
-
kind: plan.retypeMap.get(mergeKey(edge.toKind, edge.toId)) ?? edge.toKind,
|
|
3782
|
-
id: edge.toId
|
|
3783
|
-
};
|
|
3784
|
-
}
|
|
3785
5590
|
function assertValidRevalidatingWrite(analysis, message, details) {
|
|
3786
5591
|
if (analysis.status === "invalid") {
|
|
3787
5592
|
throw new MergeError(message, { details });
|
|
3788
5593
|
}
|
|
3789
5594
|
}
|
|
3790
|
-
function describeCause(cause) {
|
|
3791
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
3792
|
-
}
|
|
3793
|
-
function plannedNodeWrites(plan) {
|
|
3794
|
-
const canonicalIdentities = /* @__PURE__ */ new Set();
|
|
3795
|
-
for (const entity of plan.canonicalEntities) {
|
|
3796
|
-
canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
|
|
3797
|
-
}
|
|
3798
|
-
const survivingModProps = /* @__PURE__ */ new Map();
|
|
3799
|
-
for (const modification of plan.survivingModifications) {
|
|
3800
|
-
survivingModProps.set(
|
|
3801
|
-
mergeKeyOf(modification.node),
|
|
3802
|
-
modification.node.forkProps
|
|
3803
|
-
);
|
|
3804
|
-
}
|
|
3805
|
-
const writes = [];
|
|
3806
|
-
for (const modification of plan.survivingModifications) {
|
|
3807
|
-
const identity = mergeKeyOf(modification.node);
|
|
3808
|
-
if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
|
|
3809
|
-
continue;
|
|
3810
|
-
}
|
|
3811
|
-
writes.push({
|
|
3812
|
-
identity,
|
|
3813
|
-
kind: modification.node.kind,
|
|
3814
|
-
id: modification.node.id,
|
|
3815
|
-
props: modification.node.forkProps
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3818
|
-
for (const entity of plan.canonicalEntities) {
|
|
3819
|
-
const sourceIdentity = mergeKey(entity.kind, entity.canonicalId);
|
|
3820
|
-
if (plan.nodeDeletions.has(sourceIdentity)) {
|
|
3821
|
-
continue;
|
|
3822
|
-
}
|
|
3823
|
-
const kind = plan.retypeMap.get(sourceIdentity) ?? entity.kind;
|
|
3824
|
-
const forkProps = survivingModProps.get(sourceIdentity);
|
|
3825
|
-
writes.push({
|
|
3826
|
-
identity: mergeKey(kind, entity.canonicalId),
|
|
3827
|
-
kind,
|
|
3828
|
-
id: entity.canonicalId,
|
|
3829
|
-
props: forkProps === void 0 ? entity.props : { ...entity.props, ...forkProps }
|
|
3830
|
-
});
|
|
3831
|
-
}
|
|
3832
|
-
return writes;
|
|
3833
|
-
}
|
|
3834
5595
|
function edgeWriteSignature(edge, plan) {
|
|
3835
|
-
const from =
|
|
3836
|
-
const to =
|
|
5596
|
+
const from = finalEdgeEndpoint(plan, edge.fromKind, edge.fromId);
|
|
5597
|
+
const to = finalEdgeEndpoint(plan, edge.toKind, edge.toId);
|
|
3837
5598
|
return JSON.stringify([
|
|
3838
5599
|
edge.kind,
|
|
3839
5600
|
from.kind,
|
|
@@ -3875,7 +5636,7 @@ async function validateIncrementalNodeWrites(target, nodesApi, plan) {
|
|
|
3875
5636
|
const analysis = analyzeRevalidatingWrite(
|
|
3876
5637
|
nodeSchemaFor(target, write.kind),
|
|
3877
5638
|
nodeProps2(current),
|
|
3878
|
-
write.
|
|
5639
|
+
nodeWriteProps(write, (modification) => modification.forkProps)
|
|
3879
5640
|
);
|
|
3880
5641
|
assertValidRevalidatingWrite(
|
|
3881
5642
|
analysis,
|
|
@@ -3976,8 +5737,8 @@ async function validateIncrementalEdgeWrites(target, edgesApi, plan) {
|
|
|
3976
5737
|
{ details: { id: edge.id, committedKind: current.kind } }
|
|
3977
5738
|
);
|
|
3978
5739
|
}
|
|
3979
|
-
const from =
|
|
3980
|
-
const to =
|
|
5740
|
+
const from = finalEdgeEndpoint(plan, edge.fromKind, edge.fromId);
|
|
5741
|
+
const to = finalEdgeEndpoint(plan, edge.toKind, edge.toId);
|
|
3981
5742
|
if (current.fromId !== from.id || current.fromKind !== from.kind || current.toId !== to.id || current.toKind !== to.kind) {
|
|
3982
5743
|
throw new MergeError(
|
|
3983
5744
|
`mergeIncremental() would overwrite committed edge "${edge.id}" (kind "${edge.kind}") with different endpoints.`,
|
|
@@ -4149,6 +5910,20 @@ async function assertInheritedEdgesUnchanged(edgesApi, guard, plan) {
|
|
|
4149
5910
|
)
|
|
4150
5911
|
});
|
|
4151
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
|
+
}
|
|
4152
5927
|
async function commitIncrementalPlan(target, plan, guard) {
|
|
4153
5928
|
if (!storeBackend(target).capabilities.transactions) {
|
|
4154
5929
|
throw new MergeError(
|
|
@@ -4161,28 +5936,48 @@ async function commitIncrementalPlan(target, plan, guard) {
|
|
|
4161
5936
|
if (target.revisionTrackingEnabled) {
|
|
4162
5937
|
await lockRecordedGraphWrite(transactionBackend(tx), target.graphId);
|
|
4163
5938
|
}
|
|
5939
|
+
await assertForkPointUnchanged(guard.forkPoint);
|
|
4164
5940
|
const nodesApi = tx.nodes;
|
|
4165
5941
|
const edgesApi = tx.edges;
|
|
4166
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
|
+
);
|
|
4167
5950
|
await assertInheritedTargetUnchanged(nodesApi, edgesApi, guard, plan);
|
|
4168
5951
|
await validateIncrementalNodeWrites(target, nodesApi, plan);
|
|
4169
5952
|
await validateIncrementalEdgeWrites(target, edgesApi, plan);
|
|
4170
|
-
return applyMergePlan(
|
|
5953
|
+
return applyMergePlan(
|
|
5954
|
+
plan,
|
|
5955
|
+
nodesApi,
|
|
5956
|
+
edgesApi,
|
|
5957
|
+
target,
|
|
5958
|
+
transactionBackend(tx)
|
|
5959
|
+
);
|
|
4171
5960
|
}, mergeCommitTransactionOptions(target))
|
|
4172
5961
|
);
|
|
4173
5962
|
}
|
|
4174
5963
|
async function mergeIncremental(args) {
|
|
4175
5964
|
const { forkPoint, target, branches } = args;
|
|
4176
|
-
const normalized = tryNormalize(args.options ?? {});
|
|
5965
|
+
const normalized = tryNormalize(args.options ?? {}, ["target"]);
|
|
4177
5966
|
if (isErr(normalized)) {
|
|
4178
5967
|
return err(normalized.error);
|
|
4179
5968
|
}
|
|
4180
5969
|
const options = normalized.data;
|
|
4181
5970
|
if (options.onBasePropertyConflict !== "flag") {
|
|
4182
5971
|
return err(
|
|
4183
|
-
new
|
|
5972
|
+
new InvalidMergeOptionsError(
|
|
4184
5973
|
'mergeIncremental() requires onBasePropertyConflict: "flag" (keep-base); a non-keep-base policy could overwrite a newer committed base value with a stale branch value.',
|
|
4185
|
-
{
|
|
5974
|
+
{
|
|
5975
|
+
details: {
|
|
5976
|
+
option: "onBasePropertyConflict",
|
|
5977
|
+
accepted: "flag",
|
|
5978
|
+
received: options.onBasePropertyConflict
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
4186
5981
|
)
|
|
4187
5982
|
);
|
|
4188
5983
|
}
|
|
@@ -4214,10 +6009,16 @@ async function mergeIncremental(args) {
|
|
|
4214
6009
|
[targetBranch, ...branches],
|
|
4215
6010
|
options,
|
|
4216
6011
|
true,
|
|
4217
|
-
{
|
|
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
|
+
}
|
|
4218
6019
|
);
|
|
4219
6020
|
}
|
|
4220
6021
|
|
|
4221
|
-
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 };
|
|
4222
6023
|
//# sourceMappingURL=index.js.map
|
|
4223
6024
|
//# sourceMappingURL=index.js.map
|