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