@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.
Files changed (392) hide show
  1. package/dist/backend/drizzle/indexes.cjs +11 -11
  2. package/dist/backend/drizzle/indexes.d.cts +1 -1
  3. package/dist/backend/drizzle/indexes.d.ts +1 -1
  4. package/dist/backend/drizzle/indexes.js +7 -7
  5. package/dist/backend/index.cjs +53 -54
  6. package/dist/backend/index.d.cts +3 -3
  7. package/dist/backend/index.d.ts +3 -3
  8. package/dist/backend/index.js +13 -14
  9. package/dist/backend/postgres/index.cjs +29 -30
  10. package/dist/backend/postgres/index.d.cts +7 -7
  11. package/dist/backend/postgres/index.d.ts +7 -7
  12. package/dist/backend/postgres/index.js +16 -17
  13. package/dist/backend/postgres/pglite-store.cjs +48 -43
  14. package/dist/backend/postgres/pglite-store.cjs.map +1 -1
  15. package/dist/backend/postgres/pglite-store.d.cts +15 -9
  16. package/dist/backend/postgres/pglite-store.d.ts +15 -9
  17. package/dist/backend/postgres/pglite-store.js +44 -39
  18. package/dist/backend/postgres/pglite-store.js.map +1 -1
  19. package/dist/backend/postgres/pglite.cjs +18 -19
  20. package/dist/backend/postgres/pglite.d.cts +4 -4
  21. package/dist/backend/postgres/pglite.d.ts +4 -4
  22. package/dist/backend/postgres/pglite.js +17 -18
  23. package/dist/backend/sqlite/index.cjs +26 -27
  24. package/dist/backend/sqlite/index.d.cts +6 -6
  25. package/dist/backend/sqlite/index.d.ts +6 -6
  26. package/dist/backend/sqlite/index.js +14 -15
  27. package/dist/backend/sqlite/libsql.cjs +21 -22
  28. package/dist/backend/sqlite/libsql.cjs.map +1 -1
  29. package/dist/backend/sqlite/libsql.d.cts +15 -8
  30. package/dist/backend/sqlite/libsql.d.ts +15 -8
  31. package/dist/backend/sqlite/libsql.js +17 -18
  32. package/dist/backend/sqlite/libsql.js.map +1 -1
  33. package/dist/backend/sqlite/local-store.cjs +49 -44
  34. package/dist/backend/sqlite/local-store.cjs.map +1 -1
  35. package/dist/backend/sqlite/local-store.d.cts +15 -10
  36. package/dist/backend/sqlite/local-store.d.ts +15 -10
  37. package/dist/backend/sqlite/local-store.js +45 -40
  38. package/dist/backend/sqlite/local-store.js.map +1 -1
  39. package/dist/backend/sqlite/local.cjs +19 -20
  40. package/dist/backend/sqlite/local.d.cts +3 -3
  41. package/dist/backend/sqlite/local.d.ts +3 -3
  42. package/dist/backend/sqlite/local.js +17 -18
  43. package/dist/{chunk-XMYJY7NI.js → chunk-2LG2XB2M.js} +3 -3
  44. package/dist/{chunk-XMYJY7NI.js.map → chunk-2LG2XB2M.js.map} +1 -1
  45. package/dist/{chunk-QGKILWGZ.cjs → chunk-2NA5DYLO.cjs} +4 -4
  46. package/dist/{chunk-QGKILWGZ.cjs.map → chunk-2NA5DYLO.cjs.map} +1 -1
  47. package/dist/{chunk-5MUSU4ZP.cjs → chunk-3DHZCOT7.cjs} +20 -20
  48. package/dist/chunk-3DHZCOT7.cjs.map +1 -0
  49. package/dist/{chunk-OVUUTCPJ.cjs → chunk-4UGOKMI5.cjs} +268 -150
  50. package/dist/chunk-4UGOKMI5.cjs.map +1 -0
  51. package/dist/{chunk-FRHHDHKH.js → chunk-5262ZMTR.js} +26 -5
  52. package/dist/chunk-5262ZMTR.js.map +1 -0
  53. package/dist/{chunk-HOIFFY4M.js → chunk-5763DULU.js} +248 -76
  54. package/dist/chunk-5763DULU.js.map +1 -0
  55. package/dist/{chunk-7G32LIQM.js → chunk-5UVRGXD3.js} +277 -7
  56. package/dist/chunk-5UVRGXD3.js.map +1 -0
  57. package/dist/{chunk-IIT5K7SK.cjs → chunk-62ZP23AW.cjs} +15 -15
  58. package/dist/{chunk-IIT5K7SK.cjs.map → chunk-62ZP23AW.cjs.map} +1 -1
  59. package/dist/{chunk-S4KQ5LV4.js → chunk-775A4ZMN.js} +40 -6
  60. package/dist/chunk-775A4ZMN.js.map +1 -0
  61. package/dist/{chunk-QYWINYEA.cjs → chunk-7BQXPCGN.cjs} +830 -170
  62. package/dist/chunk-7BQXPCGN.cjs.map +1 -0
  63. package/dist/{chunk-FJMPR3OH.cjs → chunk-7EO3TCQB.cjs} +33 -7
  64. package/dist/chunk-7EO3TCQB.cjs.map +1 -0
  65. package/dist/chunk-7GT72YHH.cjs +1979 -0
  66. package/dist/chunk-7GT72YHH.cjs.map +1 -0
  67. package/dist/{chunk-SQRD2CYF.js → chunk-7VQXLT5O.js} +717 -76
  68. package/dist/chunk-7VQXLT5O.js.map +1 -0
  69. package/dist/{chunk-BCP7LLOY.js → chunk-7Z5OR7KO.js} +2706 -625
  70. package/dist/chunk-7Z5OR7KO.js.map +1 -0
  71. package/dist/chunk-A4OC4DVN.cjs +12 -0
  72. package/dist/{chunk-4SZ6XPTG.cjs.map → chunk-A4OC4DVN.cjs.map} +1 -1
  73. package/dist/{chunk-TDXIBCGY.cjs → chunk-A7J2YDDI.cjs} +5 -3
  74. package/dist/chunk-A7J2YDDI.cjs.map +1 -0
  75. package/dist/{chunk-FMMIX6N4.js → chunk-AKLS3BYZ.js} +56 -5
  76. package/dist/chunk-AKLS3BYZ.js.map +1 -0
  77. package/dist/{chunk-D7PG5D5K.js → chunk-AMKPPFUO.js} +32 -6
  78. package/dist/chunk-AMKPPFUO.js.map +1 -0
  79. package/dist/{chunk-PAW2XUZN.js → chunk-ASTI57H3.js} +3 -3
  80. package/dist/{chunk-PAW2XUZN.js.map → chunk-ASTI57H3.js.map} +1 -1
  81. package/dist/{chunk-FMGEZBAQ.js → chunk-BKCUOV7A.js} +27 -5
  82. package/dist/chunk-BKCUOV7A.js.map +1 -0
  83. package/dist/chunk-BSFJMMV3.cjs +268 -0
  84. package/dist/chunk-BSFJMMV3.cjs.map +1 -0
  85. package/dist/{chunk-63KCKSSH.js → chunk-C23WSN5E.js} +209 -24
  86. package/dist/chunk-C23WSN5E.js.map +1 -0
  87. package/dist/{chunk-JRP3VGHS.js → chunk-CBTIUB76.js} +3 -3
  88. package/dist/{chunk-JRP3VGHS.js.map → chunk-CBTIUB76.js.map} +1 -1
  89. package/dist/chunk-CNV4TXJG.js +58 -0
  90. package/dist/chunk-CNV4TXJG.js.map +1 -0
  91. package/dist/{chunk-QB6VMOJ2.cjs → chunk-CXBHOOIQ.cjs} +45 -10
  92. package/dist/chunk-CXBHOOIQ.cjs.map +1 -0
  93. package/dist/{chunk-46GDII45.js → chunk-DCQVHAYQ.js} +5 -5
  94. package/dist/{chunk-46GDII45.js.map → chunk-DCQVHAYQ.js.map} +1 -1
  95. package/dist/chunk-DLNROMQ7.js +306 -0
  96. package/dist/chunk-DLNROMQ7.js.map +1 -0
  97. package/dist/chunk-DYATYRY4.js +47 -0
  98. package/dist/chunk-DYATYRY4.js.map +1 -0
  99. package/dist/chunk-E4WNQBEF.cjs +217 -0
  100. package/dist/chunk-E4WNQBEF.cjs.map +1 -0
  101. package/dist/{chunk-G6Q5UM7U.js → chunk-FWJKWH6U.js} +5 -3
  102. package/dist/chunk-FWJKWH6U.js.map +1 -0
  103. package/dist/{chunk-BNUFUFAU.js → chunk-HFHTMECK.js} +7 -7
  104. package/dist/chunk-HFHTMECK.js.map +1 -0
  105. package/dist/{chunk-TFMVL4QH.js → chunk-I5MOXA4H.js} +20 -14
  106. package/dist/chunk-I5MOXA4H.js.map +1 -0
  107. package/dist/{chunk-4MB3AQAQ.js → chunk-J3WZ3FA2.js} +77 -3
  108. package/dist/chunk-J3WZ3FA2.js.map +1 -0
  109. package/dist/{chunk-UR7W5YQK.js → chunk-JBQQ7THH.js} +5 -3
  110. package/dist/chunk-JBQQ7THH.js.map +1 -0
  111. package/dist/chunk-JIVMTW7W.js +3393 -0
  112. package/dist/chunk-JIVMTW7W.js.map +1 -0
  113. package/dist/{chunk-2Y4IFKW7.js → chunk-JXA2ZP2Q.js} +6 -17
  114. package/dist/chunk-JXA2ZP2Q.js.map +1 -0
  115. package/dist/{chunk-GYA4JRP5.js → chunk-JXXDZTMN.js} +2 -2
  116. package/dist/chunk-JXXDZTMN.js.map +1 -0
  117. package/dist/chunk-JZUR7ZWE.js +254 -0
  118. package/dist/chunk-JZUR7ZWE.js.map +1 -0
  119. package/dist/chunk-K5ELX2GT.js +248 -0
  120. package/dist/chunk-K5ELX2GT.js.map +1 -0
  121. package/dist/chunk-LBJKZXDE.cjs +257 -0
  122. package/dist/chunk-LBJKZXDE.cjs.map +1 -0
  123. package/dist/{chunk-FGO5JFVJ.cjs → chunk-M4BDU5RX.cjs} +12 -12
  124. package/dist/{chunk-FGO5JFVJ.cjs.map → chunk-M4BDU5RX.cjs.map} +1 -1
  125. package/dist/{chunk-MIJKEVV7.js → chunk-MSY7FSX4.js} +37 -4
  126. package/dist/chunk-MSY7FSX4.js.map +1 -0
  127. package/dist/{chunk-ST3LPB2V.cjs → chunk-MUK4BU7K.cjs} +788 -146
  128. package/dist/chunk-MUK4BU7K.cjs.map +1 -0
  129. package/dist/{chunk-MCHP7RHZ.cjs → chunk-MXJDTH5B.cjs} +64 -41
  130. package/dist/chunk-MXJDTH5B.cjs.map +1 -0
  131. package/dist/{chunk-2VLBGQBY.cjs → chunk-N3257XE6.cjs} +724 -577
  132. package/dist/chunk-N3257XE6.cjs.map +1 -0
  133. package/dist/chunk-N66HCGCY.cjs +57 -0
  134. package/dist/chunk-N66HCGCY.cjs.map +1 -0
  135. package/dist/{chunk-CPEL3YIR.cjs → chunk-NFCVHQ5P.cjs} +55 -22
  136. package/dist/chunk-NFCVHQ5P.cjs.map +1 -0
  137. package/dist/{chunk-C4F7VJ5D.cjs → chunk-NI72YOJB.cjs} +84 -2
  138. package/dist/chunk-NI72YOJB.cjs.map +1 -0
  139. package/dist/{chunk-EE7N5XXQ.cjs → chunk-NK5Q7UNE.cjs} +10 -21
  140. package/dist/chunk-NK5Q7UNE.cjs.map +1 -0
  141. package/dist/{chunk-PHXITZE4.js → chunk-NMWDXFPH.js} +4 -4
  142. package/dist/{chunk-PHXITZE4.js.map → chunk-NMWDXFPH.js.map} +1 -1
  143. package/dist/{chunk-FSXTPBRN.js → chunk-NQ6BRJBE.js} +416 -850
  144. package/dist/chunk-NQ6BRJBE.js.map +1 -0
  145. package/dist/chunk-OH5E5KIO.js +2057 -0
  146. package/dist/chunk-OH5E5KIO.js.map +1 -0
  147. package/dist/chunk-ONU3HIED.cjs +2082 -0
  148. package/dist/chunk-ONU3HIED.cjs.map +1 -0
  149. package/dist/{chunk-STQ3BSED.cjs → chunk-PNN7RHVR.cjs} +66 -44
  150. package/dist/chunk-PNN7RHVR.cjs.map +1 -0
  151. package/dist/{chunk-6DCB664D.js → chunk-PQBQG7IM.js} +3 -3
  152. package/dist/{chunk-6DCB664D.js.map → chunk-PQBQG7IM.js.map} +1 -1
  153. package/dist/{chunk-ACSVZPK7.cjs → chunk-PWLJ77UV.cjs} +5 -3
  154. package/dist/chunk-PWLJ77UV.cjs.map +1 -0
  155. package/dist/chunk-Q7Q4WSLJ.cjs +328 -0
  156. package/dist/chunk-Q7Q4WSLJ.cjs.map +1 -0
  157. package/dist/{chunk-BEFM74L2.js → chunk-QLRESYPU.js} +637 -493
  158. package/dist/chunk-QLRESYPU.js.map +1 -0
  159. package/dist/{chunk-SBPC5IP3.js → chunk-QWX5YA3K.js} +784 -202
  160. package/dist/chunk-QWX5YA3K.js.map +1 -0
  161. package/dist/{chunk-TCRIQMBU.js → chunk-RJS7DECT.js} +8 -9
  162. package/dist/chunk-RJS7DECT.js.map +1 -0
  163. package/dist/{chunk-6JZUSDY2.cjs → chunk-RMQLSPA2.cjs} +297 -27
  164. package/dist/chunk-RMQLSPA2.cjs.map +1 -0
  165. package/dist/{chunk-SJDMVITW.cjs → chunk-RT5ZY4XA.cjs} +6 -5
  166. package/dist/chunk-RT5ZY4XA.cjs.map +1 -0
  167. package/dist/{chunk-IJTLXNGJ.js → chunk-T4XNKYXB.js} +780 -127
  168. package/dist/chunk-T4XNKYXB.js.map +1 -0
  169. package/dist/{chunk-PDGEXGS6.cjs → chunk-TQR4KBY2.cjs} +354 -182
  170. package/dist/chunk-TQR4KBY2.cjs.map +1 -0
  171. package/dist/chunk-TTAITEPJ.cjs +755 -0
  172. package/dist/chunk-TTAITEPJ.cjs.map +1 -0
  173. package/dist/{chunk-NN3QSOVS.cjs → chunk-U5SON7R2.cjs} +27 -27
  174. package/dist/{chunk-NN3QSOVS.cjs.map → chunk-U5SON7R2.cjs.map} +1 -1
  175. package/dist/{chunk-A2I2XO52.cjs → chunk-U63GDNRC.cjs} +32 -26
  176. package/dist/chunk-U63GDNRC.cjs.map +1 -0
  177. package/dist/{chunk-YNULOIR5.cjs → chunk-UDISNXDL.cjs} +64 -43
  178. package/dist/chunk-UDISNXDL.cjs.map +1 -0
  179. package/dist/{chunk-GWEDD3DH.js → chunk-UV3SN74I.js} +5 -5
  180. package/dist/{chunk-GWEDD3DH.js.map → chunk-UV3SN74I.js.map} +1 -1
  181. package/dist/{chunk-U5W4LKZV.cjs → chunk-VCRRKBLN.cjs} +9 -9
  182. package/dist/{chunk-U5W4LKZV.cjs.map → chunk-VCRRKBLN.cjs.map} +1 -1
  183. package/dist/{chunk-EFELJVLA.js → chunk-VGTJPTIC.js} +28 -5
  184. package/dist/chunk-VGTJPTIC.js.map +1 -0
  185. package/dist/chunk-VQH4VAWD.cjs +12 -0
  186. package/dist/{chunk-EPTSMFH7.cjs.map → chunk-VQH4VAWD.cjs.map} +1 -1
  187. package/dist/{chunk-SNXVDZAJ.js → chunk-W6OG3HDG.js} +5 -4
  188. package/dist/chunk-W6OG3HDG.js.map +1 -0
  189. package/dist/chunk-WDGA7IYB.js +207 -0
  190. package/dist/chunk-WDGA7IYB.js.map +1 -0
  191. package/dist/chunk-WEVQJ5LH.js +736 -0
  192. package/dist/chunk-WEVQJ5LH.js.map +1 -0
  193. package/dist/{chunk-3PRKZW6D.cjs → chunk-XBYVV3MP.cjs} +60 -5
  194. package/dist/chunk-XBYVV3MP.cjs.map +1 -0
  195. package/dist/{chunk-GFRGG2US.cjs → chunk-XCAGZAKP.cjs} +7 -7
  196. package/dist/{chunk-GFRGG2US.cjs.map → chunk-XCAGZAKP.cjs.map} +1 -1
  197. package/dist/{chunk-XR4YZFKW.cjs → chunk-XDF2UPEQ.cjs} +3864 -1783
  198. package/dist/chunk-XDF2UPEQ.cjs.map +1 -0
  199. package/dist/{chunk-7V2FGGI2.cjs → chunk-XMQSEJCF.cjs} +632 -1080
  200. package/dist/chunk-XMQSEJCF.cjs.map +1 -0
  201. package/dist/chunk-XNLIZYRZ.cjs +3432 -0
  202. package/dist/chunk-XNLIZYRZ.cjs.map +1 -0
  203. package/dist/{chunk-66UVLHQA.cjs → chunk-XNSI2FJO.cjs} +99 -100
  204. package/dist/chunk-XNSI2FJO.cjs.map +1 -0
  205. package/dist/chunk-XQSCL77R.cjs +60 -0
  206. package/dist/chunk-XQSCL77R.cjs.map +1 -0
  207. package/dist/{chunk-SVJ5GEXW.cjs → chunk-XUO3KQFO.cjs} +2 -2
  208. package/dist/chunk-XUO3KQFO.cjs.map +1 -0
  209. package/dist/{chunk-HJH3RMXG.cjs → chunk-YAAM3M2S.cjs} +10 -10
  210. package/dist/{chunk-HJH3RMXG.cjs.map → chunk-YAAM3M2S.cjs.map} +1 -1
  211. package/dist/{chunk-ATLN63JA.js → chunk-YBFHZUXX.js} +6 -6
  212. package/dist/{chunk-ATLN63JA.js.map → chunk-YBFHZUXX.js.map} +1 -1
  213. package/dist/{chunk-K3BUWSFR.js → chunk-YDFDBE6D.js} +119 -6
  214. package/dist/chunk-YDFDBE6D.js.map +1 -0
  215. package/dist/{chunk-OSZPLZIV.cjs → chunk-YVWGEQJE.cjs} +318 -132
  216. package/dist/chunk-YVWGEQJE.cjs.map +1 -0
  217. package/dist/{chunk-QT6V3YRH.js → chunk-YWGVVALJ.js} +3 -3
  218. package/dist/{chunk-QT6V3YRH.js.map → chunk-YWGVVALJ.js.map} +1 -1
  219. package/dist/core/index.cjs +40 -41
  220. package/dist/core/index.d.cts +4 -5
  221. package/dist/core/index.d.ts +4 -5
  222. package/dist/core/index.js +8 -9
  223. package/dist/{ddl-eWg7hx6R.d.cts → ddl-BJcOp4WZ.d.cts} +3 -3
  224. package/dist/{ddl-D5369kOY.d.ts → ddl-BXfGHbPN.d.ts} +3 -3
  225. package/dist/graph-extension/index.cjs +26 -23
  226. package/dist/graph-extension/index.d.cts +16 -6
  227. package/dist/graph-extension/index.d.ts +16 -6
  228. package/dist/graph-extension/index.js +7 -8
  229. package/dist/graph-merge/index.cjs +2459 -655
  230. package/dist/graph-merge/index.cjs.map +1 -1
  231. package/dist/graph-merge/index.d.cts +170 -33
  232. package/dist/graph-merge/index.d.ts +170 -33
  233. package/dist/graph-merge/index.js +2269 -468
  234. package/dist/graph-merge/index.js.map +1 -1
  235. package/dist/{index-By89Sezh.d.ts → index-BSHvKfok.d.ts} +221 -10
  236. package/dist/{index-DcQ3Sxs-.d.cts → index-DxaYCv8Y.d.cts} +221 -10
  237. package/dist/index.cjs +330 -269
  238. package/dist/index.cjs.map +1 -1
  239. package/dist/index.d.cts +590 -576
  240. package/dist/index.d.ts +590 -576
  241. package/dist/index.js +73 -50
  242. package/dist/index.js.map +1 -1
  243. package/dist/indexes/index.cjs +17 -17
  244. package/dist/indexes/index.d.cts +4 -4
  245. package/dist/indexes/index.d.ts +4 -4
  246. package/dist/indexes/index.js +10 -10
  247. package/dist/interchange/index.cjs +165 -50
  248. package/dist/interchange/index.cjs.map +1 -1
  249. package/dist/interchange/index.d.cts +261 -16
  250. package/dist/interchange/index.d.ts +261 -16
  251. package/dist/interchange/index.js +128 -24
  252. package/dist/interchange/index.js.map +1 -1
  253. package/dist/{manager-B0KjKE93.d.ts → manager-CfZAimti.d.ts} +36 -5
  254. package/dist/{manager-Cn0NFIhN.d.cts → manager-HGk6E_HW.d.cts} +36 -5
  255. package/dist/{materialize-indexes-BD4V3svD.d.ts → materialize-indexes-6A7dWGtb.d.cts} +10 -2
  256. package/dist/{materialize-indexes-DVRhd5h4.d.cts → materialize-indexes-BMJP6Tms.d.ts} +10 -2
  257. package/dist/{postgres-DaEWvN4x.d.cts → postgres-DFYWENdR.d.cts} +1961 -439
  258. package/dist/{postgres-u0a0HN2D.d.ts → postgres-e7a9TdvR.d.ts} +1961 -439
  259. package/dist/profiler/index.cjs +15 -15
  260. package/dist/profiler/index.d.cts +9 -10
  261. package/dist/profiler/index.d.ts +9 -10
  262. package/dist/profiler/index.js +4 -4
  263. package/dist/provenance/index.cjs +46 -48
  264. package/dist/provenance/index.cjs.map +1 -1
  265. package/dist/provenance/index.d.cts +7 -7
  266. package/dist/provenance/index.d.ts +7 -7
  267. package/dist/provenance/index.js +12 -14
  268. package/dist/provenance/index.js.map +1 -1
  269. package/dist/schema/index.cjs +50 -49
  270. package/dist/schema/index.d.cts +9 -16
  271. package/dist/schema/index.d.ts +9 -16
  272. package/dist/schema/index.js +23 -22
  273. package/dist/{searchable-CnYEancP.d.ts → searchable-9OQoPuRs.d.ts} +1 -2
  274. package/dist/{searchable-BT7j9_IK.d.cts → searchable-CIje5XYD.d.cts} +1 -2
  275. package/dist/{sqlite-BrMoSsI7.d.cts → sqlite-DerU5tc7.d.ts} +2029 -347
  276. package/dist/{sqlite-CHhZUyO5.d.ts → sqlite-DitnISa9.d.cts} +2029 -347
  277. package/dist/{store-view-Bvdku0Ht.d.ts → store-DawaWWjJ.d.ts} +3145 -2556
  278. package/dist/{store-view-w-md0nQ4.d.cts → store-RwTJx8LX.d.cts} +3145 -2556
  279. package/dist/{temporal-BCORwUau.d.cts → temporal-Bn-Fi8FJ.d.cts} +1 -1
  280. package/dist/{temporal--x9T8K4S.d.ts → temporal-BtM4UALv.d.ts} +1 -1
  281. package/dist/{types-6-WJmSD4.d.cts → types-CoIx8HX5.d.cts} +1 -1
  282. package/dist/{types-Cjvkj0jA.d.ts → types-HkveCCx0.d.cts} +866 -21
  283. package/dist/{types-Cjvkj0jA.d.cts → types-HkveCCx0.d.ts} +866 -21
  284. package/dist/{types-COODtTJU.d.ts → types-KFfk-8Ah.d.ts} +1 -1
  285. package/dist/{where-CUQmCR6d.d.cts → where--fk7_0kv.d.cts} +1 -1
  286. package/dist/{where-5xVawAwL.d.ts → where-ws5hf-Ov.d.ts} +1 -1
  287. package/package.json +1 -1
  288. package/dist/chunk-2BIG7U6E.cjs +0 -83
  289. package/dist/chunk-2BIG7U6E.cjs.map +0 -1
  290. package/dist/chunk-2L54C2C2.js +0 -144
  291. package/dist/chunk-2L54C2C2.js.map +0 -1
  292. package/dist/chunk-2VLBGQBY.cjs.map +0 -1
  293. package/dist/chunk-2WDD2NO3.js +0 -59
  294. package/dist/chunk-2WDD2NO3.js.map +0 -1
  295. package/dist/chunk-2Y4IFKW7.js.map +0 -1
  296. package/dist/chunk-35S6P5ZU.js +0 -27
  297. package/dist/chunk-35S6P5ZU.js.map +0 -1
  298. package/dist/chunk-3PRKZW6D.cjs.map +0 -1
  299. package/dist/chunk-4MB3AQAQ.js.map +0 -1
  300. package/dist/chunk-4SZ6XPTG.cjs +0 -12
  301. package/dist/chunk-5MUSU4ZP.cjs.map +0 -1
  302. package/dist/chunk-63KCKSSH.js.map +0 -1
  303. package/dist/chunk-66UVLHQA.cjs.map +0 -1
  304. package/dist/chunk-6JZUSDY2.cjs.map +0 -1
  305. package/dist/chunk-764JHTLT.cjs +0 -15
  306. package/dist/chunk-764JHTLT.cjs.map +0 -1
  307. package/dist/chunk-7G32LIQM.js.map +0 -1
  308. package/dist/chunk-7V2FGGI2.cjs.map +0 -1
  309. package/dist/chunk-A2I2XO52.cjs.map +0 -1
  310. package/dist/chunk-A4PBRX4C.cjs +0 -100
  311. package/dist/chunk-A4PBRX4C.cjs.map +0 -1
  312. package/dist/chunk-ACSVZPK7.cjs.map +0 -1
  313. package/dist/chunk-ALFUGRYB.cjs +0 -1282
  314. package/dist/chunk-ALFUGRYB.cjs.map +0 -1
  315. package/dist/chunk-B75XRV3O.js +0 -125
  316. package/dist/chunk-B75XRV3O.js.map +0 -1
  317. package/dist/chunk-BCP7LLOY.js.map +0 -1
  318. package/dist/chunk-BEFM74L2.js.map +0 -1
  319. package/dist/chunk-BNUFUFAU.js.map +0 -1
  320. package/dist/chunk-C4F7VJ5D.cjs.map +0 -1
  321. package/dist/chunk-CPEL3YIR.cjs.map +0 -1
  322. package/dist/chunk-D7PG5D5K.js.map +0 -1
  323. package/dist/chunk-DI74SMXS.cjs +0 -29
  324. package/dist/chunk-DI74SMXS.cjs.map +0 -1
  325. package/dist/chunk-DK2S42XB.js +0 -77
  326. package/dist/chunk-DK2S42XB.js.map +0 -1
  327. package/dist/chunk-DRACN6XG.js +0 -27
  328. package/dist/chunk-DRACN6XG.js.map +0 -1
  329. package/dist/chunk-EE7N5XXQ.cjs.map +0 -1
  330. package/dist/chunk-EFELJVLA.js.map +0 -1
  331. package/dist/chunk-EPTSMFH7.cjs +0 -12
  332. package/dist/chunk-EQ7JKLZO.cjs +0 -157
  333. package/dist/chunk-EQ7JKLZO.cjs.map +0 -1
  334. package/dist/chunk-FJMPR3OH.cjs.map +0 -1
  335. package/dist/chunk-FMGEZBAQ.js.map +0 -1
  336. package/dist/chunk-FMMIX6N4.js.map +0 -1
  337. package/dist/chunk-FRHHDHKH.js.map +0 -1
  338. package/dist/chunk-FSXTPBRN.js.map +0 -1
  339. package/dist/chunk-G6Q5UM7U.js.map +0 -1
  340. package/dist/chunk-GYA4JRP5.js.map +0 -1
  341. package/dist/chunk-HOHAJ2NR.cjs +0 -61
  342. package/dist/chunk-HOHAJ2NR.cjs.map +0 -1
  343. package/dist/chunk-HOIFFY4M.js.map +0 -1
  344. package/dist/chunk-IIMQJYAM.js +0 -81
  345. package/dist/chunk-IIMQJYAM.js.map +0 -1
  346. package/dist/chunk-IJTLXNGJ.js.map +0 -1
  347. package/dist/chunk-ITE4IXAK.js +0 -25
  348. package/dist/chunk-ITE4IXAK.js.map +0 -1
  349. package/dist/chunk-K3BUWSFR.js.map +0 -1
  350. package/dist/chunk-KCO5EFJU.js +0 -79
  351. package/dist/chunk-KCO5EFJU.js.map +0 -1
  352. package/dist/chunk-KKYELPZ2.cjs +0 -83
  353. package/dist/chunk-KKYELPZ2.cjs.map +0 -1
  354. package/dist/chunk-MCHP7RHZ.cjs.map +0 -1
  355. package/dist/chunk-MIJKEVV7.js.map +0 -1
  356. package/dist/chunk-NCX65HBY.cjs +0 -1396
  357. package/dist/chunk-NCX65HBY.cjs.map +0 -1
  358. package/dist/chunk-OSZPLZIV.cjs.map +0 -1
  359. package/dist/chunk-OVUUTCPJ.cjs.map +0 -1
  360. package/dist/chunk-PDGEXGS6.cjs.map +0 -1
  361. package/dist/chunk-QB6VMOJ2.cjs.map +0 -1
  362. package/dist/chunk-QMQLUA66.js +0 -12
  363. package/dist/chunk-QMQLUA66.js.map +0 -1
  364. package/dist/chunk-QYWINYEA.cjs.map +0 -1
  365. package/dist/chunk-S4KQ5LV4.js.map +0 -1
  366. package/dist/chunk-SBPC5IP3.js.map +0 -1
  367. package/dist/chunk-SJDMVITW.cjs.map +0 -1
  368. package/dist/chunk-SNXVDZAJ.js.map +0 -1
  369. package/dist/chunk-SQRD2CYF.js.map +0 -1
  370. package/dist/chunk-ST3LPB2V.cjs.map +0 -1
  371. package/dist/chunk-STQ3BSED.cjs.map +0 -1
  372. package/dist/chunk-SVJ5GEXW.cjs.map +0 -1
  373. package/dist/chunk-TCRIQMBU.js.map +0 -1
  374. package/dist/chunk-TDXIBCGY.cjs.map +0 -1
  375. package/dist/chunk-TFMVL4QH.js.map +0 -1
  376. package/dist/chunk-TRF4EBJT.cjs +0 -30
  377. package/dist/chunk-TRF4EBJT.cjs.map +0 -1
  378. package/dist/chunk-TVLKA4AY.js +0 -94
  379. package/dist/chunk-TVLKA4AY.js.map +0 -1
  380. package/dist/chunk-UR7W5YQK.js.map +0 -1
  381. package/dist/chunk-VITTKBWF.js +0 -1263
  382. package/dist/chunk-VITTKBWF.js.map +0 -1
  383. package/dist/chunk-WBUFJ7BS.cjs +0 -131
  384. package/dist/chunk-WBUFJ7BS.cjs.map +0 -1
  385. package/dist/chunk-XR4YZFKW.cjs.map +0 -1
  386. package/dist/chunk-YNULOIR5.cjs.map +0 -1
  387. package/dist/chunk-ZDH3DX3C.cjs +0 -30
  388. package/dist/chunk-ZDH3DX3C.cjs.map +0 -1
  389. package/dist/chunk-ZTAJL4DT.cjs +0 -85
  390. package/dist/chunk-ZTAJL4DT.cjs.map +0 -1
  391. package/dist/define-graph-DVF-oN1i.d.cts +0 -173
  392. package/dist/define-graph-SvYXOl78.d.ts +0 -173
@@ -1,39 +1,36 @@
1
- import { createStoreWithSchema } from '../chunk-BCP7LLOY.js';
2
- import '../chunk-PHXITZE4.js';
3
- import { importGraphStream, exportGraphStream } from '../chunk-VITTKBWF.js';
4
- import '../chunk-SBPC5IP3.js';
5
- import { storeBackend, generateId, computeUniqueKey, transactionBackend, lockRecordedGraphWrite, readRevisionOrigin, readRecordedClock } from '../chunk-FSXTPBRN.js';
6
- import '../chunk-35S6P5ZU.js';
7
- import '../chunk-MIJKEVV7.js';
8
- import '../chunk-FMMIX6N4.js';
9
- import '../chunk-2L54C2C2.js';
10
- import { computeSchemaHash, serializeSchema } from '../chunk-BEFM74L2.js';
11
- import { compareStrings } from '../chunk-DRACN6XG.js';
12
- import { defineNode } from '../chunk-2Y4IFKW7.js';
13
- import '../chunk-DK2S42XB.js';
14
- import { sortedReplacer } from '../chunk-UR7W5YQK.js';
15
- import '../chunk-B75XRV3O.js';
16
- import { getNodeKinds, getEdgeKinds, defineGraph } from '../chunk-S4KQ5LV4.js';
17
- import '../chunk-KCO5EFJU.js';
18
- import '../chunk-46GDII45.js';
19
- import '../chunk-JRP3VGHS.js';
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-K3BUWSFR.js';
24
- import '../chunk-6DCB664D.js';
25
- import '../chunk-D7PG5D5K.js';
26
- import '../chunk-SNXVDZAJ.js';
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-IJTLXNGJ.js';
30
- export { isErr, isOk, unwrap } from '../chunk-IJTLXNGJ.js';
31
- import '../chunk-GYA4JRP5.js';
32
- import '../chunk-2WDD2NO3.js';
33
- import '../chunk-ITE4IXAK.js';
34
- import '../chunk-TVLKA4AY.js';
35
- import { TypeGraphError } from '../chunk-4MB3AQAQ.js';
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({ id: baseRow.id, kind });
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
- computeContentComponent(storeBackend(store), store.graphId, store.graph)
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 result = await importGraphStream(
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
- includeMeta: true,
557
- includeTemporal: true,
558
- includeDeleted: false,
559
- batchSize: CLONE_IMPORT_BATCH_SIZE
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
- return ok({ id, base, store });
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[field];
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 in props)) {
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 && property in member.props
1765
+ (member) => member.branchId === preferredBranchId && hasOwnKey(member.props, property)
844
1766
  );
845
1767
  }
846
1768
  function memberPropertyValue(member, property) {
847
- if (!(property in member.props)) {
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" && property in member.props
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
- return { canonicalId, kind, props, resolution, conflicts };
958
- }
959
-
960
- // src/graph-merge/union-find.ts
961
- var UnionFind = class {
962
- constructor(compare) {
963
- this.compare = compare;
964
- }
965
- compare;
966
- parent = /* @__PURE__ */ new Map();
967
- /** Adds `value` as its own singleton set if it is not already present. */
968
- add(value) {
969
- if (!this.parent.has(value)) {
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 EQUIVALENT_TO_META_EDGE = "equivalentTo";
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 === EQUIVALENT_TO_META_EDGE) {
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[field];
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, droppedKind, policy, branchRank, preferredBranchId) {
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({ kind: droppedKind, id, reason: DELETED_NODE_DROP_REASON });
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 in baseProps;
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 (!(property in contribution.forkProps)) {
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) => !(property in contribution.forkProps)
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
- let survivor = requireDefined(candidates[0]);
1890
- for (const candidate of candidates.slice(1)) {
1891
- if (candidate.staged.id < survivor.staged.id) {
1892
- survivor = candidate;
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 survivor;
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 values = collectConflictingValues(property, contributions);
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 = property in survivor.staged.props ? survivor.staged.props[property] : requireDefined(values[0]).value;
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 liveByDedupeKey = /* @__PURE__ */ new Map();
1939
- const dedupeKeyByGroup = /* @__PURE__ */ new Map();
3128
+ const liveByGroup = /* @__PURE__ */ new Map();
1940
3129
  for (const staged of stagedEdges) {
1941
- const fromKey = repoint(
1942
- mergeKey(staged.fromKind, staged.fromId),
1943
- canonicalOf
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 = { staged, fromKey, toKey };
1955
- const key = dedupeKey(fromKey, staged.kind, toKey, staged.props);
1956
- const bucket = liveByDedupeKey.get(key);
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
- liveByDedupeKey.set(key, [repointed]);
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 = [...dedupeKeyByGroup.keys()].sort(
3163
+ const sortedGroups = [...liveByGroup.keys()].sort(
1977
3164
  (left, right) => compareStrings(left, right)
1978
3165
  );
1979
3166
  for (const group of sortedGroups) {
1980
- const dedupeKeys = [...requireDefined(dedupeKeyByGroup.get(group))];
1981
- const groupEdges = [];
1982
- for (const key of dedupeKeys) {
1983
- for (const edge of requireDefined(liveByDedupeKey.get(key))) {
1984
- groupEdges.push(edge);
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: dedupeKey(
2033
- mergeKey(edge.fromKind, edge.fromId),
2034
- edge.kind,
2035
- mergeKey(edge.toKind, edge.toId),
2036
- edge.props
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 Provenance = defineNode("Provenance", {
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 defineGraph({
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 [store] = await createStoreWithSchema(
2195
- buildProvenanceGraph(targetGraphId),
2196
- backend
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
- ].join(ID_SEPARATOR);
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 items = await Promise.all(
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 lookup;
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
- const orderedTexts = [...missing].sort(
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
- branchId: item.branchId
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
- pushProvenance(
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 stagedEdges = buildStagedEdges(
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 branches = stagedEdgeBranches.get(staged.id);
3231
- if (branches === void 0) {
3232
- stagedEdgeBranches.set(staged.id, /* @__PURE__ */ new Set([staged.branchId]));
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 = { ...forkProps };
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 in forkProps)) {
4937
+ if (!hasOwnKey(forkProps, key)) {
3338
4938
  props[key] = void 0;
3339
4939
  }
3340
4940
  }
3341
4941
  return props;
3342
4942
  }
3343
- async function applyMergePlan(plan, nodesApi, edgesApi) {
3344
- const committedNodeIds = /* @__PURE__ */ new Set();
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 survivingModProps = /* @__PURE__ */ new Map();
4951
+ const modificationsByIdentity = /* @__PURE__ */ new Map();
3350
4952
  for (const modification of plan.survivingModifications) {
3351
- survivingModProps.set(mergeKeyOf(modification.node), {
3352
- baseProps: modification.node.baseProps,
3353
- forkProps: modification.node.forkProps
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
- const collection = nodeCollection(nodesApi, modification.node.kind);
3362
- await collection.upsertByIdFromRecord(
3363
- modification.node.id,
3364
- commitModificationProps(
3365
- modification.node.baseProps,
3366
- modification.node.forkProps
3367
- )
3368
- );
3369
- committedNodeIds.add(identity);
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 identity = mergeKey(entity.kind, entity.canonicalId);
3373
- if (plan.nodeDeletions.has(identity)) {
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(identity) ?? entity.kind;
3377
- const modification = survivingModProps.get(identity);
3378
- const props = modification === void 0 ? entity.props : {
3379
- ...entity.props,
3380
- ...commitModificationProps(
3381
- modification.baseProps,
3382
- modification.forkProps
3383
- )
3384
- };
3385
- const collection = nodeCollection(nodesApi, kind);
3386
- await collection.upsertByIdFromRecord(entity.canonicalId, props);
3387
- committedNodeIds.add(mergeKey(kind, entity.canonicalId));
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: { kind: fromKind ?? edge.fromKind, id: edge.fromId },
3405
- to: { kind: toKind ?? edge.toKind, id: edge.toId },
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
- return { nodes: committedNodeIds.size, edges: committedEdges };
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(new MergeError("Invalid merge options.", { cause: error }));
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
- newNodesByKind(staging),
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: newNodesByKind(staging),
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 (options.persistProvenance) {
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 = finalEdgeFrom(edge, plan);
3836
- const to = finalEdgeTo(edge, plan);
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.props
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 = finalEdgeFrom(edge, plan);
3980
- const to = finalEdgeTo(edge, plan);
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(plan, nodesApi, edgesApi);
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 MergeError(
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
- { details: {} }
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
- { targetBranchId: COMMITTED_TARGET_BRANCH }
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