@nicia-ai/typegraph 0.46.2 → 0.47.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 (294) hide show
  1. package/README.md +20 -1
  2. package/dist/backend/drizzle/indexes.cjs +9 -9
  3. package/dist/backend/drizzle/indexes.d.cts +1 -1
  4. package/dist/backend/drizzle/indexes.d.ts +1 -1
  5. package/dist/backend/drizzle/indexes.js +5 -5
  6. package/dist/backend/index.cjs +50 -49
  7. package/dist/backend/index.d.cts +3 -3
  8. package/dist/backend/index.d.ts +3 -3
  9. package/dist/backend/index.js +11 -10
  10. package/dist/backend/postgres/index.cjs +27 -26
  11. package/dist/backend/postgres/index.d.cts +6 -6
  12. package/dist/backend/postgres/index.d.ts +6 -6
  13. package/dist/backend/postgres/index.js +14 -13
  14. package/dist/backend/postgres/pglite-store.cjs +34 -33
  15. package/dist/backend/postgres/pglite-store.cjs.map +1 -1
  16. package/dist/backend/postgres/pglite-store.d.cts +8 -8
  17. package/dist/backend/postgres/pglite-store.d.ts +8 -8
  18. package/dist/backend/postgres/pglite-store.js +30 -29
  19. package/dist/backend/postgres/pglite-store.js.map +1 -1
  20. package/dist/backend/postgres/pglite.cjs +16 -15
  21. package/dist/backend/postgres/pglite.d.cts +3 -3
  22. package/dist/backend/postgres/pglite.d.ts +3 -3
  23. package/dist/backend/postgres/pglite.js +15 -14
  24. package/dist/backend/sqlite/index.cjs +24 -23
  25. package/dist/backend/sqlite/index.d.cts +6 -6
  26. package/dist/backend/sqlite/index.d.ts +6 -6
  27. package/dist/backend/sqlite/index.js +12 -11
  28. package/dist/backend/sqlite/libsql.cjs +19 -18
  29. package/dist/backend/sqlite/libsql.cjs.map +1 -1
  30. package/dist/backend/sqlite/libsql.d.cts +3 -3
  31. package/dist/backend/sqlite/libsql.d.ts +3 -3
  32. package/dist/backend/sqlite/libsql.js +14 -13
  33. package/dist/backend/sqlite/libsql.js.map +1 -1
  34. package/dist/backend/sqlite/local-store.cjs +35 -34
  35. package/dist/backend/sqlite/local-store.cjs.map +1 -1
  36. package/dist/backend/sqlite/local-store.d.cts +8 -8
  37. package/dist/backend/sqlite/local-store.d.ts +8 -8
  38. package/dist/backend/sqlite/local-store.js +31 -30
  39. package/dist/backend/sqlite/local-store.js.map +1 -1
  40. package/dist/backend/sqlite/local.cjs +17 -16
  41. package/dist/backend/sqlite/local.d.cts +3 -3
  42. package/dist/backend/sqlite/local.d.ts +3 -3
  43. package/dist/backend/sqlite/local.js +15 -14
  44. package/dist/{chunk-TCT66A7N.cjs → chunk-26YKDNUD.cjs} +10 -10
  45. package/dist/{chunk-TCT66A7N.cjs.map → chunk-26YKDNUD.cjs.map} +1 -1
  46. package/dist/{chunk-QOJ2L2AD.js → chunk-3DMMGT6S.js} +3 -3
  47. package/dist/{chunk-QOJ2L2AD.js.map → chunk-3DMMGT6S.js.map} +1 -1
  48. package/dist/{chunk-ANKOXPCC.cjs → chunk-3HGRTCDU.cjs} +6 -4
  49. package/dist/chunk-3HGRTCDU.cjs.map +1 -0
  50. package/dist/{chunk-QU7QLJA4.cjs → chunk-3U5ABFDW.cjs} +26 -26
  51. package/dist/{chunk-QU7QLJA4.cjs.map → chunk-3U5ABFDW.cjs.map} +1 -1
  52. package/dist/{chunk-5PDSCELM.cjs → chunk-425IZOQ5.cjs} +11 -164
  53. package/dist/chunk-425IZOQ5.cjs.map +1 -0
  54. package/dist/{chunk-S7WIRFNS.cjs → chunk-5FBRVORR.cjs} +12 -12
  55. package/dist/{chunk-S7WIRFNS.cjs.map → chunk-5FBRVORR.cjs.map} +1 -1
  56. package/dist/{chunk-GWXXHOLU.js → chunk-5FT3TLPK.js} +11 -11
  57. package/dist/{chunk-GWXXHOLU.js.map → chunk-5FT3TLPK.js.map} +1 -1
  58. package/dist/{chunk-S3G5VLSW.js → chunk-5UMTUOM5.js} +6 -6
  59. package/dist/{chunk-S3G5VLSW.js.map → chunk-5UMTUOM5.js.map} +1 -1
  60. package/dist/{chunk-RFDEYLIK.js → chunk-6FWSNSRE.js} +7 -7
  61. package/dist/{chunk-RFDEYLIK.js.map → chunk-6FWSNSRE.js.map} +1 -1
  62. package/dist/{chunk-F36KZDFX.cjs → chunk-6JVZSOHQ.cjs} +91 -91
  63. package/dist/{chunk-F36KZDFX.cjs.map → chunk-6JVZSOHQ.cjs.map} +1 -1
  64. package/dist/chunk-6KFLYYOC.cjs +161 -0
  65. package/dist/chunk-6KFLYYOC.cjs.map +1 -0
  66. package/dist/{chunk-D6GCNNMK.cjs → chunk-6NGW27EO.cjs} +10 -10
  67. package/dist/{chunk-D6GCNNMK.cjs.map → chunk-6NGW27EO.cjs.map} +1 -1
  68. package/dist/{chunk-WHS2Y43J.js → chunk-6SWPJEYN.js} +5 -5
  69. package/dist/{chunk-WHS2Y43J.js.map → chunk-6SWPJEYN.js.map} +1 -1
  70. package/dist/{chunk-7TKNK32B.js → chunk-7SQSZN2J.js} +5 -5
  71. package/dist/{chunk-7TKNK32B.js.map → chunk-7SQSZN2J.js.map} +1 -1
  72. package/dist/{chunk-BEB4OFZF.cjs → chunk-AMMWA76F.cjs} +51 -59
  73. package/dist/chunk-AMMWA76F.cjs.map +1 -0
  74. package/dist/{chunk-PYKF7UIL.cjs → chunk-AOSSQDVO.cjs} +13 -13
  75. package/dist/{chunk-PYKF7UIL.cjs.map → chunk-AOSSQDVO.cjs.map} +1 -1
  76. package/dist/{chunk-EDHV4CBU.js → chunk-AVGUJWVX.js} +30 -10
  77. package/dist/chunk-AVGUJWVX.js.map +1 -0
  78. package/dist/{chunk-C2ZP5YS6.cjs → chunk-BCQ76AXA.cjs} +258 -153
  79. package/dist/chunk-BCQ76AXA.cjs.map +1 -0
  80. package/dist/{chunk-WA64ZSCA.cjs → chunk-BFUOME67.cjs} +11 -11
  81. package/dist/{chunk-WA64ZSCA.cjs.map → chunk-BFUOME67.cjs.map} +1 -1
  82. package/dist/{chunk-C6KE2HSQ.cjs → chunk-BR7SHKGM.cjs} +1611 -1382
  83. package/dist/chunk-BR7SHKGM.cjs.map +1 -0
  84. package/dist/{chunk-FCIZNUW5.js → chunk-CHAOFCHV.js} +3 -3
  85. package/dist/{chunk-FCIZNUW5.js.map → chunk-CHAOFCHV.js.map} +1 -1
  86. package/dist/chunk-CICO5GXD.cjs +12 -0
  87. package/dist/{chunk-73MWKDFK.cjs.map → chunk-CICO5GXD.cjs.map} +1 -1
  88. package/dist/{chunk-SXWLLQ3D.js → chunk-D5R42W5D.js} +4 -4
  89. package/dist/{chunk-SXWLLQ3D.js.map → chunk-D5R42W5D.js.map} +1 -1
  90. package/dist/{chunk-YQ4JONWE.js → chunk-DJZTXBWY.js} +342 -113
  91. package/dist/chunk-DJZTXBWY.js.map +1 -0
  92. package/dist/{chunk-BZZI7B43.cjs → chunk-DO7SAMEK.cjs} +47 -47
  93. package/dist/{chunk-BZZI7B43.cjs.map → chunk-DO7SAMEK.cjs.map} +1 -1
  94. package/dist/{chunk-2ZWM2RFH.cjs → chunk-EDN4Y4ZG.cjs} +9 -9
  95. package/dist/{chunk-2ZWM2RFH.cjs.map → chunk-EDN4Y4ZG.cjs.map} +1 -1
  96. package/dist/{chunk-E4OSGBCM.cjs → chunk-EMIB2RXD.cjs} +131 -131
  97. package/dist/{chunk-E4OSGBCM.cjs.map → chunk-EMIB2RXD.cjs.map} +1 -1
  98. package/dist/{chunk-GWUWRZXW.js → chunk-EP4RUYD6.js} +3 -3
  99. package/dist/{chunk-GWUWRZXW.js.map → chunk-EP4RUYD6.js.map} +1 -1
  100. package/dist/{chunk-LHYSY6SE.js → chunk-FNOMT74T.js} +10 -8
  101. package/dist/chunk-FNOMT74T.js.map +1 -0
  102. package/dist/{chunk-ZBVZ7HXD.cjs → chunk-FQPEMEWX.cjs} +12 -12
  103. package/dist/{chunk-ZBVZ7HXD.cjs.map → chunk-FQPEMEWX.cjs.map} +1 -1
  104. package/dist/{chunk-LNIWCQR4.js → chunk-GLHXM7C4.js} +3 -3
  105. package/dist/{chunk-LNIWCQR4.js.map → chunk-GLHXM7C4.js.map} +1 -1
  106. package/dist/{chunk-6BD6ZBKT.cjs → chunk-GYEG3ZLZ.cjs} +10 -10
  107. package/dist/{chunk-6BD6ZBKT.cjs.map → chunk-GYEG3ZLZ.cjs.map} +1 -1
  108. package/dist/{chunk-XBHRKLZU.cjs → chunk-GYUJZARH.cjs} +4 -4
  109. package/dist/{chunk-XBHRKLZU.cjs.map → chunk-GYUJZARH.cjs.map} +1 -1
  110. package/dist/{chunk-A7NMUYEL.cjs → chunk-HDL2ARHX.cjs} +27 -27
  111. package/dist/{chunk-A7NMUYEL.cjs.map → chunk-HDL2ARHX.cjs.map} +1 -1
  112. package/dist/{chunk-5ZJOB5YS.js → chunk-HEV35TGT.js} +4 -4
  113. package/dist/{chunk-5ZJOB5YS.js.map → chunk-HEV35TGT.js.map} +1 -1
  114. package/dist/{chunk-UTVQPZPN.cjs → chunk-I4G3SQPF.cjs} +20 -20
  115. package/dist/{chunk-UTVQPZPN.cjs.map → chunk-I4G3SQPF.cjs.map} +1 -1
  116. package/dist/{chunk-JCNTKRAK.js → chunk-JONXFAPO.js} +5 -5
  117. package/dist/{chunk-JCNTKRAK.js.map → chunk-JONXFAPO.js.map} +1 -1
  118. package/dist/{chunk-GVAMRRZR.cjs → chunk-JR6SX5RH.cjs} +4 -4
  119. package/dist/{chunk-GVAMRRZR.cjs.map → chunk-JR6SX5RH.cjs.map} +1 -1
  120. package/dist/{chunk-N3QTGPC7.cjs → chunk-KABFHAL7.cjs} +100 -99
  121. package/dist/chunk-KABFHAL7.cjs.map +1 -0
  122. package/dist/{chunk-KXSMF6PL.js → chunk-KH7XW7VV.js} +20 -12
  123. package/dist/chunk-KH7XW7VV.js.map +1 -0
  124. package/dist/{chunk-EDJDDY6H.js → chunk-KKBDXCLQ.js} +4 -4
  125. package/dist/{chunk-EDJDDY6H.js.map → chunk-KKBDXCLQ.js.map} +1 -1
  126. package/dist/{chunk-ZHOAZD7Q.cjs → chunk-L3LQNR34.cjs} +161 -161
  127. package/dist/{chunk-ZHOAZD7Q.cjs.map → chunk-L3LQNR34.cjs.map} +1 -1
  128. package/dist/{chunk-IHRDX7K6.js → chunk-LEWHMOUY.js} +46 -56
  129. package/dist/chunk-LEWHMOUY.js.map +1 -0
  130. package/dist/{chunk-AF3TWL5C.cjs → chunk-LRQKXRXO.cjs} +4 -4
  131. package/dist/{chunk-AF3TWL5C.cjs.map → chunk-LRQKXRXO.cjs.map} +1 -1
  132. package/dist/{chunk-WYFB7BN3.js → chunk-M35DF33T.js} +143 -38
  133. package/dist/chunk-M35DF33T.js.map +1 -0
  134. package/dist/{chunk-32UETY6S.cjs → chunk-MFZR4QKM.cjs} +30 -28
  135. package/dist/chunk-MFZR4QKM.cjs.map +1 -0
  136. package/dist/chunk-MKJ4KXL5.cjs +12 -0
  137. package/dist/{chunk-6LAXM4J2.cjs.map → chunk-MKJ4KXL5.cjs.map} +1 -1
  138. package/dist/{chunk-KCH7WQA5.cjs → chunk-MRDBBHIZ.cjs} +48 -2
  139. package/dist/chunk-MRDBBHIZ.cjs.map +1 -0
  140. package/dist/{chunk-QPOQX2DI.cjs → chunk-MX4DHSQP.cjs} +1023 -360
  141. package/dist/chunk-MX4DHSQP.cjs.map +1 -0
  142. package/dist/{chunk-FXNHZFZV.js → chunk-MYKHJI2J.js} +3 -3
  143. package/dist/{chunk-FXNHZFZV.js.map → chunk-MYKHJI2J.js.map} +1 -1
  144. package/dist/{chunk-DSAWGCMP.js → chunk-NAZ2RA4L.js} +3 -3
  145. package/dist/{chunk-DSAWGCMP.js.map → chunk-NAZ2RA4L.js.map} +1 -1
  146. package/dist/{chunk-R7EWHKID.js → chunk-NG2RB5WL.js} +4 -145
  147. package/dist/chunk-NG2RB5WL.js.map +1 -0
  148. package/dist/{chunk-A2LJG5GM.js → chunk-NNQ6A3RM.js} +803 -143
  149. package/dist/chunk-NNQ6A3RM.js.map +1 -0
  150. package/dist/{chunk-72WUT4GG.js → chunk-NVY5CJFP.js} +4 -4
  151. package/dist/{chunk-72WUT4GG.js.map → chunk-NVY5CJFP.js.map} +1 -1
  152. package/dist/{chunk-ZIH42CRX.cjs → chunk-OI2GQ25X.cjs} +7 -7
  153. package/dist/{chunk-ZIH42CRX.cjs.map → chunk-OI2GQ25X.cjs.map} +1 -1
  154. package/dist/{chunk-A4R3H7RU.cjs → chunk-OIFSOPSR.cjs} +259 -238
  155. package/dist/chunk-OIFSOPSR.cjs.map +1 -0
  156. package/dist/{chunk-AU3IJNNP.js → chunk-OMZ37BM2.js} +5 -5
  157. package/dist/{chunk-AU3IJNNP.js.map → chunk-OMZ37BM2.js.map} +1 -1
  158. package/dist/{chunk-C4DYN2SD.cjs → chunk-OORXGFL5.cjs} +48 -48
  159. package/dist/{chunk-C4DYN2SD.cjs.map → chunk-OORXGFL5.cjs.map} +1 -1
  160. package/dist/{chunk-BRN3RGD2.js → chunk-OVI7IXYX.js} +3 -3
  161. package/dist/{chunk-BRN3RGD2.js.map → chunk-OVI7IXYX.js.map} +1 -1
  162. package/dist/{chunk-WMKEGJG4.js → chunk-P6YLZTRE.js} +3 -3
  163. package/dist/{chunk-WMKEGJG4.js.map → chunk-P6YLZTRE.js.map} +1 -1
  164. package/dist/{chunk-AT3MLMJV.js → chunk-QS3NQZVC.js} +82 -16
  165. package/dist/chunk-QS3NQZVC.js.map +1 -0
  166. package/dist/{chunk-EJSYWAEV.js → chunk-QTD6HX4O.js} +5 -5
  167. package/dist/{chunk-EJSYWAEV.js.map → chunk-QTD6HX4O.js.map} +1 -1
  168. package/dist/{chunk-JL5UEBWT.cjs → chunk-ROR5ZR6A.cjs} +45 -45
  169. package/dist/{chunk-JL5UEBWT.cjs.map → chunk-ROR5ZR6A.cjs.map} +1 -1
  170. package/dist/{chunk-FSLTU57P.js → chunk-SDBBMD4N.js} +3 -3
  171. package/dist/{chunk-FSLTU57P.js.map → chunk-SDBBMD4N.js.map} +1 -1
  172. package/dist/{chunk-WEJHA7AD.js → chunk-SE6EGQSD.js} +78 -16
  173. package/dist/chunk-SE6EGQSD.js.map +1 -0
  174. package/dist/{chunk-36LIJXWC.js → chunk-SHFBB23Q.js} +4 -4
  175. package/dist/{chunk-36LIJXWC.js.map → chunk-SHFBB23Q.js.map} +1 -1
  176. package/dist/{chunk-FA6Z7B5L.js → chunk-SHSSRCFR.js} +5 -3
  177. package/dist/chunk-SHSSRCFR.js.map +1 -0
  178. package/dist/{chunk-KOAI3KIV.cjs → chunk-TZEGUG6D.cjs} +4 -4
  179. package/dist/{chunk-KOAI3KIV.cjs.map → chunk-TZEGUG6D.cjs.map} +1 -1
  180. package/dist/{chunk-HNYLMPQQ.cjs → chunk-U5EDAVOJ.cjs} +140 -74
  181. package/dist/chunk-U5EDAVOJ.cjs.map +1 -0
  182. package/dist/{chunk-RHDOAQCT.cjs → chunk-UPAJHMW3.cjs} +122 -114
  183. package/dist/chunk-UPAJHMW3.cjs.map +1 -0
  184. package/dist/{chunk-BQM5P2NO.cjs → chunk-WGX7JH7E.cjs} +235 -170
  185. package/dist/chunk-WGX7JH7E.cjs.map +1 -0
  186. package/dist/{chunk-DYPRZ53S.cjs → chunk-WODMN3Q2.cjs} +27 -27
  187. package/dist/{chunk-DYPRZ53S.cjs.map → chunk-WODMN3Q2.cjs.map} +1 -1
  188. package/dist/{chunk-OZJCH7ZS.js → chunk-XVWOYKIK.js} +3 -3
  189. package/dist/{chunk-OZJCH7ZS.js.map → chunk-XVWOYKIK.js.map} +1 -1
  190. package/dist/chunk-XY5OMOJD.js +147 -0
  191. package/dist/chunk-XY5OMOJD.js.map +1 -0
  192. package/dist/{chunk-KA4L2NZ3.js → chunk-XYBQI6LE.js} +14 -14
  193. package/dist/{chunk-KA4L2NZ3.js.map → chunk-XYBQI6LE.js.map} +1 -1
  194. package/dist/{chunk-EAMVXR5B.js → chunk-ZDKTPBGV.js} +4 -4
  195. package/dist/{chunk-EAMVXR5B.js.map → chunk-ZDKTPBGV.js.map} +1 -1
  196. package/dist/{chunk-Z43KWLZF.js → chunk-ZHNXLS3C.js} +9 -8
  197. package/dist/chunk-ZHNXLS3C.js.map +1 -0
  198. package/dist/{chunk-62ZXBMSD.js → chunk-ZLONH2UD.js} +46 -3
  199. package/dist/chunk-ZLONH2UD.js.map +1 -0
  200. package/dist/core/index.cjs +28 -27
  201. package/dist/core/index.d.cts +4 -4
  202. package/dist/core/index.d.ts +4 -4
  203. package/dist/core/index.js +8 -7
  204. package/dist/{ddl-opIn8DfX.d.ts → ddl-CprnMXJ-.d.ts} +3 -3
  205. package/dist/{ddl-mE07h_EM.d.cts → ddl-DJA7Lci5.d.cts} +3 -3
  206. package/dist/graph-extension/index.cjs +20 -20
  207. package/dist/graph-extension/index.d.cts +4 -4
  208. package/dist/graph-extension/index.d.ts +4 -4
  209. package/dist/graph-extension/index.js +4 -4
  210. package/dist/graph-merge/index.cjs +3204 -635
  211. package/dist/graph-merge/index.cjs.map +1 -1
  212. package/dist/graph-merge/index.d.cts +502 -60
  213. package/dist/graph-merge/index.d.ts +502 -60
  214. package/dist/graph-merge/index.js +2979 -427
  215. package/dist/graph-merge/index.js.map +1 -1
  216. package/dist/{index-Deyu8IXL.d.ts → index-CHTv4XMZ.d.cts} +47 -3
  217. package/dist/{index-DrRS51Ld.d.cts → index-CzoJ3MUw.d.ts} +47 -3
  218. package/dist/index.cjs +229 -216
  219. package/dist/index.cjs.map +1 -1
  220. package/dist/index.d.cts +11 -11
  221. package/dist/index.d.ts +11 -11
  222. package/dist/index.js +35 -34
  223. package/dist/index.js.map +1 -1
  224. package/dist/indexes/index.cjs +13 -13
  225. package/dist/indexes/index.d.cts +4 -4
  226. package/dist/indexes/index.d.ts +4 -4
  227. package/dist/indexes/index.js +6 -6
  228. package/dist/interchange/index.cjs +53 -52
  229. package/dist/interchange/index.cjs.map +1 -1
  230. package/dist/interchange/index.d.cts +17 -13
  231. package/dist/interchange/index.d.ts +17 -13
  232. package/dist/interchange/index.js +17 -16
  233. package/dist/interchange/index.js.map +1 -1
  234. package/dist/{manager-DX983gJ0.d.ts → manager-B6sOQzcQ.d.ts} +2 -2
  235. package/dist/{manager-ptr3JLRP.d.cts → manager-DlY1vlla.d.cts} +2 -2
  236. package/dist/{materialize-indexes-CpXoggWU.d.ts → materialize-indexes-DQL4xcpf.d.ts} +1 -1
  237. package/dist/{materialize-indexes-BXz77YqO.d.cts → materialize-indexes-sskhp-c_.d.cts} +1 -1
  238. package/dist/{postgres-DwgEXSPI.d.cts → postgres-B6YOUcaK.d.ts} +1 -1
  239. package/dist/{postgres-QdOMHTBz.d.ts → postgres-BXy_nyMs.d.cts} +1 -1
  240. package/dist/profiler/index.d.cts +9 -9
  241. package/dist/profiler/index.d.ts +9 -9
  242. package/dist/provenance/index.cjs +43 -42
  243. package/dist/provenance/index.cjs.map +1 -1
  244. package/dist/provenance/index.d.cts +7 -7
  245. package/dist/provenance/index.d.ts +7 -7
  246. package/dist/provenance/index.js +10 -9
  247. package/dist/provenance/index.js.map +1 -1
  248. package/dist/schema/index.cjs +45 -44
  249. package/dist/schema/index.d.cts +6 -6
  250. package/dist/schema/index.d.ts +6 -6
  251. package/dist/schema/index.js +18 -17
  252. package/dist/{searchable-BrUh0pdc.d.cts → searchable-0Cn_lY8U.d.cts} +1 -1
  253. package/dist/{searchable-BOUuA83f.d.ts → searchable-D9gMiwcS.d.ts} +1 -1
  254. package/dist/{sqlite-BfYcV61E.d.cts → sqlite-ClXuBYAj.d.ts} +1 -1
  255. package/dist/{sqlite-DK40XVR1.d.ts → sqlite-DioSxaPe.d.cts} +1 -1
  256. package/dist/{store-Bo1psfVZ.d.ts → store-5j-w5onJ.d.ts} +120 -71
  257. package/dist/{store-FTqBpuJD.d.cts → store-CFedKGU5.d.cts} +120 -71
  258. package/dist/{temporal-CXewwLTB.d.cts → temporal-Ce1pG0S1.d.cts} +1 -1
  259. package/dist/{temporal-DPtn0HOu.d.ts → temporal-CjO9FH1l.d.ts} +1 -1
  260. package/dist/{types-CeT7xM4M.d.ts → types-BrdOFu48.d.ts} +1 -1
  261. package/dist/{types-Du78EvpN.d.ts → types-DKkMJZjR.d.cts} +32 -6
  262. package/dist/{types-Du78EvpN.d.cts → types-DKkMJZjR.d.ts} +32 -6
  263. package/dist/{types-XR1nHERK.d.cts → types-oj_OGAaZ.d.cts} +1 -1
  264. package/dist/{where-CMbFiWwp.d.ts → where-C8w2627G.d.ts} +1 -1
  265. package/dist/{where-CJsp-YUf.d.cts → where-D1wKCJ4Q.d.cts} +1 -1
  266. package/package.json +1 -1
  267. package/dist/chunk-32UETY6S.cjs.map +0 -1
  268. package/dist/chunk-5PDSCELM.cjs.map +0 -1
  269. package/dist/chunk-62ZXBMSD.js.map +0 -1
  270. package/dist/chunk-6LAXM4J2.cjs +0 -12
  271. package/dist/chunk-73MWKDFK.cjs +0 -12
  272. package/dist/chunk-A2LJG5GM.js.map +0 -1
  273. package/dist/chunk-A4R3H7RU.cjs.map +0 -1
  274. package/dist/chunk-ANKOXPCC.cjs.map +0 -1
  275. package/dist/chunk-AT3MLMJV.js.map +0 -1
  276. package/dist/chunk-BEB4OFZF.cjs.map +0 -1
  277. package/dist/chunk-BQM5P2NO.cjs.map +0 -1
  278. package/dist/chunk-C2ZP5YS6.cjs.map +0 -1
  279. package/dist/chunk-C6KE2HSQ.cjs.map +0 -1
  280. package/dist/chunk-EDHV4CBU.js.map +0 -1
  281. package/dist/chunk-FA6Z7B5L.js.map +0 -1
  282. package/dist/chunk-HNYLMPQQ.cjs.map +0 -1
  283. package/dist/chunk-IHRDX7K6.js.map +0 -1
  284. package/dist/chunk-KCH7WQA5.cjs.map +0 -1
  285. package/dist/chunk-KXSMF6PL.js.map +0 -1
  286. package/dist/chunk-LHYSY6SE.js.map +0 -1
  287. package/dist/chunk-N3QTGPC7.cjs.map +0 -1
  288. package/dist/chunk-QPOQX2DI.cjs.map +0 -1
  289. package/dist/chunk-R7EWHKID.js.map +0 -1
  290. package/dist/chunk-RHDOAQCT.cjs.map +0 -1
  291. package/dist/chunk-WEJHA7AD.js.map +0 -1
  292. package/dist/chunk-WYFB7BN3.js.map +0 -1
  293. package/dist/chunk-YQ4JONWE.js.map +0 -1
  294. package/dist/chunk-Z43KWLZF.js.map +0 -1
@@ -1,34 +1,35 @@
1
- import { createStoreWithSchema } from '../chunk-YQ4JONWE.js';
2
- import '../chunk-IHRDX7K6.js';
3
- import { importGraphStream, exportGraphStream, importGraph, exportGraph } from '../chunk-AT3MLMJV.js';
4
- import '../chunk-WEJHA7AD.js';
5
- import { storeBackend, storeRuntime, transactionBackend } from '../chunk-LHYSY6SE.js';
6
- import { computeSchemaHash, serializeSchema } from '../chunk-KA4L2NZ3.js';
7
- import { defineNode } from '../chunk-36LIJXWC.js';
8
- import { encodeTupleKey, normalizeIdentityPair, identityReferenceKey, identityAssertionSemanticKey } from '../chunk-A2LJG5GM.js';
9
- import { generateId, compareStrings, compareCodePoints, lockRecordedGraphWrite, readRevisionOrigin, readRecordedClock } from '../chunk-EDHV4CBU.js';
1
+ import { createStoreWithSchema } from '../chunk-DJZTXBWY.js';
2
+ import '../chunk-LEWHMOUY.js';
3
+ import { importGraphStream, exportGraphStream, importGraph, exportGraph } from '../chunk-QS3NQZVC.js';
4
+ import '../chunk-SE6EGQSD.js';
5
+ import { storeBackend, storeRuntime, transactionBackend } from '../chunk-FNOMT74T.js';
6
+ import { computeSchemaHash, serializeSchema } from '../chunk-XYBQI6LE.js';
7
+ import { defineNode } from '../chunk-SHFBB23Q.js';
8
+ import { encodeTupleKey, forceWriteTransactionRevision, normalizeIdentityPair, identityReferenceKey, identityValidityWindowsOverlap, identityAssertionSemanticKey } from '../chunk-NNQ6A3RM.js';
9
+ import { compareStrings, generateId, lockRecordedGraphWrite, readRevisionOrigin, readRecordedClock, forceRecordedGraphRevision, advanceRevisionClock, compareCodePoints } from '../chunk-AVGUJWVX.js';
10
10
  import { sortedReplacer } from '../chunk-JBQQ7THH.js';
11
- import '../chunk-DSAWGCMP.js';
12
- import { createSqlSchema } from '../chunk-72WUT4GG.js';
13
- import { snapshotExportContention } from '../chunk-FA6Z7B5L.js';
14
- import { getNodeKinds, getEdgeKinds, defineInternalGraph } from '../chunk-5ZJOB5YS.js';
15
- import { computeUniqueKey } from '../chunk-LNIWCQR4.js';
16
- import { canonicalizeDatabaseTimestamp } from '../chunk-R7EWHKID.js';
17
- import '../chunk-EDJDDY6H.js';
18
- import '../chunk-FSLTU57P.js';
19
- import '../chunk-WMKEGJG4.js';
11
+ import '../chunk-NAZ2RA4L.js';
12
+ import { createSqlSchema } from '../chunk-NVY5CJFP.js';
13
+ import { snapshotExportContention } from '../chunk-SHSSRCFR.js';
14
+ import { getNodeKinds, getEdgeKinds, defineInternalGraph } from '../chunk-HEV35TGT.js';
15
+ import { validateEdgeEndpoints, computeUniqueKey } from '../chunk-GLHXM7C4.js';
16
+ import '../chunk-XY5OMOJD.js';
17
+ import { canonicalizeDatabaseTimestamp } from '../chunk-NG2RB5WL.js';
18
+ import '../chunk-KKBDXCLQ.js';
19
+ import '../chunk-SDBBMD4N.js';
20
+ import '../chunk-P6YLZTRE.js';
20
21
  import { sha256Hex } from '../chunk-K34OPSJK.js';
21
22
  import '../chunk-2EBMPJBO.js';
22
- import '../chunk-EAMVXR5B.js';
23
- import { asCompiledStatementSql, sql, asCompiledRowsSql } from '../chunk-QOJ2L2AD.js';
23
+ import '../chunk-ZDKTPBGV.js';
24
+ import { asCompiledStatementSql, sql, asCompiledRowsSql } from '../chunk-3DMMGT6S.js';
24
25
  import '../chunk-AMKPPFUO.js';
25
26
  import '../chunk-W6OG3HDG.js';
26
27
  import '../chunk-PC2UBAUB.js';
27
- import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-SXWLLQ3D.js';
28
- export { isErr, isOk, unwrap } from '../chunk-SXWLLQ3D.js';
28
+ import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-D5R42W5D.js';
29
+ export { isErr, isOk, unwrap } from '../chunk-D5R42W5D.js';
29
30
  import { META_EDGE_EQUIVALENT_TO, META_EDGE_SAME_AS } from '../chunk-JXXDZTMN.js';
30
- import '../chunk-OZJCH7ZS.js';
31
- import { TypeGraphError, ConfigurationError, IdentityContradictionError, NodeNotFoundError } from '../chunk-62ZXBMSD.js';
31
+ import '../chunk-XVWOYKIK.js';
32
+ import { TypeGraphError, ConfigurationError, IdentityContradictionError, NodeNotFoundError, IdentityEndpointValidityError } from '../chunk-ZLONH2UD.js';
32
33
  import '../chunk-FWJKWH6U.js';
33
34
  import { createDataKeyedBag, hasOwnKey, requireDefined, readOwnProperty } from '../chunk-DYATYRY4.js';
34
35
  import { z } from 'zod';
@@ -106,8 +107,20 @@ var MERGE_ERROR_CODES = {
106
107
  branch: "GRAPH_MERGE_BRANCH_ERROR",
107
108
  similarityUnavailable: "GRAPH_MERGE_SIMILARITY_UNAVAILABLE",
108
109
  conflict: "GRAPH_MERGE_CONFLICT",
110
+ constraintConflict: "GRAPH_MERGE_CONSTRAINT_CONFLICT",
109
111
  identityConflict: "GRAPH_MERGE_IDENTITY_CONFLICT",
110
- baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH"
112
+ baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH",
113
+ planCapability: "GRAPH_MERGE_PLAN_CAPABILITY",
114
+ planInvalid: "GRAPH_MERGE_PLAN_INVALID",
115
+ planVersionUnsupported: "GRAPH_MERGE_PLAN_VERSION_UNSUPPORTED",
116
+ planDigestMismatch: "GRAPH_MERGE_PLAN_DIGEST_MISMATCH",
117
+ planTargetMismatch: "GRAPH_MERGE_PLAN_TARGET_MISMATCH",
118
+ planSchemaMismatch: "GRAPH_MERGE_PLAN_SCHEMA_MISMATCH",
119
+ planOriginMismatch: "GRAPH_MERGE_PLAN_ORIGIN_MISMATCH",
120
+ planStale: "GRAPH_MERGE_PLAN_STALE",
121
+ planningStale: "GRAPH_MERGE_PLANNING_STALE",
122
+ candidateSource: "GRAPH_MERGE_CANDIDATE_SOURCE",
123
+ evidence: "GRAPH_MERGE_EVIDENCE"
111
124
  };
112
125
  function toTypeGraphErrorOptions(category, options) {
113
126
  return {
@@ -164,6 +177,31 @@ var MergeConflictError = class extends MergeError {
164
177
  this.name = "MergeConflictError";
165
178
  }
166
179
  };
180
+ var MergeConstraintConflictError = class extends MergeError {
181
+ static errorCategory = "constraint";
182
+ code = MERGE_ERROR_CODES.constraintConflict;
183
+ constructor(cause) {
184
+ super(`The resolved merge would violate ${cause.name}: ${cause.message}`, {
185
+ cause,
186
+ details: {
187
+ ...cause.details,
188
+ constraintCode: cause.code,
189
+ constraintErrorName: cause.name,
190
+ constraintDetails: cause.details
191
+ },
192
+ suggestion: cause.suggestion ?? "Change the branch data or target state so the resolved graph satisfies its constraints, then retry the merge."
193
+ });
194
+ this.name = "MergeConstraintConflictError";
195
+ }
196
+ };
197
+ function translateMergeCommitError(error) {
198
+ if (error instanceof MergeError) return error;
199
+ if (!(error instanceof TypeGraphError)) return error;
200
+ if (error.category !== "constraint" || error.code.startsWith("IDENTITY_")) {
201
+ return error;
202
+ }
203
+ return new MergeConstraintConflictError(error);
204
+ }
167
205
  var IdentityMergeConflictError = class extends MergeError {
168
206
  code = MERGE_ERROR_CODES.identityConflict;
169
207
  constructor(message, options = {}) {
@@ -181,56 +219,496 @@ var BaseVersionMismatchError = class extends MergeError {
181
219
  this.name = "BaseVersionMismatchError";
182
220
  }
183
221
  };
222
+ var MergePlanCapabilityError = class extends MergeError {
223
+ static errorCategory = "user";
224
+ code = MERGE_ERROR_CODES.planCapability;
225
+ constructor(message, options = {}) {
226
+ super(message, options);
227
+ this.name = "MergePlanCapabilityError";
228
+ }
229
+ };
230
+ var InvalidMergePlanError = class extends MergeError {
231
+ static errorCategory = "user";
232
+ code = MERGE_ERROR_CODES.planInvalid;
233
+ constructor(message, options = {}) {
234
+ super(message, options);
235
+ this.name = "InvalidMergePlanError";
236
+ }
237
+ };
238
+ var UnsupportedMergePlanVersionError = class extends InvalidMergePlanError {
239
+ code = MERGE_ERROR_CODES.planVersionUnsupported;
240
+ constructor(message, options = {}) {
241
+ super(message, options);
242
+ this.name = "UnsupportedMergePlanVersionError";
243
+ }
244
+ };
245
+ var MergePlanDigestMismatchError = class extends InvalidMergePlanError {
246
+ code = MERGE_ERROR_CODES.planDigestMismatch;
247
+ constructor(message, options = {}) {
248
+ super(message, options);
249
+ this.name = "MergePlanDigestMismatchError";
250
+ }
251
+ };
252
+ var MergePlanTargetMismatchError = class extends InvalidMergePlanError {
253
+ code = MERGE_ERROR_CODES.planTargetMismatch;
254
+ constructor(message, options = {}) {
255
+ super(message, options);
256
+ this.name = "MergePlanTargetMismatchError";
257
+ }
258
+ };
259
+ var MergePlanSchemaMismatchError = class extends InvalidMergePlanError {
260
+ code = MERGE_ERROR_CODES.planSchemaMismatch;
261
+ constructor(message, options = {}) {
262
+ super(message, options);
263
+ this.name = "MergePlanSchemaMismatchError";
264
+ }
265
+ };
266
+ var MergePlanOriginMismatchError = class extends InvalidMergePlanError {
267
+ code = MERGE_ERROR_CODES.planOriginMismatch;
268
+ constructor(message, options = {}) {
269
+ super(message, options);
270
+ this.name = "MergePlanOriginMismatchError";
271
+ }
272
+ };
273
+ var StaleMergePlanError = class extends MergeError {
274
+ code = MERGE_ERROR_CODES.planStale;
275
+ constructor(message, options = {}) {
276
+ super(message, {
277
+ ...options,
278
+ suggestion: options.suggestion ?? "Create a new merge plan against the target's current revision, review it, and apply that plan instead."
279
+ });
280
+ this.name = "StaleMergePlanError";
281
+ }
282
+ };
283
+ var MergePlanningStaleError = class extends StaleMergePlanError {
284
+ code = MERGE_ERROR_CODES.planningStale;
285
+ constructor(message, options = {}) {
286
+ super(message, options);
287
+ this.name = "MergePlanningStaleError";
288
+ }
289
+ };
290
+ var CandidateSourceError = class extends MergeError {
291
+ code = MERGE_ERROR_CODES.candidateSource;
292
+ constructor(message, options = {}) {
293
+ super(message, options);
294
+ this.name = "CandidateSourceError";
295
+ }
296
+ };
297
+ var MatchEvidenceError = class extends MergeError {
298
+ code = MERGE_ERROR_CODES.evidence;
299
+ constructor(message, options = {}) {
300
+ super(message, options);
301
+ this.name = "MatchEvidenceError";
302
+ }
303
+ };
184
304
  function describeCause(cause) {
185
305
  return cause instanceof Error ? cause.message : String(cause);
186
306
  }
187
307
 
188
- // src/graph-merge/union-find.ts
189
- var UnionFind = class {
190
- constructor(compare) {
191
- this.compare = compare;
308
+ // src/graph-merge/temporal-identity-closure.ts
309
+ function disjointCacheKey(left, right) {
310
+ return left <= right ? `${left}\0${right}` : `${right}\0${left}`;
311
+ }
312
+ function areDisjoint(state, left, right) {
313
+ const key = disjointCacheKey(left, right);
314
+ const cached = state.disjointCache.get(key);
315
+ if (cached !== void 0) return cached;
316
+ const result = state.context.areDisjoint(left, right);
317
+ state.disjointCache.set(key, result);
318
+ return result;
319
+ }
320
+ function findRoot(components, index) {
321
+ let root = index;
322
+ while (components.parent[root] !== root) {
323
+ root = requireDefined(components.parent[root]);
192
324
  }
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);
325
+ return root;
326
+ }
327
+ function componentKindsConflict(state, leftKinds, rightKinds) {
328
+ for (const left of leftKinds.keys()) {
329
+ for (const right of rightKinds.keys()) {
330
+ if (left !== right && areDisjoint(state, left, right)) return true;
199
331
  }
200
332
  }
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));
333
+ return false;
334
+ }
335
+ function unionReferences(state, leftIndex, rightIndex) {
336
+ const { components } = state;
337
+ const leftRoot = findRoot(components, leftIndex);
338
+ const rightRoot = findRoot(components, rightIndex);
339
+ if (leftRoot === rightRoot) return;
340
+ const [parentRoot, childRoot] = requireDefined(components.sizes[leftRoot]) >= requireDefined(components.sizes[rightRoot]) ? [leftRoot, rightRoot] : [rightRoot, leftRoot];
341
+ const parentKinds = requireDefined(components.kindFirstReference[parentRoot]);
342
+ const childKinds = requireDefined(components.kindFirstReference[childRoot]);
343
+ const parentDifferent = requireDefined(
344
+ components.differentAssertions[parentRoot]
345
+ );
346
+ const childDifferent = requireDefined(
347
+ components.differentAssertions[childRoot]
348
+ );
349
+ const previousParentSize = requireDefined(components.sizes[parentRoot]);
350
+ const previousViolation = requireDefined(
351
+ components.violatesOntology[parentRoot]
352
+ );
353
+ const changedKinds = [...childKinds].flatMap(
354
+ ([kind, childFirstReference]) => {
355
+ const previousFirstReference = parentKinds.get(kind);
356
+ return previousFirstReference === void 0 || childFirstReference < previousFirstReference ? [
357
+ {
358
+ kind,
359
+ previousFirstReference,
360
+ nextFirstReference: childFirstReference
361
+ }
362
+ ] : [];
207
363
  }
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;
364
+ );
365
+ const addedDifferent = [...childDifferent].filter(
366
+ (assertionIndex) => !parentDifferent.has(assertionIndex)
367
+ );
368
+ const addedContradictions = [];
369
+ const crossesDisjointKinds = componentKindsConflict(
370
+ state,
371
+ parentKinds,
372
+ childKinds
373
+ );
374
+ components.parent[childRoot] = parentRoot;
375
+ components.sizes[parentRoot] = previousParentSize + requireDefined(components.sizes[childRoot]);
376
+ for (const { kind, nextFirstReference } of changedKinds) {
377
+ parentKinds.set(kind, nextFirstReference);
378
+ }
379
+ for (const assertionIndex of addedDifferent) {
380
+ parentDifferent.add(assertionIndex);
381
+ }
382
+ components.violatesOntology[parentRoot] = previousViolation || requireDefined(components.violatesOntology[childRoot]) || crossesDisjointKinds;
383
+ for (const assertionIndex of childDifferent) {
384
+ const assertion = requireDefined(state.assertions[assertionIndex]);
385
+ const aIndex = requireDefined(
386
+ state.referenceIndexByKey.get(mergeKeyOf(assertion.a))
387
+ );
388
+ const bIndex = requireDefined(
389
+ state.referenceIndexByKey.get(mergeKeyOf(assertion.b))
390
+ );
391
+ if (findRoot(components, aIndex) === findRoot(components, bIndex) && !state.contradictoryAssertions.has(assertionIndex)) {
392
+ state.contradictoryAssertions.add(assertionIndex);
393
+ addedContradictions.push(assertionIndex);
213
394
  }
214
- return root;
215
395
  }
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;
396
+ state.undo.push(() => {
397
+ for (const assertionIndex of addedContradictions) {
398
+ state.contradictoryAssertions.delete(assertionIndex);
222
399
  }
223
- if (this.compare(leftRoot, rightRoot) <= 0) {
224
- this.parent.set(rightRoot, leftRoot);
400
+ components.violatesOntology[parentRoot] = previousViolation;
401
+ for (const assertionIndex of addedDifferent) {
402
+ parentDifferent.delete(assertionIndex);
403
+ }
404
+ for (const { kind, previousFirstReference } of changedKinds) {
405
+ if (previousFirstReference === void 0) parentKinds.delete(kind);
406
+ else parentKinds.set(kind, previousFirstReference);
407
+ }
408
+ components.sizes[parentRoot] = previousParentSize;
409
+ components.parent[childRoot] = childRoot;
410
+ });
411
+ }
412
+ function activateReference(state, referenceIndex) {
413
+ if (state.activeReferences[referenceIndex] === true) return;
414
+ const reference = requireDefined(state.references[referenceIndex]);
415
+ const peers = [...state.activeReferencesById.get(reference.id) ?? []];
416
+ let addedSharedIdViolations = 0;
417
+ for (const peerIndex of peers) {
418
+ const peer = requireDefined(state.references[peerIndex]);
419
+ if (areDisjoint(state, reference.kind, peer.kind)) {
420
+ addedSharedIdViolations += 1;
421
+ }
422
+ }
423
+ state.sharedIdViolationCount.value += addedSharedIdViolations;
424
+ state.activeReferences[referenceIndex] = true;
425
+ const activeForId = state.activeReferencesById.get(reference.id) ?? /* @__PURE__ */ new Set();
426
+ activeForId.add(referenceIndex);
427
+ state.activeReferencesById.set(reference.id, activeForId);
428
+ state.undo.push(() => {
429
+ activeForId.delete(referenceIndex);
430
+ if (activeForId.size === 0) {
431
+ state.activeReferencesById.delete(reference.id);
432
+ }
433
+ state.activeReferences[referenceIndex] = false;
434
+ state.sharedIdViolationCount.value -= addedSharedIdViolations;
435
+ });
436
+ if (state.context.sameIdAcrossKinds === "fold") {
437
+ for (const peerIndex of peers) {
438
+ unionReferences(state, referenceIndex, peerIndex);
439
+ }
440
+ }
441
+ }
442
+ function addDifferentAssertion(state, assertionIndex) {
443
+ const assertion = requireDefined(state.assertions[assertionIndex]);
444
+ const aIndex = requireDefined(
445
+ state.referenceIndexByKey.get(mergeKeyOf(assertion.a))
446
+ );
447
+ const bIndex = requireDefined(
448
+ state.referenceIndexByKey.get(mergeKeyOf(assertion.b))
449
+ );
450
+ const aRoot = findRoot(state.components, aIndex);
451
+ const bRoot = findRoot(state.components, bIndex);
452
+ if (aRoot === bRoot) {
453
+ state.contradictoryAssertions.add(assertionIndex);
454
+ state.undo.push(() => {
455
+ state.contradictoryAssertions.delete(assertionIndex);
456
+ });
457
+ return;
458
+ }
459
+ const aDifferent = requireDefined(
460
+ state.components.differentAssertions[aRoot]
461
+ );
462
+ const bDifferent = requireDefined(
463
+ state.components.differentAssertions[bRoot]
464
+ );
465
+ aDifferent.add(assertionIndex);
466
+ bDifferent.add(assertionIndex);
467
+ state.undo.push(() => {
468
+ aDifferent.delete(assertionIndex);
469
+ bDifferent.delete(assertionIndex);
470
+ });
471
+ }
472
+ function rollback(state, snapshot) {
473
+ while (state.undo.length > snapshot) {
474
+ requireDefined(state.undo.pop())();
475
+ }
476
+ }
477
+ function firstDisjointPair(state, kinds) {
478
+ for (const [index, left] of kinds.entries()) {
479
+ for (const right of kinds.slice(index + 1)) {
480
+ if (areDisjoint(state, left, right)) return [left, right];
481
+ }
482
+ }
483
+ return void 0;
484
+ }
485
+ function firstAssertionIndex(assertionIndexes) {
486
+ let first = Number.POSITIVE_INFINITY;
487
+ for (const assertionIndex of assertionIndexes) {
488
+ first = Math.min(first, assertionIndex);
489
+ }
490
+ return first;
491
+ }
492
+ function activeClassMembers(state, root) {
493
+ return state.references.filter(
494
+ (_reference, index) => state.activeReferences[index] === true && findRoot(state.components, index) === root
495
+ ).toSorted(
496
+ (left, right) => compareMergeKeys(
497
+ mergeKey(left.kind, left.id),
498
+ mergeKey(right.kind, right.id)
499
+ )
500
+ );
501
+ }
502
+ function componentKindsInReferenceOrder(state, root) {
503
+ return [...requireDefined(state.components.kindFirstReference[root])].toSorted((left, right) => left[1] - right[1]).map(([kind]) => kind);
504
+ }
505
+ function assertCoordinateConsistent(state) {
506
+ if (state.sharedIdViolationCount.value > 0) {
507
+ const activeById = /* @__PURE__ */ new Map();
508
+ for (const [index, reference] of state.references.entries()) {
509
+ if (state.activeReferences[index] !== true) continue;
510
+ const references = activeById.get(reference.id) ?? [];
511
+ references.push(reference);
512
+ activeById.set(reference.id, references);
513
+ }
514
+ for (const [sharedId, references] of activeById) {
515
+ const disjointKinds = firstDisjointPair(state, [
516
+ ...new Set(references.map((reference) => reference.kind))
517
+ ]);
518
+ if (disjointKinds === void 0) continue;
519
+ throw new IdentityMergeConflictError(
520
+ "The merged graph would give one id to two ontology-disjoint kinds.",
521
+ { details: { disjointKinds, sharedId } }
522
+ );
523
+ }
524
+ }
525
+ if (state.contradictoryAssertions.size > 0) {
526
+ const assertionIndex = firstAssertionIndex(state.contradictoryAssertions);
527
+ const assertion = requireDefined(state.assertions[assertionIndex]);
528
+ const root = findRoot(
529
+ state.components,
530
+ requireDefined(state.referenceIndexByKey.get(mergeKeyOf(assertion.a)))
531
+ );
532
+ throw new IdentityMergeConflictError(
533
+ "The merged identity ledger would assert one pair of nodes is both the same and different.",
534
+ {
535
+ details: {
536
+ assertion,
537
+ sameClass: activeClassMembers(state, root)
538
+ }
539
+ }
540
+ );
541
+ }
542
+ const checkedRoots = /* @__PURE__ */ new Set();
543
+ for (const [referenceIndex, active] of state.activeReferences.entries()) {
544
+ if (!active) continue;
545
+ const root = findRoot(state.components, referenceIndex);
546
+ if (checkedRoots.has(root)) continue;
547
+ checkedRoots.add(root);
548
+ if (!requireDefined(state.components.violatesOntology[root])) continue;
549
+ const sameClass = activeClassMembers(state, root);
550
+ const disjointKinds = firstDisjointPair(
551
+ state,
552
+ componentKindsInReferenceOrder(state, root)
553
+ );
554
+ if (disjointKinds === void 0) continue;
555
+ throw new IdentityMergeConflictError(
556
+ "The merged identity ledger would join two ontology-disjoint kinds into one class.",
557
+ { details: { disjointKinds, sameClass } }
558
+ );
559
+ }
560
+ }
561
+ function addRangeEvent(events, node, left, right, start, end, add) {
562
+ if (start <= left && right <= end) {
563
+ add(requireDefined(events[node]));
564
+ return;
565
+ }
566
+ const middle = Math.floor((left + right) / 2);
567
+ if (start < middle) {
568
+ addRangeEvent(events, node * 2, left, middle, start, end, add);
569
+ }
570
+ if (middle < end) {
571
+ addRangeEvent(events, node * 2 + 1, middle, right, start, end, add);
572
+ }
573
+ }
574
+ function visitTemporalEvents(events, state, node, left, right) {
575
+ const snapshot = state.undo.length;
576
+ const event = requireDefined(events[node]);
577
+ for (const referenceIndex of event.references) {
578
+ activateReference(state, referenceIndex);
579
+ }
580
+ for (const assertionIndex of event.sameAssertions) {
581
+ const assertion = requireDefined(state.assertions[assertionIndex]);
582
+ unionReferences(
583
+ state,
584
+ requireDefined(state.referenceIndexByKey.get(mergeKeyOf(assertion.a))),
585
+ requireDefined(state.referenceIndexByKey.get(mergeKeyOf(assertion.b)))
586
+ );
587
+ }
588
+ for (const assertionIndex of event.differentAssertions) {
589
+ addDifferentAssertion(state, assertionIndex);
590
+ }
591
+ if (right - left === 1) {
592
+ assertCoordinateConsistent(state);
593
+ } else {
594
+ const middle = Math.floor((left + right) / 2);
595
+ visitTemporalEvents(events, state, node * 2, left, middle);
596
+ visitTemporalEvents(events, state, node * 2 + 1, middle, right);
597
+ }
598
+ rollback(state, snapshot);
599
+ }
600
+ function mergeRanges(ranges) {
601
+ const merged = [];
602
+ for (const range of ranges.toSorted(
603
+ (left, right) => left.start - right.start
604
+ )) {
605
+ const previous = merged.at(-1);
606
+ if (previous === void 0 || previous.end < range.start) {
607
+ merged.push({ ...range });
225
608
  } else {
226
- this.parent.set(leftRoot, rightRoot);
609
+ previous.end = Math.max(previous.end, range.end);
227
610
  }
228
611
  }
229
- /** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
230
- members() {
231
- return [...this.parent.keys()];
612
+ return merged;
613
+ }
614
+ function assertTemporalIdentityClosureConsistent(assertions, identityContext, nodeUniverse) {
615
+ const boundaries = /* @__PURE__ */ new Set();
616
+ for (const assertion of assertions) {
617
+ boundaries.add(assertion.validFrom);
618
+ if (assertion.validTo !== void 0) boundaries.add(assertion.validTo);
232
619
  }
233
- };
620
+ const sortedBoundaries = [...boundaries].toSorted();
621
+ const leafCount = Math.max(1, sortedBoundaries.length);
622
+ const boundaryIndex = new Map(
623
+ sortedBoundaries.map((boundary, index) => [boundary, index])
624
+ );
625
+ const assertionRanges = assertions.map(
626
+ (assertion, assertionIndex) => ({
627
+ assertionIndex,
628
+ start: requireDefined(boundaryIndex.get(assertion.validFrom)),
629
+ end: assertion.validTo === void 0 ? leafCount : requireDefined(boundaryIndex.get(assertion.validTo))
630
+ })
631
+ );
632
+ const references = [];
633
+ const referenceIndexByKey = /* @__PURE__ */ new Map();
634
+ const addReference = (reference) => {
635
+ const key = mergeKey(reference.kind, reference.id);
636
+ const existing = referenceIndexByKey.get(key);
637
+ if (existing !== void 0) return existing;
638
+ const index = references.length;
639
+ references.push(reference);
640
+ referenceIndexByKey.set(key, index);
641
+ return index;
642
+ };
643
+ const alwaysActive = new Set(nodeUniverse.map((node) => addReference(node)));
644
+ const rangesByReference = /* @__PURE__ */ new Map();
645
+ for (const range of assertionRanges) {
646
+ if (range.start >= range.end) continue;
647
+ const assertion = requireDefined(assertions[range.assertionIndex]);
648
+ for (const endpoint of [assertion.a, assertion.b]) {
649
+ const referenceIndex = addReference(endpoint);
650
+ const ranges = rangesByReference.get(referenceIndex) ?? [];
651
+ ranges.push({ start: range.start, end: range.end });
652
+ rangesByReference.set(referenceIndex, ranges);
653
+ }
654
+ }
655
+ const events = Array.from(
656
+ { length: leafCount * 4 },
657
+ () => ({ references: [], sameAssertions: [], differentAssertions: [] })
658
+ );
659
+ for (const [referenceIndex, ranges] of rangesByReference) {
660
+ if (alwaysActive.has(referenceIndex)) continue;
661
+ for (const range of mergeRanges(ranges)) {
662
+ addRangeEvent(
663
+ events,
664
+ 1,
665
+ 0,
666
+ leafCount,
667
+ range.start,
668
+ range.end,
669
+ (event) => event.references.push(referenceIndex)
670
+ );
671
+ }
672
+ }
673
+ for (const range of assertionRanges) {
674
+ if (range.start >= range.end) continue;
675
+ const assertion = requireDefined(assertions[range.assertionIndex]);
676
+ addRangeEvent(
677
+ events,
678
+ 1,
679
+ 0,
680
+ leafCount,
681
+ range.start,
682
+ range.end,
683
+ (event) => assertion.relation === "same" ? event.sameAssertions.push(range.assertionIndex) : event.differentAssertions.push(range.assertionIndex)
684
+ );
685
+ }
686
+ const state = {
687
+ assertions,
688
+ context: identityContext,
689
+ references,
690
+ referenceIndexByKey,
691
+ activeReferences: references.map(() => false),
692
+ activeReferencesById: /* @__PURE__ */ new Map(),
693
+ components: {
694
+ parent: references.map((_reference, index) => index),
695
+ sizes: references.map(() => 1),
696
+ kindFirstReference: references.map(
697
+ (reference, index) => /* @__PURE__ */ new Map([[reference.kind, index]])
698
+ ),
699
+ differentAssertions: references.map(() => /* @__PURE__ */ new Set()),
700
+ violatesOntology: references.map(() => false)
701
+ },
702
+ contradictoryAssertions: /* @__PURE__ */ new Set(),
703
+ undo: [],
704
+ disjointCache: /* @__PURE__ */ new Map(),
705
+ sharedIdViolationCount: { value: 0 }
706
+ };
707
+ for (const referenceIndex of alwaysActive)
708
+ activateReference(state, referenceIndex);
709
+ state.undo.length = 0;
710
+ visitTemporalEvents(events, state, 1, 0, leafCount);
711
+ }
234
712
 
235
713
  // src/graph-merge/merge-identity.ts
236
714
  function endpointTuple(assertion) {
@@ -246,12 +724,18 @@ function identitySemanticKey(assertion) {
246
724
  assertion.b
247
725
  );
248
726
  }
727
+ function identityDedupeKey(assertion) {
728
+ const semantic = identitySemanticKey(assertion);
729
+ if (assertion.validTo === void 0) return semantic;
730
+ return encodeTupleKey([semantic, assertion.validFrom, assertion.validTo]);
731
+ }
249
732
  function compareIdentitySurvivors(left, right) {
250
733
  const byValidity = compareCodePoints(left.validFrom, right.validFrom);
251
734
  return byValidity === 0 ? compareCodePoints(left.id, right.id) : byValidity;
252
735
  }
253
736
  var DUPLICATE_IDENTITY_ASSERTION_DROP_REASON = "identity:duplicate-assertion";
254
737
  var REDUNDANT_IDENTITY_ASSERTION_DROP_REASON = "identity:endpoints-collapsed";
738
+ var EMPTY_REMAPPED_IDENTITY_WINDOW_DROP_REASON = "identity:empty-remapped-window";
255
739
  function droppedIdentityAssertion(assertion, reason) {
256
740
  return { kind: "identity", id: assertion.id, reason };
257
741
  }
@@ -259,7 +743,7 @@ function dedupeIdentityAssertions(assertions, committedIds) {
259
743
  const survivorBySemantic = /* @__PURE__ */ new Map();
260
744
  const dropped = [];
261
745
  for (const assertion of assertions) {
262
- const key = identitySemanticKey(assertion);
746
+ const key = identityDedupeKey(assertion);
263
747
  const previous = survivorBySemantic.get(key);
264
748
  if (previous === void 0) {
265
749
  survivorBySemantic.set(key, assertion);
@@ -278,7 +762,7 @@ function dedupeIdentityAssertions(assertions, committedIds) {
278
762
  }
279
763
  return {
280
764
  survivors: [...survivorBySemantic.values()].toSorted(
281
- (left, right) => compareCodePoints(identitySemanticKey(left), identitySemanticKey(right))
765
+ (left, right) => compareCodePoints(identityDedupeKey(left), identityDedupeKey(right))
282
766
  ),
283
767
  dropped
284
768
  };
@@ -292,13 +776,26 @@ function assertNoOpposingIdentityRelations(assertions) {
292
776
  byEndpoint.set(key, group);
293
777
  }
294
778
  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 } }
779
+ const same = group.filter((assertion) => assertion.relation === "same");
780
+ const different = group.filter(
781
+ (assertion) => assertion.relation === "different"
301
782
  );
783
+ for (const sameAssertion of same) {
784
+ for (const differentAssertion of different) {
785
+ if (!identityValidityWindowsOverlap(sameAssertion, differentAssertion)) {
786
+ continue;
787
+ }
788
+ throw new IdentityMergeConflictError(
789
+ "Branches asserted opposing identity relations for one endpoint pair.",
790
+ {
791
+ details: {
792
+ endpoint,
793
+ assertions: [sameAssertion, differentAssertion]
794
+ }
795
+ }
796
+ );
797
+ }
798
+ }
302
799
  }
303
800
  }
304
801
  function assertNoRetractReassertRace(staging) {
@@ -415,9 +912,10 @@ function resolveIdentityEndpoint(endpoint, canonicalOf, retypeMap) {
415
912
  id: idOf(canonical)
416
913
  };
417
914
  }
418
- function remapIdentityAssertionEndpoints(assertions, canonicalOf, retypeMap, storedRowsById) {
915
+ function remapIdentityAssertionEndpoints(assertions, canonicalOf, retypeMap, storedRowsById, endpointWindows = /* @__PURE__ */ new Map()) {
419
916
  const remapped = [];
420
917
  const dropped = [];
918
+ const narrowingWarningById = /* @__PURE__ */ new Map();
421
919
  for (const assertion of assertions) {
422
920
  const remappedA = resolveIdentityEndpoint(
423
921
  assertion.a,
@@ -445,7 +943,47 @@ function remapIdentityAssertionEndpoints(assertions, canonicalOf, retypeMap, sto
445
943
  continue;
446
944
  }
447
945
  const [a, b] = normalizeIdentityPair(remappedA, remappedB);
448
- const result = { ...assertion, a, b };
946
+ const endpointWindowA = endpointWindows.get(mergeKeyOf(a));
947
+ const endpointWindowB = endpointWindows.get(mergeKeyOf(b));
948
+ const validFrom = [
949
+ assertion.validFrom,
950
+ endpointWindowA?.validFrom,
951
+ endpointWindowB?.validFrom
952
+ ].filter((bound) => bound !== void 0).reduce(
953
+ (latest, bound) => compareCodePoints(latest, bound) < 0 ? bound : latest
954
+ );
955
+ const validTo = [
956
+ assertion.validTo,
957
+ endpointWindowA?.validTo,
958
+ endpointWindowB?.validTo
959
+ ].filter((bound) => bound !== void 0).reduce(
960
+ (earliest, bound) => earliest === void 0 || compareCodePoints(bound, earliest) < 0 ? bound : earliest,
961
+ void 0
962
+ );
963
+ if (validTo !== void 0 && compareCodePoints(validFrom, validTo) >= 0) {
964
+ dropped.push(
965
+ droppedIdentityAssertion(
966
+ assertion,
967
+ EMPTY_REMAPPED_IDENTITY_WINDOW_DROP_REASON
968
+ )
969
+ );
970
+ continue;
971
+ }
972
+ if (validFrom !== assertion.validFrom || validTo !== assertion.validTo) {
973
+ const originalUpper = assertion.validTo ?? "open";
974
+ const remappedUpper = validTo ?? "open";
975
+ narrowingWarningById.set(
976
+ assertion.id,
977
+ `Identity assertion ${JSON.stringify(assertion.id)} was narrowed from [${assertion.validFrom}, ${originalUpper}) to [${validFrom}, ${remappedUpper}) to fit its remapped endpoint windows.`
978
+ );
979
+ }
980
+ const result = {
981
+ ...assertion,
982
+ a,
983
+ b,
984
+ validFrom,
985
+ ...validTo === void 0 ? {} : { validTo }
986
+ };
449
987
  const stored = storedRowsById.get(assertion.id);
450
988
  if (stored !== void 0 && assertionTruthKey(stored) === assertionTruthKey(assertion) && assertionTruthKey(result) !== assertionTruthKey(assertion)) {
451
989
  throw new IdentityMergeConflictError(
@@ -468,7 +1006,11 @@ function remapIdentityAssertionEndpoints(assertions, canonicalOf, retypeMap, sto
468
1006
  assertNoOpposingIdentityRelations(deduped.survivors);
469
1007
  return {
470
1008
  assertions: deduped.survivors,
471
- dropped: [...dropped, ...deduped.dropped]
1009
+ dropped: [...dropped, ...deduped.dropped],
1010
+ warnings: deduped.survivors.flatMap((assertion) => {
1011
+ const warning = narrowingWarningById.get(assertion.id);
1012
+ return warning === void 0 ? [] : [warning];
1013
+ })
472
1014
  };
473
1015
  }
474
1016
  function assertIdentityEndpointsNotDeleted(assertions, nodeDeletions, retypeMap) {
@@ -505,89 +1047,11 @@ function assertNoContradictoryIdentityClosure(plannedAssertions, retractions, ba
505
1047
  b: resolveIdentityEndpoint(assertion.b, canonicalOf, retypeMap)
506
1048
  }))
507
1049
  ];
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))
1050
+ assertTemporalIdentityClosureConsistent(
1051
+ mergedLedger,
1052
+ identityContext,
1053
+ nodeUniverse
521
1054
  );
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
1055
  }
592
1056
  async function buildIdentityPeerProbe(target, probedIds, targetPeers, plan, profile) {
593
1057
  const finalIds = /* @__PURE__ */ new Set([
@@ -608,9 +1072,9 @@ async function buildIdentityPeerProbe(target, probedIds, targetPeers, plan, prof
608
1072
  seedsByKey.set(mergeKey(kind, id), { kind, id });
609
1073
  };
610
1074
  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);
1075
+ const sourceKey2 = mergeKey(entity.kind, entity.canonicalId);
1076
+ if (plan.nodeDeletions.has(sourceKey2)) continue;
1077
+ addSeed(plan.retypeMap.get(sourceKey2) ?? entity.kind, entity.canonicalId);
614
1078
  }
615
1079
  for (const assertion of plan.identityAssertions) {
616
1080
  addSeed(assertion.a.kind, assertion.a.id);
@@ -789,9 +1253,9 @@ function affectedIdentityClassSeeds(plan, profile) {
789
1253
  }
790
1254
  if (profile === "fold") {
791
1255
  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);
1256
+ const sourceKey2 = mergeKey(entity.kind, entity.canonicalId);
1257
+ if (plan.nodeDeletions.has(sourceKey2)) continue;
1258
+ addSeed(plan.retypeMap.get(sourceKey2) ?? entity.kind, entity.canonicalId);
795
1259
  }
796
1260
  }
797
1261
  return [...seedsByKey.values()];
@@ -1126,30 +1590,22 @@ function classifyRetraction(assertion, deletedByKey, forkAssertion) {
1126
1590
  }
1127
1591
  return EXPLICIT_RETRACTION;
1128
1592
  }
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
- }
1593
+ function classifyRetractions(retracted, deletedNodes, forkRowsById) {
1594
+ if (retracted.length === 0) return [];
1136
1595
  const deletedByKey = new Map(
1137
1596
  deletedNodes.map((deletion) => [
1138
1597
  mergeKey(deletion.kind, deletion.id),
1139
1598
  deletion
1140
1599
  ])
1141
1600
  );
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
- }));
1601
+ return retracted.map((assertion) => {
1602
+ const forkAssertion = forkRowsById.get(assertion.id);
1603
+ const endedAssertion = forkAssertion?.validTo !== void 0 && assertionIdentityKey(forkAssertion) === assertionIdentityKey(assertion) ? forkAssertion : assertion;
1604
+ return {
1605
+ assertion: endedAssertion,
1606
+ cause: classifyRetraction(assertion, deletedByKey, forkAssertion)
1607
+ };
1608
+ });
1153
1609
  }
1154
1610
  function byId(left, right) {
1155
1611
  return compareStrings(left.id, right.id);
@@ -1159,8 +1615,8 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
1159
1615
  const nodeKinds = getNodeKinds(graph);
1160
1616
  const edgeKinds = getEdgeKinds(graph);
1161
1617
  const [baseIdentity, forkIdentity] = await Promise.all([
1162
- storeRuntime(baseStore).readCurrentIdentityAssertions("state"),
1163
- storeRuntime(forkStore).readCurrentIdentityAssertions("state")
1618
+ storeRuntime(baseStore).readCurrentIdentityAssertions("archival"),
1619
+ storeRuntime(forkStore).readCurrentIdentityAssertions("archival")
1164
1620
  ]);
1165
1621
  const baseIdentityById = new Map(
1166
1622
  baseIdentity.map((assertion) => [assertion.id, assertion])
@@ -1252,10 +1708,10 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
1252
1708
  const fork = forkIdentityById.get(assertion.id);
1253
1709
  return fork === void 0 || assertionTruthKey(fork) !== assertionTruthKey(assertion);
1254
1710
  }).toSorted((left, right) => compareStrings(left.id, right.id));
1255
- const classifiedRetractions = await classifyRetractions(
1256
- forkStore,
1711
+ const classifiedRetractions = classifyRetractions(
1257
1712
  retractedAssertions,
1258
- deletedNodes
1713
+ deletedNodes,
1714
+ forkIdentityById
1259
1715
  );
1260
1716
  return {
1261
1717
  nodes: {
@@ -1281,7 +1737,7 @@ async function diffAgainstBase(baseStore, forkStore, captureForkState = true) {
1281
1737
  // unsupported id reuse as a typed conflict.
1282
1738
  new: forkIdentity.filter((assertion) => {
1283
1739
  const base = baseIdentityById.get(assertion.id);
1284
- return base === void 0 || assertionTruthKey(base) !== assertionTruthKey(assertion);
1740
+ return base === void 0 || assertionIdentityKey(base) !== assertionIdentityKey(assertion) && assertionTruthKey(base) !== assertionTruthKey(assertion);
1285
1741
  }).toSorted((left, right) => compareStrings(left.id, right.id)),
1286
1742
  retracted: classifiedRetractions
1287
1743
  },
@@ -1527,6 +1983,14 @@ var UNIQUE_KEY_PREFIX = "u";
1527
1983
  function isUniqueBucketKey(key) {
1528
1984
  return key.startsWith(`${UNIQUE_KEY_PREFIX}${KEY_PART_SEPARATOR}`);
1529
1985
  }
1986
+ function uniqueConstraintNameForBucket(key) {
1987
+ if (!isUniqueBucketKey(key)) {
1988
+ return void 0;
1989
+ }
1990
+ const constraintStart = UNIQUE_KEY_PREFIX.length + KEY_PART_SEPARATOR.length;
1991
+ const constraintEnd = key.indexOf(KEY_PART_SEPARATOR, constraintStart);
1992
+ return constraintEnd === -1 ? void 0 : key.slice(constraintStart, constraintEnd);
1993
+ }
1530
1994
  function constraintSignature(node, constraint) {
1531
1995
  const props = node;
1532
1996
  for (const field of constraint.fields) {
@@ -1874,7 +2338,10 @@ function canonicalizeCluster(cluster, members, policy, branchRank, weights, cano
1874
2338
  canonicalId,
1875
2339
  memberIds,
1876
2340
  kind,
1877
- branchOrigins
2341
+ branchOrigins,
2342
+ // The merge orchestrator replaces this with the witness selected from the
2343
+ // post-guard surviving edge set. Canonicalization never re-derives evidence.
2344
+ decisiveEdges: []
1878
2345
  };
1879
2346
  const windowSource = members.find(
1880
2347
  (member) => member.branchId === COMMITTED_TARGET_BRANCH && member.kind === kind && member.id === canonicalId
@@ -1886,10 +2353,59 @@ function canonicalizeCluster(cluster, members, policy, branchRank, weights, cano
1886
2353
  resolution,
1887
2354
  conflicts,
1888
2355
  ...windowSource.validFrom === void 0 ? {} : { validFrom: windowSource.validFrom },
1889
- ...windowSource.validTo === void 0 ? {} : { validTo: windowSource.validTo }
2356
+ ...windowSource.validTo === void 0 ? {} : { validTo: windowSource.validTo },
2357
+ ...windowSource.validFrom === void 0 ? {} : { endpointValidFrom: windowSource.validFrom },
2358
+ ...windowSource.validTo === void 0 ? {} : { endpointValidTo: windowSource.validTo }
1890
2359
  };
1891
2360
  }
1892
2361
 
2362
+ // src/graph-merge/union-find.ts
2363
+ var UnionFind = class {
2364
+ constructor(compare) {
2365
+ this.compare = compare;
2366
+ }
2367
+ compare;
2368
+ parent = /* @__PURE__ */ new Map();
2369
+ /** Adds `value` as its own singleton set if it is not already present. */
2370
+ add(value) {
2371
+ if (!this.parent.has(value)) {
2372
+ this.parent.set(value, value);
2373
+ }
2374
+ }
2375
+ /** Returns the representative of `value`'s set, compressing the path to it. */
2376
+ find(value) {
2377
+ this.add(value);
2378
+ let root = value;
2379
+ while (this.parent.get(root) !== root) {
2380
+ root = requireDefined(this.parent.get(root));
2381
+ }
2382
+ let cursor = value;
2383
+ while (cursor !== root) {
2384
+ const next = requireDefined(this.parent.get(cursor));
2385
+ this.parent.set(cursor, root);
2386
+ cursor = next;
2387
+ }
2388
+ return root;
2389
+ }
2390
+ /** Merges the sets of `left` and `right`; the `compare`-minimal root wins. */
2391
+ union(left, right) {
2392
+ const leftRoot = this.find(left);
2393
+ const rightRoot = this.find(right);
2394
+ if (leftRoot === rightRoot) {
2395
+ return;
2396
+ }
2397
+ if (this.compare(leftRoot, rightRoot) <= 0) {
2398
+ this.parent.set(rightRoot, leftRoot);
2399
+ } else {
2400
+ this.parent.set(leftRoot, rightRoot);
2401
+ }
2402
+ }
2403
+ /** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
2404
+ members() {
2405
+ return [...this.parent.keys()];
2406
+ }
2407
+ };
2408
+
1893
2409
  // src/graph-merge/closures.ts
1894
2410
  var SUB_CLASS_OF_META_EDGE = "subClassOf";
1895
2411
  var EQUIVALENCE_META_EDGES = /* @__PURE__ */ new Set([
@@ -1944,6 +2460,101 @@ function isReachable2(closure, from, to) {
1944
2460
  return isReachable(closure.closure, fromRep, toRep);
1945
2461
  }
1946
2462
 
2463
+ // src/graph-merge/evidence.ts
2464
+ function entityRef(key) {
2465
+ return { kind: kindOf(key), id: idOf(key) };
2466
+ }
2467
+ var SOURCE_KIND_ORDER = {
2468
+ block: 0,
2469
+ unique: 1,
2470
+ baseUnique: 2,
2471
+ baseIndex: 3,
2472
+ keyless: 4,
2473
+ retype: 5,
2474
+ custom: 6
2475
+ };
2476
+ function sourceKey(source) {
2477
+ switch (source.kind) {
2478
+ case "block":
2479
+ case "keyless":
2480
+ case "retype": {
2481
+ return JSON.stringify([SOURCE_KIND_ORDER[source.kind], source.sourceId]);
2482
+ }
2483
+ case "unique":
2484
+ case "baseUnique": {
2485
+ return JSON.stringify([
2486
+ SOURCE_KIND_ORDER[source.kind],
2487
+ source.sourceId,
2488
+ source.constraintName
2489
+ ]);
2490
+ }
2491
+ case "baseIndex": {
2492
+ return JSON.stringify([
2493
+ SOURCE_KIND_ORDER[source.kind],
2494
+ source.sourceId,
2495
+ source.indexName
2496
+ ]);
2497
+ }
2498
+ case "custom": {
2499
+ return JSON.stringify([
2500
+ SOURCE_KIND_ORDER.custom,
2501
+ source.sourceId,
2502
+ source.metadata === void 0 ? "" : canonicalValueKey(source.metadata)
2503
+ ]);
2504
+ }
2505
+ default: {
2506
+ const exhaustive = source;
2507
+ return exhaustive;
2508
+ }
2509
+ }
2510
+ }
2511
+ function compareMatchSources(left, right) {
2512
+ return compareStrings(sourceKey(left), sourceKey(right));
2513
+ }
2514
+ function compareEntityRefs(left, right) {
2515
+ return compareMergeKeys(
2516
+ mergeKey(left.kind, left.id),
2517
+ mergeKey(right.kind, right.id)
2518
+ );
2519
+ }
2520
+ function normalizeMatchSources(sources) {
2521
+ const byKey = /* @__PURE__ */ new Map();
2522
+ for (const source of sources) {
2523
+ byKey.set(sourceKey(source), source);
2524
+ }
2525
+ return [...byKey].sort(([, left], [, right]) => compareMatchSources(left, right)).map(([, source]) => source);
2526
+ }
2527
+ var DEFAULT_HYBRID_WEIGHT = 0.5;
2528
+ function describeMatchStrategy(strategy) {
2529
+ switch (strategy.kind) {
2530
+ case "fulltext": {
2531
+ return { kind: "fulltext", fields: [...strategy.fields] };
2532
+ }
2533
+ case "vector": {
2534
+ return { kind: "vector", fields: [strategy.field] };
2535
+ }
2536
+ case "hybrid": {
2537
+ const vector = strategy.weights?.vector ?? DEFAULT_HYBRID_WEIGHT;
2538
+ const fulltext = strategy.weights?.fulltext ?? DEFAULT_HYBRID_WEIGHT;
2539
+ const total = vector + fulltext;
2540
+ const weights = total === 0 ? { vector: DEFAULT_HYBRID_WEIGHT, fulltext: DEFAULT_HYBRID_WEIGHT } : { vector: vector / total, fulltext: fulltext / total };
2541
+ return { kind: "hybrid", fields: [...strategy.fields], weights };
2542
+ }
2543
+ case "custom": {
2544
+ return { kind: "custom" };
2545
+ }
2546
+ default: {
2547
+ const exhaustive = strategy;
2548
+ return exhaustive;
2549
+ }
2550
+ }
2551
+ }
2552
+ function compareMatchEvidence(left, right) {
2553
+ const byA = compareEntityRefs(left.a, right.a);
2554
+ if (byA !== 0) return byA;
2555
+ return compareEntityRefs(left.b, right.b);
2556
+ }
2557
+
1947
2558
  // src/graph-merge/similarity.ts
1948
2559
  var MIN_SCORE = 0;
1949
2560
  var MAX_SCORE = 1;
@@ -2073,18 +2684,18 @@ function cosineFromLookup(embeddings, leftText, rightText) {
2073
2684
  }
2074
2685
  return cosineSimilarity(leftVector, rightVector);
2075
2686
  }
2076
- var DEFAULT_HYBRID_WEIGHT = 0.5;
2687
+ var DEFAULT_HYBRID_WEIGHT2 = 0.5;
2077
2688
  function nonNegativeFinite(value) {
2078
2689
  return Number.isFinite(value) ? Math.max(0, value) : 0;
2079
2690
  }
2080
2691
  function normalizeHybridWeights(weights) {
2081
- const vector = nonNegativeFinite(weights?.vector ?? DEFAULT_HYBRID_WEIGHT);
2692
+ const vector = nonNegativeFinite(weights?.vector ?? DEFAULT_HYBRID_WEIGHT2);
2082
2693
  const fulltext = nonNegativeFinite(
2083
- weights?.fulltext ?? DEFAULT_HYBRID_WEIGHT
2694
+ weights?.fulltext ?? DEFAULT_HYBRID_WEIGHT2
2084
2695
  );
2085
2696
  const total = vector + fulltext;
2086
2697
  if (total === 0) {
2087
- return { vector: DEFAULT_HYBRID_WEIGHT, fulltext: DEFAULT_HYBRID_WEIGHT };
2698
+ return { vector: DEFAULT_HYBRID_WEIGHT2, fulltext: DEFAULT_HYBRID_WEIGHT2 };
2088
2699
  }
2089
2700
  return { vector: vector / total, fulltext: fulltext / total };
2090
2701
  }
@@ -2123,7 +2734,36 @@ function prepareNode(node, strategy) {
2123
2734
  function scorePrepared(left, right, strategy, ctx) {
2124
2735
  switch (strategy.kind) {
2125
2736
  case "custom": {
2126
- const raw = strategy.score(left.node, right.node);
2737
+ let raw;
2738
+ try {
2739
+ raw = strategy.score(left.node, right.node);
2740
+ } catch (error) {
2741
+ return err(
2742
+ new MatchEvidenceError("Custom similarity scorer failed.", {
2743
+ details: {
2744
+ kind: left.node.kind,
2745
+ sourceId: "similarity:custom",
2746
+ operation: "score"
2747
+ },
2748
+ cause: error
2749
+ })
2750
+ );
2751
+ }
2752
+ if (!Number.isFinite(raw)) {
2753
+ return err(
2754
+ new MatchEvidenceError(
2755
+ "Custom similarity scorer returned a non-finite score.",
2756
+ {
2757
+ details: {
2758
+ kind: left.node.kind,
2759
+ sourceId: "similarity:custom",
2760
+ operation: "score",
2761
+ score: String(raw)
2762
+ }
2763
+ }
2764
+ )
2765
+ );
2766
+ }
2127
2767
  return ok(clampScore(raw));
2128
2768
  }
2129
2769
  case "fulltext": {
@@ -2191,25 +2831,51 @@ function compareCandidateEdges(left, right) {
2191
2831
  function endpointKey(a, b) {
2192
2832
  return JSON.stringify([a, b]);
2193
2833
  }
2194
- function scoreCandidates(input, resolveConfig, ctx, ceilingPolicy, maxComparisonsPerKind) {
2834
+ function withSources(edge, sources) {
2835
+ return {
2836
+ ...edge,
2837
+ evidence: {
2838
+ ...edge.evidence,
2839
+ sources: normalizeMatchSources([...edge.evidence.sources, ...sources])
2840
+ }
2841
+ };
2842
+ }
2843
+ function scoreCandidates(input, resolveConfig, ctx, ceilingPolicy, maxComparisonsPerKind, diagnosticLimit = 0) {
2195
2844
  const { similarity, threshold } = resolveConfig;
2196
- const seen = /* @__PURE__ */ new Set();
2197
- const forced = [];
2845
+ const forcedByKey = /* @__PURE__ */ new Map();
2198
2846
  for (const edge of input.forcedEdges) {
2199
2847
  const key = endpointKey(edge.a, edge.b);
2200
- if (!seen.has(key)) {
2201
- seen.add(key);
2202
- forced.push(edge);
2203
- }
2848
+ const previous = forcedByKey.get(key);
2849
+ forcedByKey.set(
2850
+ key,
2851
+ previous === void 0 ? edge : withSources(previous, edge.evidence.sources)
2852
+ );
2204
2853
  }
2205
- const fuzzy = [];
2854
+ const fuzzyByKey = /* @__PURE__ */ new Map();
2206
2855
  for (const pair of input.pairs) {
2207
2856
  const key = endpointKey(pair.a, pair.b);
2208
- if (!seen.has(key)) {
2209
- seen.add(key);
2210
- fuzzy.push(pair);
2857
+ const forced2 = forcedByKey.get(key);
2858
+ if (forced2 !== void 0) {
2859
+ forcedByKey.set(key, withSources(forced2, pair.sources));
2860
+ continue;
2211
2861
  }
2862
+ const previous = fuzzyByKey.get(key);
2863
+ fuzzyByKey.set(
2864
+ key,
2865
+ previous === void 0 ? pair : {
2866
+ ...previous,
2867
+ sources: normalizeMatchSources([
2868
+ ...previous.sources,
2869
+ ...pair.sources
2870
+ ])
2871
+ }
2872
+ );
2212
2873
  }
2874
+ const forced = [...forcedByKey.values()];
2875
+ const fuzzy = [...fuzzyByKey.values()].sort((left, right) => {
2876
+ const byA = compareMergeKeys(left.a, right.a);
2877
+ return byA === 0 ? compareMergeKeys(left.b, right.b) : byA;
2878
+ });
2213
2879
  if (maxComparisonsPerKind !== void 0 && fuzzy.length > maxComparisonsPerKind) {
2214
2880
  if (ceilingPolicy === "error") {
2215
2881
  return err(
@@ -2236,23 +2902,81 @@ function scoreCandidates(input, resolveConfig, ctx, ceilingPolicy, maxComparison
2236
2902
  limit: maxComparisonsPerKind,
2237
2903
  message: `Skipped similarity for this kind: ${fuzzy.length} candidate pairs exceeded maxComparisonsPerKind ${maxComparisonsPerKind}; nodes will merge by id and exact unique match only.`
2238
2904
  }
2239
- ]
2905
+ ],
2906
+ diagnostics: [],
2907
+ diagnosticsTotal: 0
2240
2908
  });
2241
2909
  }
2242
2910
  const scorer = createPairScorer(similarity, ctx);
2243
2911
  const edges = [...forced];
2244
- for (const { left, right, a, b } of fuzzy) {
2912
+ const diagnostics = [];
2913
+ let diagnosticsTotal = 0;
2914
+ for (const { left, right, a, b, sources } of fuzzy) {
2245
2915
  const scored = scorer(a, left, b, right);
2246
2916
  if (isErr(scored)) {
2917
+ if (similarity.kind === "custom" && scored.error instanceof MatchEvidenceError) {
2918
+ return err(
2919
+ new MatchEvidenceError(scored.error.message, {
2920
+ cause: scored.error,
2921
+ details: {
2922
+ kind: left.kind,
2923
+ operation: "score",
2924
+ sourceIds: normalizeMatchSources(sources).map(
2925
+ (source) => source.sourceId
2926
+ ),
2927
+ endpoints: [entityRef(a), entityRef(b)]
2928
+ }
2929
+ })
2930
+ );
2931
+ }
2247
2932
  return err(scored.error);
2248
2933
  }
2249
- if (scored.data >= threshold) {
2250
- edges.push({ a, b, score: scored.data });
2934
+ if (!Number.isFinite(scored.data)) {
2935
+ return err(
2936
+ new MatchEvidenceError(
2937
+ "Similarity scorer returned a non-finite score.",
2938
+ {
2939
+ details: {
2940
+ kind: left.kind,
2941
+ operation: "score",
2942
+ sourceIds: normalizeMatchSources(sources).map(
2943
+ (source) => source.sourceId
2944
+ ),
2945
+ score: String(scored.data)
2946
+ }
2947
+ }
2948
+ )
2949
+ );
2950
+ }
2951
+ const noComparableValues = scored.data < 0;
2952
+ const publicScore = noComparableValues ? 0 : scored.data;
2953
+ const accepted = !noComparableValues && scored.data >= threshold;
2954
+ diagnosticsTotal += 1;
2955
+ const retainsDiagnostic = diagnostics.length < diagnosticLimit;
2956
+ if (accepted || retainsDiagnostic) {
2957
+ const evidence = {
2958
+ a: entityRef(a),
2959
+ b: entityRef(b),
2960
+ sources: normalizeMatchSources(sources),
2961
+ decision: "scored",
2962
+ strategy: describeMatchStrategy(similarity),
2963
+ score: publicScore,
2964
+ threshold
2965
+ };
2966
+ if (accepted) edges.push({ a, b, score: scored.data, evidence });
2967
+ if (!retainsDiagnostic) continue;
2968
+ diagnostics.push({
2969
+ evidence,
2970
+ scoreDecision: accepted ? "accepted" : "rejected",
2971
+ ...noComparableValues ? { reason: "noComparableValues" } : {}
2972
+ });
2251
2973
  }
2252
2974
  }
2253
2975
  return ok({
2254
2976
  edges: edges.sort((left, right) => compareCandidateEdges(left, right)),
2255
- warnings: []
2977
+ warnings: [],
2978
+ diagnostics,
2979
+ diagnosticsTotal
2256
2980
  });
2257
2981
  }
2258
2982
 
@@ -2354,13 +3078,14 @@ function weakestEdge(edges) {
2354
3078
  }
2355
3079
  function splitUntil(members, edges, isOffending) {
2356
3080
  let surviving = internalEdges(members, edges);
3081
+ const removedEdges = [];
2357
3082
  for (; ; ) {
2358
3083
  const components = buildComponents(surviving, members, true);
2359
3084
  const offending = components.filter(
2360
3085
  (component) => isOffending(component.members, surviving)
2361
3086
  );
2362
3087
  if (offending.length === 0) {
2363
- return components;
3088
+ return { clusters: components, survivingEdges: surviving, removedEdges };
2364
3089
  }
2365
3090
  let splittable;
2366
3091
  for (const component of offending) {
@@ -2372,11 +3097,16 @@ function splitUntil(members, edges, isOffending) {
2372
3097
  }
2373
3098
  if (splittable === void 0) {
2374
3099
  const offendingSet = new Set(offending);
2375
- return components.flatMap(
2376
- (component) => offendingSet.has(component) ? component.members.map((id) => ({ members: [id] })) : [component]
2377
- );
3100
+ return {
3101
+ clusters: components.flatMap(
3102
+ (component) => offendingSet.has(component) ? component.members.map((id) => ({ members: [id] })) : [component]
3103
+ ),
3104
+ survivingEdges: surviving,
3105
+ removedEdges
3106
+ };
2378
3107
  }
2379
3108
  const weakest = weakestEdge(splittable);
3109
+ removedEdges.push(weakest);
2380
3110
  surviving = surviving.filter((edge) => edge !== weakest);
2381
3111
  }
2382
3112
  }
@@ -2390,16 +3120,30 @@ function splitByDropWeakest(members, edges, maxDiameter) {
2390
3120
  function connectedComponents(candidateEdges, nodeIds) {
2391
3121
  return buildComponents(candidateEdges, nodeIds);
2392
3122
  }
2393
- function enforceDiameter(clusters, candidateEdges, clusterMaxDiameter) {
3123
+ function enforceDiameterWithEdges(clusters, candidateEdges, clusterMaxDiameter) {
2394
3124
  const guarded = [];
3125
+ let survivingEdges = [...candidateEdges];
3126
+ const excludedEdges = [];
2395
3127
  for (const component of clusters) {
2396
3128
  if (exceedsDiameter(component.members, candidateEdges, clusterMaxDiameter)) {
2397
- guarded.push(
2398
- ...splitByDropWeakest(
2399
- component.members,
2400
- candidateEdges,
2401
- clusterMaxDiameter
2402
- )
3129
+ const split = splitByDropWeakest(
3130
+ component.members,
3131
+ survivingEdges,
3132
+ clusterMaxDiameter
3133
+ );
3134
+ guarded.push(...split.clusters);
3135
+ const memberSet = new Set(component.members);
3136
+ survivingEdges = [
3137
+ ...survivingEdges.filter(
3138
+ (edge) => !(memberSet.has(edge.a) && memberSet.has(edge.b))
3139
+ ),
3140
+ ...split.survivingEdges
3141
+ ].sort((left, right) => compareCandidateEdges(left, right));
3142
+ excludedEdges.push(
3143
+ ...split.removedEdges.map((edge) => ({
3144
+ edge,
3145
+ reason: "diameter"
3146
+ }))
2403
3147
  );
2404
3148
  } else {
2405
3149
  guarded.push(component);
@@ -2411,7 +3155,7 @@ function enforceDiameter(clusters, candidateEdges, clusterMaxDiameter) {
2411
3155
  requireDefined(right.members[0])
2412
3156
  )
2413
3157
  );
2414
- return guarded;
3158
+ return { clusters: guarded, survivingEdges, excludedEdges };
2415
3159
  }
2416
3160
  function countBaseMembers(members, baseIds) {
2417
3161
  let count = 0;
@@ -2431,10 +3175,19 @@ function splitByBaseMultiplicity(members, edges, baseIds) {
2431
3175
  }
2432
3176
  function enforceBaseGuard(clusters, candidateEdges, baseIds) {
2433
3177
  if (baseIds.size === 0) {
2434
- return { clusters, events: [] };
3178
+ return {
3179
+ clusters,
3180
+ events: [],
3181
+ survivingEdges: [...candidateEdges].sort(
3182
+ (left, right) => compareCandidateEdges(left, right)
3183
+ ),
3184
+ excludedEdges: []
3185
+ };
2435
3186
  }
2436
3187
  const result = [];
2437
3188
  const events = [];
3189
+ let survivingEdges = [...candidateEdges];
3190
+ const excludedEdges = [];
2438
3191
  for (const cluster of clusters) {
2439
3192
  const clusterBaseIds = cluster.members.filter((id) => baseIds.has(id));
2440
3193
  if (clusterBaseIds.length < 2) {
@@ -2449,8 +3202,24 @@ function enforceBaseGuard(clusters, candidateEdges, baseIds) {
2449
3202
  (left, right) => compareMergeKeys(left, right)
2450
3203
  )
2451
3204
  });
2452
- result.push(
2453
- ...splitByBaseMultiplicity(cluster.members, candidateEdges, baseIds)
3205
+ const split = splitByBaseMultiplicity(
3206
+ cluster.members,
3207
+ survivingEdges,
3208
+ baseIds
3209
+ );
3210
+ result.push(...split.clusters);
3211
+ const memberSet = new Set(cluster.members);
3212
+ survivingEdges = [
3213
+ ...survivingEdges.filter(
3214
+ (edge) => !(memberSet.has(edge.a) && memberSet.has(edge.b))
3215
+ ),
3216
+ ...split.survivingEdges
3217
+ ].sort((left, right) => compareCandidateEdges(left, right));
3218
+ excludedEdges.push(
3219
+ ...split.removedEdges.map((edge) => ({
3220
+ edge,
3221
+ reason: "baseAmbiguity"
3222
+ }))
2454
3223
  );
2455
3224
  }
2456
3225
  result.sort(
@@ -2459,7 +3228,20 @@ function enforceBaseGuard(clusters, candidateEdges, baseIds) {
2459
3228
  requireDefined(right.members[0])
2460
3229
  )
2461
3230
  );
2462
- return { clusters: result, events };
3231
+ return { clusters: result, events, survivingEdges, excludedEdges };
3232
+ }
3233
+ function decisiveEdgesForCluster(cluster, survivingEdges) {
3234
+ const members = new Set(cluster.members);
3235
+ const forest = new UnionFind(compareMergeKeys);
3236
+ for (const member of cluster.members) forest.add(member);
3237
+ const decisive = [];
3238
+ for (const edge of internalEdges(cluster.members, survivingEdges)) {
3239
+ if (!members.has(edge.a) || !members.has(edge.b)) continue;
3240
+ if (forest.find(edge.a) === forest.find(edge.b)) continue;
3241
+ forest.union(edge.a, edge.b);
3242
+ decisive.push(edge.evidence);
3243
+ }
3244
+ return decisive;
2463
3245
  }
2464
3246
 
2465
3247
  // src/graph-merge/delete-modify.ts
@@ -2756,8 +3538,9 @@ var WINDOW_NOT_APPLICABLE_DROP_REASON = "window-not-applicable";
2756
3538
  function earliestEnd(claims) {
2757
3539
  let earliest;
2758
3540
  for (const claim of claims) {
2759
- if (earliest === void 0 || compareStrings(claim.validTo, earliest) < 0) {
2760
- earliest = claim.validTo;
3541
+ if (claim.change.kind !== "set") continue;
3542
+ if (earliest === void 0 || compareStrings(claim.change.validTo, earliest) < 0) {
3543
+ earliest = claim.change.validTo;
2761
3544
  }
2762
3545
  }
2763
3546
  return earliest;
@@ -2766,18 +3549,26 @@ function resolveEndClaims(claims, preferredBranchId) {
2766
3549
  const preferred = claims.filter(
2767
3550
  (claim) => claim.branchId === preferredBranchId
2768
3551
  );
2769
- return earliestEnd(preferred.length > 0 ? preferred : claims);
3552
+ const candidates = preferred.length > 0 ? preferred : claims;
3553
+ const earliest = earliestEnd(candidates);
3554
+ return earliest === void 0 ? candidates.some((claim) => claim.change.kind === "clear") ? { kind: "clear" } : void 0 : { kind: "set", validTo: earliest };
2770
3555
  }
2771
3556
  function claimingBranches(claims) {
2772
3557
  return [...new Set(claims.map((claim) => claim.branchId))].sort(
2773
3558
  (left, right) => compareStrings(left, right)
2774
3559
  );
2775
3560
  }
3561
+ function sameValidToChange(left, right) {
3562
+ if (left.kind !== right.kind) return false;
3563
+ if (left.kind === "clear") return true;
3564
+ return right.kind === "set" && left.validTo === right.validTo;
3565
+ }
2776
3566
  function classifyDelta(base, fork) {
3567
+ const lowerBoundMoved = fork.validFrom !== base.validFrom;
2777
3568
  const endCleared = fork.validTo === void 0 && base.validTo !== void 0;
2778
3569
  return {
2779
- applicableEnd: fork.validTo !== void 0 && fork.validTo !== base.validTo ? fork.validTo : void 0,
2780
- unapplicable: endCleared || fork.validFrom !== base.validFrom
3570
+ applicableEnd: fork.validTo !== void 0 && fork.validTo !== base.validTo ? { kind: "set", validTo: fork.validTo } : endCleared && !lowerBoundMoved ? { kind: "clear" } : void 0,
3571
+ unapplicable: lowerBoundMoved
2781
3572
  };
2782
3573
  }
2783
3574
  function resolvePopulation(entity, deltas, deletions, preferredBranchId, dropItem) {
@@ -2812,7 +3603,7 @@ function resolvePopulation(entity, deltas, deletions, preferredBranchId, dropIte
2812
3603
  continue;
2813
3604
  }
2814
3605
  if (applicableEnd !== void 0) {
2815
- claims.push({ branchId: delta.branchId, validTo: applicableEnd });
3606
+ claims.push({ branchId: delta.branchId, change: applicableEnd });
2816
3607
  }
2817
3608
  if (unapplicable && !reportedUnapplicable) {
2818
3609
  reportedUnapplicable = true;
@@ -2826,27 +3617,29 @@ function resolvePopulation(entity, deltas, deletions, preferredBranchId, dropIte
2826
3617
  entity,
2827
3618
  kind: first.kind,
2828
3619
  id: first.id,
2829
- validTo: targetEnd,
3620
+ ...targetEnd.kind === "set" ? { validTo: targetEnd.validTo } : { clearValidTo: true },
2830
3621
  claimedBy: claimingBranches(claims),
2831
3622
  precedence: VALIDITY_END_TARGET_PRECEDENCE
2832
3623
  });
2833
3624
  }
2834
3625
  continue;
2835
3626
  }
2836
- const resolved = earliestEnd(claims);
3627
+ const resolved = resolveEndClaims(claims, void 0);
2837
3628
  if (resolved === void 0) {
2838
3629
  continue;
2839
3630
  }
2840
3631
  ends.set(identity, resolved);
2841
3632
  credits.set(
2842
3633
  identity,
2843
- claimingBranches(claims.filter((claim) => claim.validTo === resolved))
3634
+ claimingBranches(
3635
+ claims.filter((claim) => sameValidToChange(claim.change, resolved))
3636
+ )
2844
3637
  );
2845
3638
  resolutions.push({
2846
3639
  entity,
2847
3640
  kind: first.kind,
2848
3641
  id: first.id,
2849
- validTo: resolved,
3642
+ ...resolved.kind === "set" ? { validTo: resolved.validTo } : { clearValidTo: true },
2850
3643
  claimedBy: claimingBranches(claims)
2851
3644
  });
2852
3645
  }
@@ -3070,17 +3863,19 @@ function foldEdgeSet(foldSet, context, branchRank, preferredBranchId) {
3070
3863
  const fromKind = kindOf(survivor.fromKey);
3071
3864
  const toId = idOf(survivor.toKey);
3072
3865
  const toKind = kindOf(survivor.toKey);
3073
- const preferredSurvivorEnd = survivor.staged.branchId === preferredBranchId ? survivor.staged.validTo : void 0;
3866
+ const preferredSurvivorEnd = survivor.staged.branchId === preferredBranchId ? typeof survivor.staged.validTo === "string" ? { kind: "set", validTo: survivor.staged.validTo } : survivor.staged.clearValidTo === true ? { kind: "clear" } : void 0 : void 0;
3074
3867
  const foldedEnd = preferredSurvivorEnd ?? resolveEndClaims(
3075
- foldSet.filter((edge) => edge.staged.validTo !== void 0).map((edge) => ({
3868
+ foldSet.filter(
3869
+ (edge) => edge.staged.validTo !== void 0 || edge.staged.clearValidTo === true
3870
+ ).map((edge) => ({
3076
3871
  branchId: edge.staged.branchId,
3077
- validTo: requireDefined(edge.staged.validTo)
3872
+ change: typeof edge.staged.validTo === "string" ? { kind: "set", validTo: edge.staged.validTo } : { kind: "clear" }
3078
3873
  })),
3079
3874
  preferredBranchId
3080
3875
  );
3081
3876
  const window = {
3082
3877
  ...survivor.staged.validFrom === void 0 ? {} : { validFrom: survivor.staged.validFrom },
3083
- ...foldedEnd === void 0 ? {} : { validTo: foldedEnd }
3878
+ ...foldedEnd?.kind === "set" ? { validTo: foldedEnd.validTo } : foldedEnd?.kind === "clear" ? { clearValidTo: true } : {}
3084
3879
  };
3085
3880
  const contentKeys = new Set(foldSet.map((edge) => edge.dedupeKey));
3086
3881
  if (contentKeys.size === 1) {
@@ -3225,7 +4020,10 @@ var mergeOptionsScalarSchema = z.object({
3225
4020
  provenance: z.boolean().default(MERGE_OPTION_DEFAULTS.provenance),
3226
4021
  persistProvenance: z.boolean().default(MERGE_OPTION_DEFAULTS.persistProvenance),
3227
4022
  maxComparisonsPerKind: z.number().int({ message: "maxComparisonsPerKind must be an integer" }).min(0, { message: "maxComparisonsPerKind must be >= 0" }).optional(),
3228
- clusterMaxDiameter: z.number().positive({ message: "clusterMaxDiameter must be positive" }).optional()
4023
+ clusterMaxDiameter: z.number().positive({ message: "clusterMaxDiameter must be positive" }).optional(),
4024
+ candidateDiagnostics: z.object({
4025
+ limit: z.number().int({ message: "candidateDiagnostics.limit must be an integer" }).min(0, { message: "candidateDiagnostics.limit must be >= 0" })
4026
+ }).strict().optional()
3229
4027
  });
3230
4028
  function validatePropertyConflictPolicy(policy, label) {
3231
4029
  if (typeof policy === "string" && !propertyConflictPolicySchema.safeParse(policy).success) {
@@ -3288,7 +4086,8 @@ function normalizeMergeOptions(options = {}) {
3288
4086
  provenance: options.provenance,
3289
4087
  persistProvenance: options.persistProvenance,
3290
4088
  ...options.maxComparisonsPerKind === void 0 ? {} : { maxComparisonsPerKind: options.maxComparisonsPerKind },
3291
- ...options.clusterMaxDiameter === void 0 ? {} : { clusterMaxDiameter: options.clusterMaxDiameter }
4089
+ ...options.clusterMaxDiameter === void 0 ? {} : { clusterMaxDiameter: options.clusterMaxDiameter },
4090
+ ...options.candidateDiagnostics === void 0 ? {} : { candidateDiagnostics: options.candidateDiagnostics }
3292
4091
  });
3293
4092
  const onPropertyConflict = validatePropertyConflictPolicy(
3294
4093
  options.onPropertyConflict ?? MERGE_OPTION_DEFAULTS.onPropertyConflict,
@@ -3319,10 +4118,625 @@ function normalizeMergeOptions(options = {}) {
3319
4118
  ...options.target === void 0 ? {} : { target: options.target },
3320
4119
  ...scalar.maxComparisonsPerKind === void 0 ? {} : { maxComparisonsPerKind: scalar.maxComparisonsPerKind },
3321
4120
  ...scalar.clusterMaxDiameter === void 0 ? {} : { clusterMaxDiameter: scalar.clusterMaxDiameter },
4121
+ ...scalar.candidateDiagnostics === void 0 ? {} : { candidateDiagnostics: scalar.candidateDiagnostics },
3322
4122
  ...options.branchOrder === void 0 ? {} : { branchOrder: options.branchOrder },
3323
4123
  ...provenanceWeights === void 0 ? {} : { provenanceWeights }
3324
4124
  };
3325
4125
  }
4126
+ var MERGE_PLAN_FORMAT_VERSION = 1;
4127
+ var MERGE_PLAN_DIGEST_ALGORITHM = "sha256";
4128
+ var nonEmptyStringSchema = z.string().min(1);
4129
+ var finiteNumberSchema = z.number();
4130
+ var nonNegativeIntegerSchema = finiteNumberSchema.int().nonnegative();
4131
+ var jsonObjectSchema = z.record(z.string(), z.json());
4132
+ var mergePlanEntityRefSchema = z.object({ kind: nonEmptyStringSchema, id: nonEmptyStringSchema }).strict();
4133
+ var mergePlanSchemaFenceSchema = z.object({
4134
+ managed: z.boolean(),
4135
+ version: nonNegativeIntegerSchema,
4136
+ hash: nonEmptyStringSchema
4137
+ }).strict();
4138
+ var mergePlanRevisionFenceSchema = z.object({
4139
+ origin: nonEmptyStringSchema,
4140
+ revision: nonEmptyStringSchema.nullable()
4141
+ }).strict();
4142
+ var mergePlanTargetFenceSchema = z.object({
4143
+ graphId: nonEmptyStringSchema,
4144
+ schema: mergePlanSchemaFenceSchema,
4145
+ revision: mergePlanRevisionFenceSchema
4146
+ }).strict();
4147
+ var branchAnchorSchema = z.object({
4148
+ branchId: nonEmptyStringSchema,
4149
+ baseVersion: nonEmptyStringSchema
4150
+ }).strict();
4151
+ var snapshotAnchorsSchema = z.object({
4152
+ kind: z.literal("snapshot"),
4153
+ base: z.object({
4154
+ graphId: nonEmptyStringSchema,
4155
+ baseVersion: nonEmptyStringSchema
4156
+ }).strict(),
4157
+ branches: z.array(branchAnchorSchema)
4158
+ }).strict();
4159
+ var incrementalAnchorsSchema = z.object({
4160
+ kind: z.literal("incremental"),
4161
+ forkPoint: z.object({
4162
+ graphId: nonEmptyStringSchema,
4163
+ baseVersion: nonEmptyStringSchema,
4164
+ schema: mergePlanSchemaFenceSchema
4165
+ }).strict(),
4166
+ branches: z.array(branchAnchorSchema)
4167
+ }).strict();
4168
+ var mergePlanAnchorsSchema = z.discriminatedUnion("kind", [
4169
+ snapshotAnchorsSchema,
4170
+ incrementalAnchorsSchema
4171
+ ]);
4172
+ var nodeUpsertSchema = z.object({
4173
+ kind: nonEmptyStringSchema,
4174
+ id: nonEmptyStringSchema,
4175
+ setProps: jsonObjectSchema,
4176
+ unsetProps: z.array(nonEmptyStringSchema),
4177
+ validFrom: nonEmptyStringSchema.optional(),
4178
+ validTo: nonEmptyStringSchema.optional()
4179
+ }).strict();
4180
+ var edgeUpsertSchema = z.object({
4181
+ kind: nonEmptyStringSchema,
4182
+ id: nonEmptyStringSchema,
4183
+ from: mergePlanEntityRefSchema,
4184
+ to: mergePlanEntityRefSchema,
4185
+ setProps: jsonObjectSchema,
4186
+ unsetProps: z.array(nonEmptyStringSchema),
4187
+ validFrom: nonEmptyStringSchema.optional(),
4188
+ validTo: nonEmptyStringSchema.optional()
4189
+ }).strict();
4190
+ var mergePlanIdentityAssertionSchema = z.object({
4191
+ id: nonEmptyStringSchema,
4192
+ relation: z.enum(["same", "different"]),
4193
+ a: mergePlanEntityRefSchema,
4194
+ b: mergePlanEntityRefSchema,
4195
+ validFrom: nonEmptyStringSchema,
4196
+ validTo: nonEmptyStringSchema.optional(),
4197
+ endedBy: mergePlanEntityRefSchema.optional()
4198
+ }).strict();
4199
+ var mergePlanWritesSchema = z.object({
4200
+ nodeDeletes: z.array(mergePlanEntityRefSchema),
4201
+ nodeUpserts: z.array(nodeUpsertSchema),
4202
+ edgeDeletes: z.array(mergePlanEntityRefSchema),
4203
+ edgeUpserts: z.array(edgeUpsertSchema),
4204
+ identityAssertions: z.array(mergePlanIdentityAssertionSchema),
4205
+ identityRetractions: z.array(mergePlanIdentityAssertionSchema)
4206
+ }).strict();
4207
+ var proposedRoleSchema = z.object({
4208
+ upserts: nonNegativeIntegerSchema,
4209
+ deletions: nonNegativeIntegerSchema
4210
+ }).strict();
4211
+ var mergePlanProposedSummarySchema = z.object({
4212
+ nodes: proposedRoleSchema,
4213
+ edges: proposedRoleSchema,
4214
+ identity: z.object({
4215
+ assertions: nonNegativeIntegerSchema,
4216
+ retractions: nonNegativeIntegerSchema
4217
+ }).strict()
4218
+ }).strict();
4219
+ var mergePlanGuardsSchema = z.object({
4220
+ canonicalMappings: z.array(
4221
+ z.object({
4222
+ member: mergePlanEntityRefSchema,
4223
+ canonical: mergePlanEntityRefSchema
4224
+ }).strict()
4225
+ ),
4226
+ retypes: z.array(
4227
+ z.object({
4228
+ entity: mergePlanEntityRefSchema,
4229
+ toKind: nonEmptyStringSchema
4230
+ }).strict()
4231
+ ),
4232
+ deletedNodes: z.array(mergePlanEntityRefSchema),
4233
+ incremental: z.object({
4234
+ tombstoneResurrection: z.literal("refuse"),
4235
+ lossyUpdates: z.literal("refuse"),
4236
+ edgeIdentity: z.literal("preserve")
4237
+ }).strict().optional()
4238
+ }).strict();
4239
+ var matchSourceSchema = z.discriminatedUnion("kind", [
4240
+ z.object({ kind: z.literal("block"), sourceId: nonEmptyStringSchema }).strict(),
4241
+ z.object({
4242
+ kind: z.literal("unique"),
4243
+ sourceId: nonEmptyStringSchema,
4244
+ constraintName: nonEmptyStringSchema
4245
+ }).strict(),
4246
+ z.object({
4247
+ kind: z.literal("baseUnique"),
4248
+ sourceId: nonEmptyStringSchema,
4249
+ constraintName: nonEmptyStringSchema
4250
+ }).strict(),
4251
+ z.object({
4252
+ kind: z.literal("baseIndex"),
4253
+ sourceId: nonEmptyStringSchema,
4254
+ indexName: nonEmptyStringSchema
4255
+ }).strict(),
4256
+ z.object({ kind: z.literal("keyless"), sourceId: nonEmptyStringSchema }).strict(),
4257
+ z.object({ kind: z.literal("retype"), sourceId: nonEmptyStringSchema }).strict(),
4258
+ z.object({
4259
+ kind: z.literal("custom"),
4260
+ sourceId: nonEmptyStringSchema,
4261
+ metadata: z.json().optional()
4262
+ }).strict()
4263
+ ]);
4264
+ var similarityStrategySchema = z.discriminatedUnion("kind", [
4265
+ z.object({
4266
+ kind: z.literal("fulltext"),
4267
+ fields: z.array(nonEmptyStringSchema)
4268
+ }).strict(),
4269
+ z.object({
4270
+ kind: z.literal("vector"),
4271
+ fields: z.array(nonEmptyStringSchema)
4272
+ }).strict(),
4273
+ z.object({
4274
+ kind: z.literal("hybrid"),
4275
+ fields: z.array(nonEmptyStringSchema),
4276
+ weights: z.object({ vector: finiteNumberSchema, fulltext: finiteNumberSchema }).strict()
4277
+ }).strict(),
4278
+ z.object({ kind: z.literal("custom") }).strict()
4279
+ ]);
4280
+ var definitionalEvidenceSchema = z.object({
4281
+ a: mergePlanEntityRefSchema,
4282
+ b: mergePlanEntityRefSchema,
4283
+ sources: z.array(matchSourceSchema),
4284
+ decision: z.literal("definitional")
4285
+ }).strict();
4286
+ var scoredEvidenceSchema = z.object({
4287
+ a: mergePlanEntityRefSchema,
4288
+ b: mergePlanEntityRefSchema,
4289
+ sources: z.array(matchSourceSchema),
4290
+ decision: z.literal("scored"),
4291
+ strategy: similarityStrategySchema,
4292
+ score: finiteNumberSchema,
4293
+ threshold: finiteNumberSchema
4294
+ }).strict();
4295
+ var mergePlanMatchEvidenceSchema = z.discriminatedUnion("decision", [
4296
+ definitionalEvidenceSchema,
4297
+ scoredEvidenceSchema
4298
+ ]);
4299
+ var entityResolutionSchema = z.object({
4300
+ canonicalId: nonEmptyStringSchema,
4301
+ memberIds: z.array(nonEmptyStringSchema),
4302
+ kind: nonEmptyStringSchema,
4303
+ branchOrigins: z.array(nonEmptyStringSchema),
4304
+ decisiveEdges: z.array(mergePlanMatchEvidenceSchema)
4305
+ }).strict();
4306
+ var diagnosticsSchema = z.object({
4307
+ entries: z.array(
4308
+ z.object({
4309
+ evidence: mergePlanMatchEvidenceSchema,
4310
+ scoreDecision: z.enum(["accepted", "rejected"]),
4311
+ reason: z.literal("noComparableValues").optional(),
4312
+ clusterDisposition: z.union([
4313
+ z.literal("retained"),
4314
+ z.object({
4315
+ kind: z.literal("excluded"),
4316
+ reason: z.enum(["diameter", "baseAmbiguity"])
4317
+ }).strict()
4318
+ ]).optional()
4319
+ }).strict()
4320
+ ),
4321
+ total: nonNegativeIntegerSchema,
4322
+ limit: nonNegativeIntegerSchema,
4323
+ truncated: z.boolean()
4324
+ }).strict();
4325
+ var mergePlanReviewSchema = z.object({
4326
+ resolutions: z.array(entityResolutionSchema),
4327
+ conflicts: z.array(
4328
+ z.object({
4329
+ entityId: nonEmptyStringSchema,
4330
+ kind: nonEmptyStringSchema,
4331
+ property: nonEmptyStringSchema,
4332
+ values: z.array(
4333
+ z.object({ branchId: nonEmptyStringSchema, value: z.json() }).strict()
4334
+ ),
4335
+ resolution: z.json()
4336
+ }).strict()
4337
+ ),
4338
+ deleteModifyConflicts: z.array(
4339
+ z.object({
4340
+ entityId: nonEmptyStringSchema,
4341
+ kind: nonEmptyStringSchema,
4342
+ deletedBy: nonEmptyStringSchema,
4343
+ modifiedBy: nonEmptyStringSchema,
4344
+ resolution: z.enum(["deleteWins", "modifyWins", "flag"])
4345
+ }).strict()
4346
+ ),
4347
+ typeReconciliations: z.array(
4348
+ z.object({
4349
+ entityId: nonEmptyStringSchema,
4350
+ fromTypes: z.array(nonEmptyStringSchema),
4351
+ toType: nonEmptyStringSchema,
4352
+ decisiveEdges: z.array(mergePlanMatchEvidenceSchema).optional()
4353
+ }).strict()
4354
+ ),
4355
+ dropped: z.array(
4356
+ z.discriminatedUnion("kind", [
4357
+ z.object({
4358
+ kind: z.literal("node"),
4359
+ id: nonEmptyStringSchema,
4360
+ reason: nonEmptyStringSchema
4361
+ }).strict(),
4362
+ z.object({
4363
+ kind: z.literal("edge"),
4364
+ id: nonEmptyStringSchema,
4365
+ reason: nonEmptyStringSchema
4366
+ }).strict(),
4367
+ z.object({
4368
+ kind: z.literal("identity"),
4369
+ id: nonEmptyStringSchema,
4370
+ reason: nonEmptyStringSchema
4371
+ }).strict()
4372
+ ])
4373
+ ),
4374
+ validityEnds: z.array(
4375
+ z.object({
4376
+ entity: z.enum(["node", "edge"]),
4377
+ kind: nonEmptyStringSchema,
4378
+ id: nonEmptyStringSchema,
4379
+ validTo: nonEmptyStringSchema,
4380
+ claimedBy: z.array(nonEmptyStringSchema),
4381
+ precedence: z.literal("target").optional()
4382
+ }).strict()
4383
+ ),
4384
+ baseAmbiguities: z.array(
4385
+ z.object({
4386
+ baseIds: z.array(mergePlanEntityRefSchema),
4387
+ memberIds: z.array(mergePlanEntityRefSchema)
4388
+ }).strict()
4389
+ ),
4390
+ provenanceRecords: z.array(
4391
+ z.object({
4392
+ role: z.enum(["node", "edge"]),
4393
+ canonicalId: nonEmptyStringSchema,
4394
+ canonicalKind: nonEmptyStringSchema,
4395
+ branchId: nonEmptyStringSchema,
4396
+ sourceId: nonEmptyStringSchema
4397
+ }).strict()
4398
+ ),
4399
+ warnings: z.array(z.string()),
4400
+ diagnostics: diagnosticsSchema.optional()
4401
+ }).strict();
4402
+ var mergePlanDigestSchema = z.object({
4403
+ algorithm: z.literal(MERGE_PLAN_DIGEST_ALGORITHM),
4404
+ value: z.string().regex(/^[\da-f]{64}$/)
4405
+ }).strict();
4406
+ var mergePlanArtifactV1BaseSchema = z.object({
4407
+ formatVersion: z.literal(MERGE_PLAN_FORMAT_VERSION),
4408
+ digest: mergePlanDigestSchema,
4409
+ mode: z.enum(["snapshot", "incremental"]),
4410
+ target: mergePlanTargetFenceSchema,
4411
+ anchors: mergePlanAnchorsSchema,
4412
+ proposed: mergePlanProposedSummarySchema,
4413
+ writes: mergePlanWritesSchema,
4414
+ guards: mergePlanGuardsSchema,
4415
+ review: mergePlanReviewSchema,
4416
+ provenance: z.object({ includeInReport: z.boolean(), persist: z.boolean() }).strict()
4417
+ }).strict();
4418
+ var mergePlanArtifactV1InputBaseSchema = mergePlanArtifactV1BaseSchema.omit({
4419
+ digest: true
4420
+ });
4421
+ function addSemanticIssues(artifact, ctx) {
4422
+ if (artifact.mode !== artifact.anchors.kind) {
4423
+ ctx.addIssue({
4424
+ code: "custom",
4425
+ path: ["anchors", "kind"],
4426
+ message: `Expected ${artifact.mode} anchors for a ${artifact.mode} plan.`
4427
+ });
4428
+ }
4429
+ if (artifact.mode === "incremental" !== (artifact.guards.incremental !== void 0)) {
4430
+ ctx.addIssue({
4431
+ code: "custom",
4432
+ path: ["guards", "incremental"],
4433
+ message: "Incremental write guards must be present exactly for incremental plans."
4434
+ });
4435
+ }
4436
+ const expectedCounts = {
4437
+ nodeUpserts: artifact.writes.nodeUpserts.length,
4438
+ nodeDeletions: artifact.writes.nodeDeletes.length,
4439
+ edgeUpserts: artifact.writes.edgeUpserts.length,
4440
+ edgeDeletions: artifact.writes.edgeDeletes.length,
4441
+ assertions: artifact.writes.identityAssertions.length,
4442
+ retractions: artifact.writes.identityRetractions.length
4443
+ };
4444
+ const receivedCounts = {
4445
+ nodeUpserts: artifact.proposed.nodes.upserts,
4446
+ nodeDeletions: artifact.proposed.nodes.deletions,
4447
+ edgeUpserts: artifact.proposed.edges.upserts,
4448
+ edgeDeletions: artifact.proposed.edges.deletions,
4449
+ assertions: artifact.proposed.identity.assertions,
4450
+ retractions: artifact.proposed.identity.retractions
4451
+ };
4452
+ if (JSON.stringify(expectedCounts) !== JSON.stringify(receivedCounts)) {
4453
+ ctx.addIssue({
4454
+ code: "custom",
4455
+ path: ["proposed"],
4456
+ message: "Proposed counts must equal the resolved write-set counts."
4457
+ });
4458
+ }
4459
+ addOperationIssues(
4460
+ artifact.writes.nodeDeletes,
4461
+ artifact.writes.nodeUpserts,
4462
+ "node",
4463
+ ctx
4464
+ );
4465
+ addOperationIssues(
4466
+ artifact.writes.edgeDeletes,
4467
+ artifact.writes.edgeUpserts,
4468
+ "edge",
4469
+ ctx
4470
+ );
4471
+ for (const [index, upsert] of artifact.writes.nodeUpserts.entries()) {
4472
+ addPropertyPatchIssues(upsert, ["writes", "nodeUpserts", index], ctx);
4473
+ }
4474
+ for (const [index, upsert] of artifact.writes.edgeUpserts.entries()) {
4475
+ addPropertyPatchIssues(upsert, ["writes", "edgeUpserts", index], ctx);
4476
+ }
4477
+ const canonicalByMember = /* @__PURE__ */ new Map();
4478
+ let invalidCanonicalMappings = false;
4479
+ for (const mapping of artifact.guards.canonicalMappings) {
4480
+ const member = entityKey(mapping.member);
4481
+ const canonical = entityKey(mapping.canonical);
4482
+ if (canonicalByMember.has(member)) invalidCanonicalMappings = true;
4483
+ canonicalByMember.set(member, canonical);
4484
+ }
4485
+ for (const canonical of canonicalByMember.values()) {
4486
+ if (canonicalByMember.get(canonical) !== canonical) {
4487
+ invalidCanonicalMappings = true;
4488
+ }
4489
+ }
4490
+ if (invalidCanonicalMappings) {
4491
+ ctx.addIssue({
4492
+ code: "custom",
4493
+ path: ["guards", "canonicalMappings"],
4494
+ message: "Canonical mappings must name each member once and include a self-mapped canonical root."
4495
+ });
4496
+ }
4497
+ const diagnostics = artifact.review.diagnostics;
4498
+ if (diagnostics !== void 0) {
4499
+ const expectedRetained = Math.min(diagnostics.total, diagnostics.limit);
4500
+ if (diagnostics.entries.length !== expectedRetained || diagnostics.truncated !== diagnostics.total > diagnostics.entries.length) {
4501
+ ctx.addIssue({
4502
+ code: "custom",
4503
+ path: ["review", "diagnostics"],
4504
+ message: "Diagnostic retention and truncation metadata must exactly match total and limit."
4505
+ });
4506
+ }
4507
+ for (const [index, diagnostic] of diagnostics.entries.entries()) {
4508
+ addEvidenceIssues(
4509
+ diagnostic.evidence,
4510
+ ["review", "diagnostics", "entries", index, "evidence"],
4511
+ ctx
4512
+ );
4513
+ const accepted = diagnostic.evidence.decision === "definitional" || diagnostic.evidence.score >= diagnostic.evidence.threshold && diagnostic.reason === void 0;
4514
+ if (diagnostic.scoreDecision !== (accepted ? "accepted" : "rejected") || diagnostic.evidence.decision === "scored" && diagnostic.reason === "noComparableValues" && diagnostic.evidence.score !== 0 || diagnostic.evidence.decision === "definitional" && (diagnostic.reason !== void 0 || typeof diagnostic.clusterDisposition !== "object") || diagnostic.scoreDecision === "rejected" && diagnostic.clusterDisposition !== void 0) {
4515
+ ctx.addIssue({
4516
+ code: "custom",
4517
+ path: ["review", "diagnostics", "entries", index],
4518
+ message: "Diagnostic decision, reason, and cluster disposition must agree with its score evidence."
4519
+ });
4520
+ }
4521
+ }
4522
+ }
4523
+ for (const [index, resolution] of artifact.review.resolutions.entries()) {
4524
+ addResolutionEvidenceIssues(artifact, resolution, index, ctx);
4525
+ }
4526
+ for (const [
4527
+ reconciliationIndex,
4528
+ reconciliation
4529
+ ] of artifact.review.typeReconciliations.entries()) {
4530
+ for (const [edgeIndex, evidence] of (reconciliation.decisiveEdges ?? []).entries()) {
4531
+ addEvidenceIssues(
4532
+ evidence,
4533
+ [
4534
+ "review",
4535
+ "typeReconciliations",
4536
+ reconciliationIndex,
4537
+ "decisiveEdges",
4538
+ edgeIndex
4539
+ ],
4540
+ ctx
4541
+ );
4542
+ }
4543
+ }
4544
+ }
4545
+ function addResolutionEvidenceIssues(artifact, resolution, resolutionIndex, ctx) {
4546
+ const retypedKinds = new Map(
4547
+ artifact.guards.retypes.map((retype) => [
4548
+ entityKey(retype.entity),
4549
+ retype.toKind
4550
+ ])
4551
+ );
4552
+ const clusterMembers = artifact.guards.canonicalMappings.filter((mapping) => {
4553
+ const canonicalKind = retypedKinds.get(entityKey(mapping.canonical)) ?? mapping.canonical.kind;
4554
+ return mapping.canonical.id === resolution.canonicalId && canonicalKind === resolution.kind;
4555
+ }).map((mapping) => mapping.member);
4556
+ const uniqueMembers = new Map(
4557
+ clusterMembers.map((member) => [entityKey(member), member])
4558
+ );
4559
+ const memberIds = new Set(
4560
+ [...uniqueMembers.values()].map((member) => member.id)
4561
+ );
4562
+ const declaredMemberIds = new Set(resolution.memberIds);
4563
+ if (uniqueMembers.size < 2 || declaredMemberIds.size !== resolution.memberIds.length || memberIds.size !== declaredMemberIds.size || [...memberIds].some((id) => !declaredMemberIds.has(id)) || resolution.decisiveEdges.length !== uniqueMembers.size - 1) {
4564
+ ctx.addIssue({
4565
+ code: "custom",
4566
+ path: ["review", "resolutions", resolutionIndex],
4567
+ message: "A resolution must name its complete guarded cluster and carry exactly N-1 decisive edges."
4568
+ });
4569
+ return;
4570
+ }
4571
+ const parent = new Map([...uniqueMembers.keys()].map((key) => [key, key]));
4572
+ const find = (key) => {
4573
+ const next = parent.get(key);
4574
+ if (next === void 0 || next === key) return key;
4575
+ const root = find(next);
4576
+ parent.set(key, root);
4577
+ return root;
4578
+ };
4579
+ let invalidTree = false;
4580
+ for (const [edgeIndex, evidence] of resolution.decisiveEdges.entries()) {
4581
+ addEvidenceIssues(
4582
+ evidence,
4583
+ ["review", "resolutions", resolutionIndex, "decisiveEdges", edgeIndex],
4584
+ ctx
4585
+ );
4586
+ const a = entityKey(evidence.a);
4587
+ const b = entityKey(evidence.b);
4588
+ if (!uniqueMembers.has(a) || !uniqueMembers.has(b)) {
4589
+ invalidTree = true;
4590
+ continue;
4591
+ }
4592
+ const rootA = find(a);
4593
+ const rootB = find(b);
4594
+ if (rootA === rootB) invalidTree = true;
4595
+ else parent.set(rootB, rootA);
4596
+ }
4597
+ if (invalidTree || new Set([...uniqueMembers.keys()].map((key) => find(key))).size !== 1) {
4598
+ ctx.addIssue({
4599
+ code: "custom",
4600
+ path: ["review", "resolutions", resolutionIndex, "decisiveEdges"],
4601
+ message: "Decisive edges must be an acyclic connected witness over the guarded cluster."
4602
+ });
4603
+ }
4604
+ }
4605
+ function addEvidenceIssues(evidence, path, ctx) {
4606
+ const sourcesCanonical = evidence.sources.every((source, index) => {
4607
+ const previous = evidence.sources[index - 1];
4608
+ return previous === void 0 || compareMatchSources(previous, source) < 0;
4609
+ });
4610
+ const invalidStrategy = evidence.decision === "scored" && (evidence.strategy.kind === "vector" && evidence.strategy.fields.length !== 1 || evidence.strategy.kind === "hybrid" && (evidence.strategy.weights.vector < 0 || evidence.strategy.weights.vector > 1 || evidence.strategy.weights.fulltext < 0 || evidence.strategy.weights.fulltext > 1 || Math.abs(
4611
+ evidence.strategy.weights.vector + evidence.strategy.weights.fulltext - 1
4612
+ ) > Number.EPSILON));
4613
+ if (compareEntityRefs(evidence.a, evidence.b) >= 0 || evidence.sources.length === 0 || !sourcesCanonical || evidence.decision === "scored" && (evidence.score < 0 || evidence.score > 1 || evidence.threshold < 0 || evidence.threshold > 1) || invalidStrategy) {
4614
+ ctx.addIssue({
4615
+ code: "custom",
4616
+ path: [...path],
4617
+ message: "Evidence endpoints and sources must be canonical, and scored values must be within [0, 1]."
4618
+ });
4619
+ }
4620
+ }
4621
+ function addOperationIssues(deletions, upserts, role, ctx) {
4622
+ const deletionKeys = deletions.map((entry) => entityKey(entry));
4623
+ const upsertKeys = upserts.map((entry) => entityKey(entry));
4624
+ if (new Set(deletionKeys).size !== deletionKeys.length || new Set(upsertKeys).size !== upsertKeys.length) {
4625
+ ctx.addIssue({
4626
+ code: "custom",
4627
+ path: ["writes"],
4628
+ message: `The ${role} write set must not contain duplicate identities.`
4629
+ });
4630
+ }
4631
+ const deleted = new Set(deletionKeys);
4632
+ if (upsertKeys.some((key) => deleted.has(key))) {
4633
+ ctx.addIssue({
4634
+ code: "custom",
4635
+ path: ["writes"],
4636
+ message: `The same ${role} identity cannot be both deleted and upserted.`
4637
+ });
4638
+ }
4639
+ }
4640
+ function addPropertyPatchIssues(patch, path, ctx) {
4641
+ const unset = new Set(patch.unsetProps);
4642
+ if (unset.size !== patch.unsetProps.length || Object.keys(patch.setProps).some((key) => unset.has(key))) {
4643
+ ctx.addIssue({
4644
+ code: "custom",
4645
+ path: [...path],
4646
+ message: "unsetProps must be duplicate-free and disjoint from setProps keys."
4647
+ });
4648
+ }
4649
+ }
4650
+ function entityKey(entity) {
4651
+ return JSON.stringify([entity.kind, entity.id]);
4652
+ }
4653
+ var mergePlanArtifactV1InputSchema = mergePlanArtifactV1InputBaseSchema.superRefine(
4654
+ (artifact, ctx) => addSemanticIssues(artifact, ctx)
4655
+ );
4656
+ var mergePlanArtifactV1Schema = mergePlanArtifactV1BaseSchema.superRefine(
4657
+ (artifact, ctx) => addSemanticIssues(artifact, ctx)
4658
+ );
4659
+
4660
+ // src/graph-merge/plan-canonical.ts
4661
+ function canonicalMergePlanJson(artifact) {
4662
+ const digestless = "digest" in artifact ? omitDigest(artifact) : artifact;
4663
+ return JSON.stringify(digestless, sortedReplacer);
4664
+ }
4665
+ async function computeMergePlanDigest(artifact) {
4666
+ return sha256Hex(canonicalMergePlanJson(artifact), 32);
4667
+ }
4668
+ async function finalizeMergePlanArtifact(artifact) {
4669
+ const finalized = {
4670
+ ...artifact,
4671
+ digest: {
4672
+ algorithm: MERGE_PLAN_DIGEST_ALGORITHM,
4673
+ value: await computeMergePlanDigest(artifact)
4674
+ }
4675
+ };
4676
+ const canonical = JSON.parse(
4677
+ JSON.stringify(finalized, sortedReplacer)
4678
+ );
4679
+ return canonical;
4680
+ }
4681
+ function omitDigest(artifact) {
4682
+ const { digest: _digest, ...digestless } = artifact;
4683
+ return digestless;
4684
+ }
4685
+
4686
+ // src/graph-merge/plan-wire.ts
4687
+ function parseMergePlanArtifact(input) {
4688
+ const version = readFormatVersion(input);
4689
+ if (version !== MERGE_PLAN_FORMAT_VERSION) {
4690
+ return {
4691
+ success: false,
4692
+ error: { kind: "unsupported-version", received: version }
4693
+ };
4694
+ }
4695
+ const parsed = mergePlanArtifactV1Schema.safeParse(input);
4696
+ if (!parsed.success) {
4697
+ return {
4698
+ success: false,
4699
+ error: { kind: "malformed", issues: parsed.error.issues }
4700
+ };
4701
+ }
4702
+ return {
4703
+ success: true,
4704
+ artifact: parsed.data
4705
+ };
4706
+ }
4707
+ async function verifyMergePlanDigest(artifact) {
4708
+ const expected = await computeMergePlanDigest(artifact);
4709
+ const received = artifact.digest.value;
4710
+ return expected === received ? { valid: true } : { valid: false, expected, received };
4711
+ }
4712
+ async function validateMergePlanArtifact(input) {
4713
+ const parsed = parseMergePlanArtifact(input);
4714
+ if (!parsed.success) return parsed;
4715
+ const digest = await verifyMergePlanDigest(parsed.artifact);
4716
+ if (!digest.valid) {
4717
+ return {
4718
+ success: false,
4719
+ error: {
4720
+ kind: "digest-mismatch",
4721
+ expected: digest.expected,
4722
+ received: digest.received
4723
+ }
4724
+ };
4725
+ }
4726
+ return parsed;
4727
+ }
4728
+ async function constructMergePlanArtifact(input) {
4729
+ const validatedInput = mergePlanArtifactV1InputSchema.parse(input);
4730
+ return finalizeMergePlanArtifact(
4731
+ validatedInput
4732
+ );
4733
+ }
4734
+ function readFormatVersion(input) {
4735
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
4736
+ return void 0;
4737
+ }
4738
+ return Reflect.get(input, "formatVersion");
4739
+ }
3326
4740
  var PROVENANCE_KIND = "Provenance";
3327
4741
  var PROVENANCE_OWNER_KIND = "ProvenanceOwner";
3328
4742
  var Provenance = defineNode(PROVENANCE_KIND, {
@@ -3673,10 +5087,10 @@ async function readProvenance(store, query = {}) {
3673
5087
  }
3674
5088
 
3675
5089
  // src/graph-merge/sources.ts
3676
- function orderEndpoints(left, right) {
5090
+ function orderEndpoints(left, right, sources) {
3677
5091
  const leftKey = mergeKeyOf(left);
3678
5092
  const rightKey = mergeKeyOf(right);
3679
- return compareMergeKeys(leftKey, rightKey) <= 0 ? { a: leftKey, b: rightKey, left, right } : { a: rightKey, b: leftKey, left: right, right: left };
5093
+ return compareMergeKeys(leftKey, rightKey) <= 0 ? { a: leftKey, b: rightKey, left, right, sources } : { a: rightKey, b: leftKey, left: right, right: left, sources };
3680
5094
  }
3681
5095
  function sortMembersById(members) {
3682
5096
  return [...members].sort((left, right) => compareStrings(left.id, right.id));
@@ -3719,7 +5133,8 @@ function windowedPairs(members, config) {
3719
5133
  pairs.push(
3720
5134
  orderEndpoints(
3721
5135
  requireDefined(keyed[index]).node,
3722
- requireDefined(keyed[index_]).node
5136
+ requireDefined(keyed[index_]).node,
5137
+ [{ kind: "keyless", sourceId: "keyless" }]
3723
5138
  )
3724
5139
  );
3725
5140
  }
@@ -3733,8 +5148,22 @@ function pairsFromBlocks(blocks, keyless) {
3733
5148
  continue;
3734
5149
  }
3735
5150
  const members = sortMembersById(blocks.get(bucketKey) ?? []);
3736
- if (keyless !== void 0 && bucketKey === UNBLOCKED_BUCKET_KEY) {
3737
- pairs.push(...windowedPairs(members, keyless));
5151
+ if (bucketKey === UNBLOCKED_BUCKET_KEY) {
5152
+ if (keyless !== void 0) {
5153
+ pairs.push(...windowedPairs(members, keyless));
5154
+ continue;
5155
+ }
5156
+ for (let index = 0; index < members.length; index += 1) {
5157
+ for (let index_ = index + 1; index_ < members.length; index_ += 1) {
5158
+ pairs.push(
5159
+ orderEndpoints(
5160
+ requireDefined(members[index]),
5161
+ requireDefined(members[index_]),
5162
+ [{ kind: "keyless", sourceId: "keyless" }]
5163
+ )
5164
+ );
5165
+ }
5166
+ }
3738
5167
  continue;
3739
5168
  }
3740
5169
  for (let index = 0; index < members.length; index += 1) {
@@ -3742,7 +5171,8 @@ function pairsFromBlocks(blocks, keyless) {
3742
5171
  pairs.push(
3743
5172
  orderEndpoints(
3744
5173
  requireDefined(members[index]),
3745
- requireDefined(members[index_])
5174
+ requireDefined(members[index_]),
5175
+ [{ kind: "block", sourceId: "exactKey" }]
3746
5176
  )
3747
5177
  );
3748
5178
  }
@@ -3757,13 +5187,37 @@ function forcedEdgesFromBlocks(blocks) {
3757
5187
  continue;
3758
5188
  }
3759
5189
  const members = sortMembersById(blocks.get(bucketKey) ?? []);
5190
+ const constraintName = uniqueConstraintNameForBucket(bucketKey);
5191
+ if (constraintName === void 0) {
5192
+ throw new CandidateSourceError(
5193
+ "Unique candidate bucket has no constraint name.",
5194
+ {
5195
+ details: {
5196
+ operation: "candidateEvidence",
5197
+ sourceId: "unique",
5198
+ kind: members[0]?.kind ?? "unknown"
5199
+ }
5200
+ }
5201
+ );
5202
+ }
3760
5203
  for (let index = 0; index < members.length; index += 1) {
3761
5204
  for (let index_ = index + 1; index_ < members.length; index_ += 1) {
3762
5205
  const { a, b } = orderEndpoints(
3763
5206
  requireDefined(members[index]),
3764
- requireDefined(members[index_])
5207
+ requireDefined(members[index_]),
5208
+ []
3765
5209
  );
3766
- edges.push({ a, b, score: FORCED_MATCH_SCORE });
5210
+ edges.push({
5211
+ a,
5212
+ b,
5213
+ score: FORCED_MATCH_SCORE,
5214
+ evidence: {
5215
+ a: entityRef(a),
5216
+ b: entityRef(b),
5217
+ sources: [{ kind: "unique", sourceId: "unique", constraintName }],
5218
+ decision: "definitional"
5219
+ }
5220
+ });
3767
5221
  }
3768
5222
  }
3769
5223
  }
@@ -3797,7 +5251,13 @@ function ontologyRetypeEdges(stagedIdentities, isRetypeCompatible) {
3797
5251
  edges.push({
3798
5252
  a: requireDefined(members[0]),
3799
5253
  b: requireDefined(members[index]),
3800
- score: FORCED_MATCH_SCORE
5254
+ score: FORCED_MATCH_SCORE,
5255
+ evidence: {
5256
+ a: entityRef(requireDefined(members[0])),
5257
+ b: entityRef(requireDefined(members[index])),
5258
+ sources: [{ kind: "retype", sourceId: "retype" }],
5259
+ decision: "definitional"
5260
+ }
3801
5261
  });
3802
5262
  }
3803
5263
  }
@@ -3864,9 +5324,16 @@ var baseUniqueSource = {
3864
5324
  async generate(scope) {
3865
5325
  const { kind, nodes, uniqueConstraints, store } = scope;
3866
5326
  if (nodes === void 0 || uniqueConstraints === void 0 || store === void 0) {
3867
- throw new MergeError(
5327
+ throw new CandidateSourceError(
3868
5328
  "baseUniqueSource requires nodes, uniqueConstraints, and store in the source scope.",
3869
- { details: { kind, source: "baseUnique" } }
5329
+ {
5330
+ details: {
5331
+ kind,
5332
+ source: "baseUnique",
5333
+ sourceId: "baseUnique",
5334
+ operation: "generate"
5335
+ }
5336
+ }
3870
5337
  );
3871
5338
  }
3872
5339
  const collection = store.nodes[kind];
@@ -3884,14 +5351,23 @@ var baseUniqueSource = {
3884
5351
  try {
3885
5352
  return await collection.bulkFindByConstraint(constraint.name, items);
3886
5353
  } catch (error) {
3887
- throw new MergeError(
5354
+ throw new CandidateSourceError(
3888
5355
  `baseUniqueSource: new-vs-base lookup for constraint "${constraint.name}" on kind "${kind}" failed \u2014 a staged node's props could not be validated against the kind's schema for constraint-key computation.`,
3889
- { details: { kind, constraint: constraint.name }, cause: error }
5356
+ {
5357
+ details: {
5358
+ kind,
5359
+ sourceId: "baseUnique",
5360
+ operation: "lookupConstraint",
5361
+ constraint: constraint.name
5362
+ },
5363
+ cause: error
5364
+ }
3890
5365
  );
3891
5366
  }
3892
5367
  })
3893
5368
  );
3894
- for (const matches of matchesByConstraint) {
5369
+ for (const [constraintIndex, matches] of matchesByConstraint.entries()) {
5370
+ const constraint = requireDefined(constraints[constraintIndex]);
3895
5371
  for (const [index, node] of nodes.entries()) {
3896
5372
  const base = matches[index];
3897
5373
  if (base === void 0) {
@@ -3905,14 +5381,32 @@ var baseUniqueSource = {
3905
5381
  origin: "base",
3906
5382
  id: base.id,
3907
5383
  kind: base.kind,
3908
- props: nodeProps(base)
5384
+ props: nodeProps(base),
5385
+ ...base.meta.validFrom === void 0 ? {} : { validFrom: base.meta.validFrom },
5386
+ ...base.meta.validTo === void 0 ? {} : { validTo: base.meta.validTo }
3909
5387
  });
3910
5388
  }
3911
5389
  if (stagedKey === baseKey) {
3912
5390
  continue;
3913
5391
  }
3914
5392
  const { a, b } = compareMergeKeys(stagedKey, baseKey) <= 0 ? { a: stagedKey, b: baseKey } : { a: baseKey, b: stagedKey };
3915
- forcedEdges.push({ a, b, score: FORCED_MATCH_SCORE });
5393
+ forcedEdges.push({
5394
+ a,
5395
+ b,
5396
+ score: FORCED_MATCH_SCORE,
5397
+ evidence: {
5398
+ a: entityRef(a),
5399
+ b: entityRef(b),
5400
+ sources: [
5401
+ {
5402
+ kind: "baseUnique",
5403
+ sourceId: "baseUnique",
5404
+ constraintName: constraint.name
5405
+ }
5406
+ ],
5407
+ decision: "definitional"
5408
+ }
5409
+ });
3916
5410
  }
3917
5411
  }
3918
5412
  return {
@@ -3932,9 +5426,16 @@ var baseKeySource = {
3932
5426
  return { pairs: [], forcedEdges: [], baseMembers: [] };
3933
5427
  }
3934
5428
  if (nodes === void 0 || store === void 0) {
3935
- throw new MergeError(
5429
+ throw new CandidateSourceError(
3936
5430
  "baseKeySource requires nodes and store in the source scope.",
3937
- { details: { kind, source: "baseKey" } }
5431
+ {
5432
+ details: {
5433
+ kind,
5434
+ source: "baseKey",
5435
+ sourceId: "baseKey",
5436
+ operation: "generate"
5437
+ }
5438
+ }
3938
5439
  );
3939
5440
  }
3940
5441
  const collection = store.nodes[kind];
@@ -3946,9 +5447,17 @@ var baseKeySource = {
3946
5447
  try {
3947
5448
  matchesByNode = await collection.bulkFindByIndex(blockIndex, items);
3948
5449
  } catch (error) {
3949
- throw new MergeError(
5450
+ throw new CandidateSourceError(
3950
5451
  `baseKeySource: new-vs-base lookup on index "${blockIndex}" for kind "${kind}" failed \u2014 the index is undeclared or a staged node's props are incompatible with its indexed fields.`,
3951
- { details: { kind, index: blockIndex }, cause: error }
5452
+ {
5453
+ details: {
5454
+ kind,
5455
+ sourceId: "baseKey",
5456
+ operation: "lookupIndex",
5457
+ index: blockIndex
5458
+ },
5459
+ cause: error
5460
+ }
3952
5461
  );
3953
5462
  }
3954
5463
  const pairs = [];
@@ -3963,13 +5472,19 @@ var baseKeySource = {
3963
5472
  origin: "base",
3964
5473
  id: base.id,
3965
5474
  kind: base.kind,
3966
- props: nodeProps(base)
5475
+ props: nodeProps(base),
5476
+ ...base.meta.validFrom === void 0 ? {} : { validFrom: base.meta.validFrom },
5477
+ ...base.meta.validTo === void 0 ? {} : { validTo: base.meta.validTo }
3967
5478
  });
3968
5479
  }
3969
5480
  if (stagedKey === baseKey) {
3970
5481
  continue;
3971
5482
  }
3972
- pairs.push(orderEndpoints(stagedNode, base));
5483
+ pairs.push(
5484
+ orderEndpoints(stagedNode, base, [
5485
+ { kind: "baseIndex", sourceId: "baseKey", indexName: blockIndex }
5486
+ ])
5487
+ );
3973
5488
  }
3974
5489
  }
3975
5490
  return {
@@ -4321,6 +5836,8 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4321
5836
  const allEdges = [];
4322
5837
  const warnings = [];
4323
5838
  const baseMembers = [];
5839
+ const diagnostics = [];
5840
+ let diagnosticsTotal = 0;
4324
5841
  const byKind = newNodesByKind(staging);
4325
5842
  const sources = useBaseSources ? [...CANDIDATE_SOURCES, baseUniqueSource, baseKeySource] : CANDIDATE_SOURCES;
4326
5843
  const baseStore = target;
@@ -4329,7 +5846,13 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4329
5846
  async ([kind, items]) => {
4330
5847
  const resolveConfig = options.resolve[kind];
4331
5848
  if (resolveConfig === void 0) {
4332
- return ok({ edges: [], warnings: [], baseMembers: [] });
5849
+ return ok({
5850
+ edges: [],
5851
+ warnings: [],
5852
+ baseMembers: [],
5853
+ diagnostics: [],
5854
+ diagnosticsTotal: 0
5855
+ });
4333
5856
  }
4334
5857
  const nodes = items.map((staged) => asNode(staged));
4335
5858
  const uniqueConstraints = uniqueConstraintsFor(
@@ -4370,7 +5893,8 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4370
5893
  resolveConfig,
4371
5894
  kindCtx,
4372
5895
  options.onComparisonCeiling,
4373
- options.maxComparisonsPerKind
5896
+ options.maxComparisonsPerKind,
5897
+ options.candidateDiagnostics?.limit ?? 0
4374
5898
  );
4375
5899
  if (isErr(scored)) {
4376
5900
  return err(scored.error);
@@ -4380,7 +5904,9 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4380
5904
  warnings: scored.data.warnings.map(
4381
5905
  (warning) => `[${kind}] ${warning.message}`
4382
5906
  ),
4383
- baseMembers: kindBaseMembers
5907
+ baseMembers: kindBaseMembers,
5908
+ diagnostics: scored.data.diagnostics,
5909
+ diagnosticsTotal: scored.data.diagnosticsTotal
4384
5910
  });
4385
5911
  }
4386
5912
  )
@@ -4392,6 +5918,14 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4392
5918
  allEdges.push(...result.data.edges);
4393
5919
  warnings.push(...result.data.warnings);
4394
5920
  baseMembers.push(...result.data.baseMembers);
5921
+ if (options.candidateDiagnostics !== void 0) {
5922
+ diagnostics.push(...result.data.diagnostics);
5923
+ diagnostics.sort(
5924
+ (left, right) => compareMatchEvidence(left.evidence, right.evidence)
5925
+ );
5926
+ diagnostics.splice(options.candidateDiagnostics.limit);
5927
+ }
5928
+ diagnosticsTotal += result.data.diagnosticsTotal;
4395
5929
  }
4396
5930
  return ok({
4397
5931
  // The ONE shared `(a, b)` edge comparator (id-first `(kind, id)` order), so this
@@ -4402,7 +5936,9 @@ async function generateAllCandidates(target, staging, options, introspectionKind
4402
5936
  // base members would tie under a bare-id compare).
4403
5937
  baseMembers: baseMembers.sort(
4404
5938
  (left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right))
4405
- )
5939
+ ),
5940
+ diagnostics,
5941
+ diagnosticsTotal
4406
5942
  });
4407
5943
  }
4408
5944
  function clusterMembersFor(cluster, newNodesById, baseMembersById) {
@@ -4426,7 +5962,9 @@ function clusterMembersFor(cluster, newNodesById, baseMembersById) {
4426
5962
  id: base.id,
4427
5963
  kind: base.kind,
4428
5964
  branchId: BASE_PROVENANCE_BRANCH,
4429
- props: base.props
5965
+ props: base.props,
5966
+ ...base.validFrom === void 0 ? {} : { validFrom: base.validFrom },
5967
+ ...base.validTo === void 0 ? {} : { validTo: base.validTo }
4430
5968
  });
4431
5969
  }
4432
5970
  }
@@ -4459,7 +5997,7 @@ function buildStagedEdges(staging, modifiedEdges, edgeValidityEnds, edgeDeletion
4459
5997
  for (const item of modifiedEdges) {
4460
5998
  const identity = mergeKeyOf(item.edge);
4461
5999
  stagedIdentities.add(identity);
4462
- const validTo = edgeValidityEnds.get(identity);
6000
+ const validToChange = edgeValidityEnds.get(identity);
4463
6001
  staged.push({
4464
6002
  id: item.edge.id,
4465
6003
  kind: item.edge.kind,
@@ -4471,13 +6009,13 @@ function buildStagedEdges(staging, modifiedEdges, edgeValidityEnds, edgeDeletion
4471
6009
  props: item.edge.forkProps,
4472
6010
  baseProps: item.edge.baseProps,
4473
6011
  branchId: item.branchId,
4474
- ...validTo === void 0 ? {} : { validTo }
6012
+ ...validToChange?.kind === "set" ? { validTo: validToChange.validTo } : validToChange?.kind === "clear" ? { clearValidTo: true } : {}
4475
6013
  });
4476
6014
  }
4477
6015
  for (const item of staging.windowedEdges) {
4478
6016
  const identity = mergeKeyOf(item.edge);
4479
- const validTo = edgeValidityEnds.get(identity);
4480
- if (validTo === void 0 || stagedIdentities.has(identity) || edgeDeletions.has(identity)) {
6017
+ const validToChange = edgeValidityEnds.get(identity);
6018
+ if (validToChange === void 0 || stagedIdentities.has(identity) || edgeDeletions.has(identity)) {
4481
6019
  continue;
4482
6020
  }
4483
6021
  stagedIdentities.add(identity);
@@ -4493,7 +6031,7 @@ function buildStagedEdges(staging, modifiedEdges, edgeValidityEnds, edgeDeletion
4493
6031
  props: item.edge.props,
4494
6032
  baseProps: item.edge.baseProps,
4495
6033
  branchId: item.branchId,
4496
- validTo
6034
+ ...validToChange.kind === "set" ? { validTo: validToChange.validTo } : { clearValidTo: true }
4497
6035
  });
4498
6036
  }
4499
6037
  return { edges: staged, windowOnlyCarried };
@@ -4541,7 +6079,7 @@ function buildProvenanceIndex(records) {
4541
6079
  };
4542
6080
  }
4543
6081
  var EMPTY_WEIGHTS = /* @__PURE__ */ new Map();
4544
- function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, options, branchRank, subClassClosure, identityContext, storedIdentityRowsById, targetPeers, preferredBranchId) {
6082
+ function buildInternalMergePlan(staging, candidateEdges, candidateWarnings, candidateDiagnostics, candidateDiagnosticsTotal, baseMembers, options, branchRank, subClassClosure, identityContext, storedIdentityRowsById, targetPeers, preferredBranchId) {
4545
6083
  const identity = planIdentityChanges(staging, storedIdentityRowsById);
4546
6084
  const provenanceRecords = [];
4547
6085
  const recordedContributions = /* @__PURE__ */ new Set();
@@ -4593,7 +6131,56 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4593
6131
  clusterEdges,
4594
6132
  baseIds
4595
6133
  );
4596
- const clusters = options.clusterMaxDiameter === void 0 ? guard.clusters : enforceDiameter(guard.clusters, clusterEdges, options.clusterMaxDiameter);
6134
+ const diameterGuard = options.clusterMaxDiameter === void 0 ? {
6135
+ clusters: guard.clusters,
6136
+ survivingEdges: guard.survivingEdges,
6137
+ excludedEdges: []
6138
+ } : enforceDiameterWithEdges(
6139
+ guard.clusters,
6140
+ clusterEdges,
6141
+ options.clusterMaxDiameter
6142
+ );
6143
+ const clusters = diameterGuard.clusters;
6144
+ const baseSurvivingEdges = new Set(guard.survivingEdges);
6145
+ const survivingEdges = diameterGuard.survivingEdges.filter(
6146
+ (edge) => baseSurvivingEdges.has(edge)
6147
+ );
6148
+ const excludedByEndpoints = /* @__PURE__ */ new Map();
6149
+ for (const excluded of [
6150
+ ...guard.excludedEdges,
6151
+ ...diameterGuard.excludedEdges
6152
+ ]) {
6153
+ excludedByEndpoints.set(
6154
+ JSON.stringify([excluded.edge.a, excluded.edge.b]),
6155
+ excluded.reason
6156
+ );
6157
+ }
6158
+ const diagnosticsWithDisposition = candidateDiagnostics.map((diagnostic) => {
6159
+ if (diagnostic.evidence.decision === "definitional") return diagnostic;
6160
+ if (diagnostic.scoreDecision === "rejected") return diagnostic;
6161
+ const a = mergeKey(diagnostic.evidence.a.kind, diagnostic.evidence.a.id);
6162
+ const b = mergeKey(diagnostic.evidence.b.kind, diagnostic.evidence.b.id);
6163
+ const reason = excludedByEndpoints.get(JSON.stringify([a, b]));
6164
+ return {
6165
+ ...diagnostic,
6166
+ clusterDisposition: reason === void 0 ? "retained" : { kind: "excluded", reason }
6167
+ };
6168
+ });
6169
+ const definitionalExclusions = [
6170
+ ...guard.excludedEdges,
6171
+ ...diameterGuard.excludedEdges
6172
+ ].filter((excluded) => excluded.edge.evidence.decision === "definitional").map((excluded) => ({
6173
+ evidence: excluded.edge.evidence,
6174
+ scoreDecision: "accepted",
6175
+ clusterDisposition: {
6176
+ kind: "excluded",
6177
+ reason: excluded.reason
6178
+ }
6179
+ }));
6180
+ const retainedDiagnostics = [
6181
+ ...diagnosticsWithDisposition,
6182
+ ...definitionalExclusions
6183
+ ].sort((left, right) => compareMatchEvidence(left.evidence, right.evidence));
4597
6184
  const baseAmbiguities = guard.events.map((event) => ({
4598
6185
  baseIds: event.baseIds.map((key) => ({ kind: kindOf(key), id: idOf(key) })),
4599
6186
  memberIds: event.memberIds.map((key) => ({
@@ -4603,6 +6190,7 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4603
6190
  }));
4604
6191
  const canonicalEntities = [];
4605
6192
  const resolutions = [];
6193
+ const clusterByCanonicalIdentity = /* @__PURE__ */ new Map();
4606
6194
  const propertyConflicts = [];
4607
6195
  const reconcileInputs = [];
4608
6196
  for (const cluster of clusters) {
@@ -4622,6 +6210,10 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4622
6210
  preferredBranchId
4623
6211
  );
4624
6212
  canonicalEntities.push(entity);
6213
+ clusterByCanonicalIdentity.set(
6214
+ mergeKey(entity.kind, entity.canonicalId),
6215
+ cluster
6216
+ );
4625
6217
  reconcileInputs.push({
4626
6218
  canonicalId: mergeKey(entity.kind, entity.canonicalId),
4627
6219
  memberKinds: members.map((member) => member.kind)
@@ -4630,7 +6222,10 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4630
6222
  cluster.members.map((member) => idOf(member))
4631
6223
  );
4632
6224
  if (distinctMergedIds.size > 1) {
4633
- resolutions.push(entity.resolution);
6225
+ resolutions.push({
6226
+ ...entity.resolution,
6227
+ decisiveEdges: decisiveEdgesForCluster(cluster, survivingEdges)
6228
+ });
4634
6229
  }
4635
6230
  for (const conflict of entity.conflicts) {
4636
6231
  propertyConflicts.push(conflict);
@@ -4761,6 +6356,21 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4761
6356
  subClassClosure,
4762
6357
  options.reconcileTypes
4763
6358
  );
6359
+ const reconciledClusterByEntityId = /* @__PURE__ */ new Map();
6360
+ for (const identity2 of reconciliation.retypeMap.keys()) {
6361
+ const entityId = idOf(identity2);
6362
+ const cluster = clusterByCanonicalIdentity.get(identity2);
6363
+ if (cluster !== void 0 && !reconciledClusterByEntityId.has(entityId)) {
6364
+ reconciledClusterByEntityId.set(entityId, cluster);
6365
+ }
6366
+ }
6367
+ const typeReconciliations = reconciliation.reconciliations.map((item) => {
6368
+ const cluster = reconciledClusterByEntityId.get(item.entityId);
6369
+ return cluster === void 0 ? item : {
6370
+ ...item,
6371
+ decisiveEdges: decisiveEdgesForCluster(cluster, survivingEdges)
6372
+ };
6373
+ });
4764
6374
  const entityByIdentity = /* @__PURE__ */ new Map();
4765
6375
  for (const entity of canonicalEntities) {
4766
6376
  entityByIdentity.set(mergeKey(entity.kind, entity.canonicalId), entity);
@@ -4769,11 +6379,24 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4769
6379
  clusters,
4770
6380
  (cluster) => pickClusterCanonical(cluster, entityByIdentity)
4771
6381
  );
6382
+ const canonicalEndpointWindows = /* @__PURE__ */ new Map();
6383
+ for (const entity of canonicalEntities) {
6384
+ if (entity.endpointValidFrom === void 0 && entity.endpointValidTo === void 0) {
6385
+ continue;
6386
+ }
6387
+ const sourceKey2 = mergeKey(entity.kind, entity.canonicalId);
6388
+ const finalKind = reconciliation.retypeMap.get(sourceKey2) ?? entity.kind;
6389
+ canonicalEndpointWindows.set(mergeKey(finalKind, entity.canonicalId), {
6390
+ ...entity.endpointValidFrom === void 0 ? {} : { validFrom: entity.endpointValidFrom },
6391
+ ...entity.endpointValidTo === void 0 ? {} : { validTo: entity.endpointValidTo }
6392
+ });
6393
+ }
4772
6394
  const identityRemap = remapIdentityAssertionEndpoints(
4773
6395
  identity.assertions,
4774
6396
  canonicalOf,
4775
6397
  reconciliation.retypeMap,
4776
- storedIdentityRowsById
6398
+ storedIdentityRowsById,
6399
+ canonicalEndpointWindows
4777
6400
  );
4778
6401
  assertIdentityEndpointsNotDeleted(
4779
6402
  identityRemap.assertions,
@@ -4897,7 +6520,7 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4897
6520
  mergeKey(right.kind, right.entityId)
4898
6521
  )
4899
6522
  ),
4900
- typeReconciliations: reconciliation.reconciliations,
6523
+ typeReconciliations,
4901
6524
  dropped,
4902
6525
  baseAmbiguities: baseAmbiguities.sort(
4903
6526
  (left, right) => compareMergeKeys(
@@ -4912,7 +6535,18 @@ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, opti
4912
6535
  )
4913
6536
  ),
4914
6537
  provenanceRecords,
4915
- warnings: candidateWarnings,
6538
+ warnings: [...candidateWarnings, ...identityRemap.warnings],
6539
+ ...options.candidateDiagnostics === void 0 ? {} : {
6540
+ candidateDiagnostics: {
6541
+ entries: retainedDiagnostics.slice(
6542
+ 0,
6543
+ options.candidateDiagnostics.limit
6544
+ ),
6545
+ total: candidateDiagnosticsTotal + definitionalExclusions.length,
6546
+ limit: options.candidateDiagnostics.limit,
6547
+ truncated: candidateDiagnosticsTotal + definitionalExclusions.length > options.candidateDiagnostics.limit
6548
+ }
6549
+ },
4916
6550
  identityAssertions: identityRemap.assertions,
4917
6551
  identityRetractions: survivingRetractions
4918
6552
  };
@@ -4963,13 +6597,13 @@ function plannedNodeWrites(plan) {
4963
6597
  continue;
4964
6598
  }
4965
6599
  written.add(identity);
4966
- const validTo = plan.nodeValidityEnds.get(identity);
6600
+ const validToChange = plan.nodeValidityEnds.get(identity);
4967
6601
  writes.push({
4968
6602
  identity,
4969
6603
  kind: modification.node.kind,
4970
6604
  id: modification.node.id,
4971
6605
  modification: modification.node,
4972
- ...validTo === void 0 ? {} : { validTo }
6606
+ ...validToChange?.kind === "set" ? { validTo: validToChange.validTo } : validToChange?.kind === "clear" ? { clearValidTo: true } : {}
4973
6607
  });
4974
6608
  }
4975
6609
  for (const entity of plan.canonicalEntities) {
@@ -4980,7 +6614,7 @@ function plannedNodeWrites(plan) {
4980
6614
  const kind = plan.retypeMap.get(sourceIdentity) ?? entity.kind;
4981
6615
  const modification = modificationsByIdentity.get(sourceIdentity);
4982
6616
  written.add(sourceIdentity);
4983
- const validTo = entity.validTo ?? plan.nodeValidityEnds.get(sourceIdentity);
6617
+ const validToChange = plan.nodeValidityEnds.get(sourceIdentity);
4984
6618
  writes.push({
4985
6619
  identity: mergeKey(kind, entity.canonicalId),
4986
6620
  kind,
@@ -4988,7 +6622,7 @@ function plannedNodeWrites(plan) {
4988
6622
  entity,
4989
6623
  ...modification === void 0 ? {} : { modification },
4990
6624
  ...entity.validFrom === void 0 ? {} : { validFrom: entity.validFrom },
4991
- ...validTo === void 0 ? {} : { validTo }
6625
+ ...typeof entity.validTo === "string" ? { validTo: entity.validTo } : validToChange?.kind === "set" ? { validTo: validToChange.validTo } : validToChange?.kind === "clear" ? { clearValidTo: true } : {}
4992
6626
  });
4993
6627
  }
4994
6628
  for (const identity of [...plan.nodeValidityEnds.keys()].sort(
@@ -4998,11 +6632,12 @@ function plannedNodeWrites(plan) {
4998
6632
  continue;
4999
6633
  }
5000
6634
  const kind = plan.retypeMap.get(identity) ?? kindOf(identity);
6635
+ const validToChange = requireDefined(plan.nodeValidityEnds.get(identity));
5001
6636
  writes.push({
5002
6637
  identity: mergeKey(kind, idOf(identity)),
5003
6638
  kind,
5004
6639
  id: idOf(identity),
5005
- validTo: requireDefined(plan.nodeValidityEnds.get(identity))
6640
+ ...validToChange.kind === "set" ? { validTo: validToChange.validTo } : { clearValidTo: true }
5006
6641
  });
5007
6642
  }
5008
6643
  return writes;
@@ -5013,67 +6648,128 @@ function nodeWriteProps(write, modificationProps) {
5013
6648
  ...write.modification === void 0 ? void 0 : modificationProps(write.modification)
5014
6649
  };
5015
6650
  }
5016
- async function applyMergePlan(plan, nodesApi, edgesApi, target, txBackend) {
5017
- const committedNodeIds = /* @__PURE__ */ new Set();
5018
- for (const [identity, kind] of plan.nodeDeletions) {
5019
- const collection = nodeCollection(nodesApi, kind);
5020
- await collection.delete(idOf(identity));
6651
+ function nodeWriteWindowOptions(write) {
6652
+ const validFrom = write.validFrom === void 0 ? {} : { validFrom: write.validFrom };
6653
+ if (write.clearValidTo === true) {
6654
+ return { ...validFrom, clearValidTo: true };
5021
6655
  }
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
- }
6656
+ return {
6657
+ ...validFrom,
6658
+ ...write.validTo === void 0 ? {} : { validTo: write.validTo }
6659
+ };
6660
+ }
6661
+ async function applyNodeRows(nodesApi, deletions, upserts) {
6662
+ for (const deletion of deletions) {
6663
+ await nodeCollection(nodesApi, deletion.kind).delete(deletion.id);
6664
+ }
6665
+ const committed = /* @__PURE__ */ new Set();
6666
+ for (const upsert of upserts) {
6667
+ await nodeCollection(nodesApi, upsert.kind).upsertByIdFromRecord(
6668
+ upsert.id,
6669
+ upsert.props,
6670
+ nodeWriteWindowOptions(upsert)
5034
6671
  );
5035
- committedNodeIds.add(write.identity);
6672
+ committed.add(mergeKey(upsert.kind, upsert.id));
5036
6673
  }
5037
- for (const [identity, kind] of plan.edgeDeletions) {
5038
- await edgeCollection(edgesApi, kind).delete(idOf(identity));
6674
+ return committed.size;
6675
+ }
6676
+ async function applyEdgeRows(edgesApi, deletions, upserts) {
6677
+ for (const deletion of deletions) {
6678
+ await edgeCollection(edgesApi, deletion.kind).delete(deletion.id);
5039
6679
  }
5040
- const edgesByKind = /* @__PURE__ */ new Map();
5041
- for (const edge of plan.mergedEdges) {
5042
- const edgeBaseProps = plan.inheritedEdgeBaseProps.get(edge.id);
5043
- const props = edgeBaseProps === void 0 ? edge.props : commitModificationProps(edgeBaseProps, edge.props);
5044
- const item = {
5045
- id: edge.id,
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 }
5051
- };
5052
- const existing = edgesByKind.get(edge.kind);
5053
- if (existing === void 0) {
5054
- edgesByKind.set(edge.kind, [item]);
5055
- } else {
5056
- existing.push(item);
5057
- }
6680
+ const byKind = /* @__PURE__ */ new Map();
6681
+ for (const upsert of upserts) {
6682
+ const items = byKind.get(upsert.kind);
6683
+ if (items === void 0) byKind.set(upsert.kind, [upsert.item]);
6684
+ else items.push(upsert.item);
5058
6685
  }
5059
- let committedEdges = 0;
5060
- for (const [kind, items] of edgesByKind) {
6686
+ let committed = 0;
6687
+ for (const [kind, items] of byKind) {
5061
6688
  await edgeCollection(edgesApi, kind).bulkUpsertById(items);
5062
- committedEdges += items.length;
6689
+ committed += items.length;
5063
6690
  }
5064
- let appliedIdentity;
6691
+ return committed;
6692
+ }
6693
+ async function applyIdentityRows(target, txBackend, assertions, retractions, assertConsistent) {
5065
6694
  try {
5066
- appliedIdentity = await storeRuntime(target).applyIdentityMergeAtTarget(
6695
+ const applied = await storeRuntime(target).applyIdentityMergeAtTarget(
5067
6696
  txBackend,
5068
- plan.identityRetractions.map((retraction) => retraction.id),
5069
- plan.identityAssertions
6697
+ retractions,
6698
+ assertions
5070
6699
  );
5071
- await assertMergedIdentityClassesConsistent(target, txBackend, plan);
6700
+ if (assertConsistent !== void 0) await assertConsistent();
6701
+ return { asserted: applied.created, retracted: applied.retracted };
5072
6702
  } catch (error) {
5073
6703
  throw translateIdentityCommitError(error);
5074
6704
  }
6705
+ }
6706
+ async function applyInternalMergePlan(plan, nodesApi, edgesApi, target, txBackend) {
6707
+ const nodeDeletions = [...plan.nodeDeletions].map(([identity, kind]) => ({
6708
+ kind,
6709
+ id: idOf(identity)
6710
+ }));
6711
+ const nodeUpserts = plannedNodeWrites(plan).map((write) => ({
6712
+ kind: write.kind,
6713
+ id: write.id,
6714
+ props: nodeWriteProps(
6715
+ write,
6716
+ (modification) => commitModificationProps(modification.baseProps, modification.forkProps)
6717
+ ),
6718
+ ...write.validFrom === void 0 ? {} : { validFrom: write.validFrom },
6719
+ ...write.clearValidTo === true ? { clearValidTo: true } : write.validTo === void 0 ? {} : { validTo: write.validTo }
6720
+ }));
6721
+ const validityEndedNodes = new Set(
6722
+ nodeUpserts.filter((write) => "validTo" in write).map((write) => mergeKey(write.kind, write.id))
6723
+ );
6724
+ const earlyIdentityRetractions = plan.identityRetractions.filter(
6725
+ (retraction) => validityEndedNodes.has(mergeKeyOf(retraction.a)) || validityEndedNodes.has(mergeKeyOf(retraction.b))
6726
+ );
6727
+ const earlyIdentity = await applyIdentityRows(
6728
+ target,
6729
+ txBackend,
6730
+ [],
6731
+ earlyIdentityRetractions
6732
+ );
6733
+ let committedNodes;
6734
+ try {
6735
+ committedNodes = await applyNodeRows(nodesApi, nodeDeletions, nodeUpserts);
6736
+ } catch (error) {
6737
+ throw error instanceof IdentityEndpointValidityError ? translateIdentityCommitError(error) : error;
6738
+ }
6739
+ const edgeDeletions = [...plan.edgeDeletions].map(([identity, kind]) => ({
6740
+ kind,
6741
+ id: idOf(identity)
6742
+ }));
6743
+ const edgeUpserts = [];
6744
+ for (const edge of plan.mergedEdges) {
6745
+ const edgeBaseProps = plan.inheritedEdgeBaseProps.get(edge.id);
6746
+ const props = edgeBaseProps === void 0 ? edge.props : commitModificationProps(edgeBaseProps, edge.props);
6747
+ edgeUpserts.push({
6748
+ kind: edge.kind,
6749
+ item: {
6750
+ id: edge.id,
6751
+ from: finalEdgeEndpoint(plan, edge.fromKind, edge.fromId),
6752
+ to: finalEdgeEndpoint(plan, edge.toKind, edge.toId),
6753
+ props,
6754
+ ...edge.validFrom === void 0 ? {} : { validFrom: edge.validFrom },
6755
+ ...edge.clearValidTo === true ? { clearValidTo: true } : edge.validTo === void 0 ? {} : { validTo: edge.validTo }
6756
+ }
6757
+ });
6758
+ }
6759
+ const committedEdges = await applyEdgeRows(
6760
+ edgesApi,
6761
+ edgeDeletions,
6762
+ edgeUpserts
6763
+ );
6764
+ const appliedIdentity = await applyIdentityRows(
6765
+ target,
6766
+ txBackend,
6767
+ plan.identityAssertions,
6768
+ plan.identityRetractions,
6769
+ () => assertMergedIdentityClassesConsistent(target, txBackend, plan)
6770
+ );
5075
6771
  return {
5076
- nodes: committedNodeIds.size,
6772
+ nodes: committedNodes,
5077
6773
  edges: committedEdges,
5078
6774
  // ACTUAL ledger effects from the applier: rows created (idempotent
5079
6775
  // exact/pair matches the target already held are excluded — the normal
@@ -5082,8 +6778,8 @@ async function applyMergePlan(plan, nodesApi, edgesApi, target, txBackend) {
5082
6778
  // failure aborts the whole merge, so the counts never describe a
5083
6779
  // partial commit.
5084
6780
  identity: {
5085
- asserted: appliedIdentity.created,
5086
- retracted: appliedIdentity.retracted
6781
+ asserted: appliedIdentity.asserted,
6782
+ retracted: earlyIdentity.retracted + appliedIdentity.retracted
5087
6783
  }
5088
6784
  };
5089
6785
  }
@@ -5094,26 +6790,39 @@ async function commitPlan(target, plan, expectedBaseVersion) {
5094
6790
  { details: { capability: "transactions" } }
5095
6791
  );
5096
6792
  }
5097
- return withTxConflictRetry(
5098
- () => target.transaction(async (tx) => {
5099
- if (expectedBaseVersion !== void 0) {
5100
- await assertTargetUnchanged(
6793
+ return runMergeCommit(
6794
+ () => withTxConflictRetry(
6795
+ () => target.transaction(async (tx) => {
6796
+ if (expectedBaseVersion !== void 0) {
6797
+ await assertTargetUnchanged(
6798
+ transactionBackend(tx),
6799
+ target,
6800
+ expectedBaseVersion
6801
+ );
6802
+ }
6803
+ await assertPlannedIdentityIdsFresh(
6804
+ target,
5101
6805
  transactionBackend(tx),
6806
+ plan
6807
+ );
6808
+ return applyInternalMergePlan(
6809
+ plan,
6810
+ tx.nodes,
6811
+ tx.edges,
5102
6812
  target,
5103
- expectedBaseVersion
6813
+ transactionBackend(tx)
5104
6814
  );
5105
- }
5106
- await assertPlannedIdentityIdsFresh(target, transactionBackend(tx), plan);
5107
- return applyMergePlan(
5108
- plan,
5109
- tx.nodes,
5110
- tx.edges,
5111
- target,
5112
- transactionBackend(tx)
5113
- );
5114
- }, mergeCommitTransactionOptions(target))
6815
+ }, mergeCommitTransactionOptions(target))
6816
+ )
5115
6817
  );
5116
6818
  }
6819
+ async function runMergeCommit(commit) {
6820
+ try {
6821
+ return await commit();
6822
+ } catch (error) {
6823
+ throw translateMergeCommitError(error);
6824
+ }
6825
+ }
5117
6826
  var MERGE_COMMIT_TX_OPTIONS = {
5118
6827
  isolationLevel: "serializable"
5119
6828
  };
@@ -5250,7 +6959,169 @@ async function tryOpenProvenanceStore(target) {
5250
6959
  );
5251
6960
  }
5252
6961
  }
5253
- async function resolveMerge(store, target, branches, options, useBaseSources, incremental, expectedBaseVersion) {
6962
+ function assertPublicPlanCapability(target) {
6963
+ if (!storeBackend(target).capabilities.transactions) {
6964
+ throw new MergePlanCapabilityError(
6965
+ "Public merge plans require a transaction-capable target backend.",
6966
+ { details: { capability: "transactions" } }
6967
+ );
6968
+ }
6969
+ if (!target.revisionTrackingEnabled) {
6970
+ throw new MergePlanCapabilityError(
6971
+ "Public merge plans require durable target revision tracking.",
6972
+ {
6973
+ details: { capability: "revisionTracking" },
6974
+ suggestion: "Create the target Store with { revisionTracking: true } (or history enabled), then create a new plan."
6975
+ }
6976
+ );
6977
+ }
6978
+ }
6979
+ async function captureMergePlanTargetFence(target) {
6980
+ assertPublicPlanCapability(target);
6981
+ const [activeSchema, schemaHash, origin, revision] = await Promise.all([
6982
+ storeBackend(target).getActiveSchema(target.graphId),
6983
+ computeSchemaComponent(target),
6984
+ target.revisionOriginNow(),
6985
+ target.revisionNow()
6986
+ ]);
6987
+ return {
6988
+ graphId: target.graphId,
6989
+ schema: {
6990
+ managed: activeSchema !== void 0,
6991
+ version: activeSchema?.version ?? 1,
6992
+ hash: activeSchema?.schema_hash ?? schemaHash
6993
+ },
6994
+ revision: { origin, revision: revision ?? null }
6995
+ };
6996
+ }
6997
+ function sameMergePlanTargetFence(left, right) {
6998
+ return left.graphId === right.graphId && left.schema.managed === right.schema.managed && left.schema.version === right.schema.version && left.schema.hash === right.schema.hash && left.revision.origin === right.revision.origin && left.revision.revision === right.revision.revision;
6999
+ }
7000
+ async function assertPlanningFenceUnchanged(target, startingFence) {
7001
+ const endingFence = await captureMergePlanTargetFence(target);
7002
+ if (sameMergePlanTargetFence(startingFence, endingFence)) return;
7003
+ throw new MergePlanningStaleError(
7004
+ "The merge target changed while the plan was being computed; no reviewable plan was produced.",
7005
+ { details: { startingFence, endingFence } }
7006
+ );
7007
+ }
7008
+ function splitWireProps(props) {
7009
+ const setProps = createDataKeyedBag();
7010
+ const unsetProps = [];
7011
+ for (const key of Object.keys(props).sort(compareStrings)) {
7012
+ const value = props[key];
7013
+ if (value === void 0) {
7014
+ unsetProps.push(key);
7015
+ } else {
7016
+ setProps[key] = value;
7017
+ }
7018
+ }
7019
+ return { setProps, unsetProps };
7020
+ }
7021
+ function wireEntityRef(kind, id) {
7022
+ return { kind, id };
7023
+ }
7024
+ function resolvedNodeUpserts(plan) {
7025
+ return plannedNodeWrites(plan).map((write) => ({
7026
+ kind: write.kind,
7027
+ id: write.id,
7028
+ ...splitWireProps(
7029
+ nodeWriteProps(
7030
+ write,
7031
+ (modification) => commitModificationProps(modification.baseProps, modification.forkProps)
7032
+ )
7033
+ ),
7034
+ ...write.validFrom === void 0 ? {} : { validFrom: write.validFrom },
7035
+ ...write.validTo === void 0 ? {} : { validTo: write.validTo }
7036
+ }));
7037
+ }
7038
+ async function resolvedMergeArtifact(resolved, mode, targetFence, anchors) {
7039
+ const { plan, options } = resolved;
7040
+ const nodeUpserts = resolvedNodeUpserts(plan);
7041
+ const edgeUpserts = plan.mergedEdges.map((edge) => {
7042
+ const baseProps = plan.inheritedEdgeBaseProps.get(edge.id);
7043
+ const props = baseProps === void 0 ? edge.props : commitModificationProps(baseProps, edge.props);
7044
+ return {
7045
+ kind: edge.kind,
7046
+ id: edge.id,
7047
+ from: finalEdgeEndpoint(plan, edge.fromKind, edge.fromId),
7048
+ to: finalEdgeEndpoint(plan, edge.toKind, edge.toId),
7049
+ ...splitWireProps(props),
7050
+ ...edge.validFrom === void 0 ? {} : { validFrom: edge.validFrom },
7051
+ ...edge.validTo === void 0 ? {} : { validTo: edge.validTo }
7052
+ };
7053
+ });
7054
+ const input = {
7055
+ formatVersion: 1,
7056
+ mode,
7057
+ target: targetFence,
7058
+ anchors,
7059
+ proposed: {
7060
+ nodes: {
7061
+ upserts: nodeUpserts.length,
7062
+ deletions: plan.nodeDeletions.size
7063
+ },
7064
+ edges: {
7065
+ upserts: edgeUpserts.length,
7066
+ deletions: plan.edgeDeletions.size
7067
+ },
7068
+ identity: {
7069
+ assertions: plan.identityAssertions.length,
7070
+ retractions: plan.identityRetractions.length
7071
+ }
7072
+ },
7073
+ writes: {
7074
+ nodeDeletes: [...plan.nodeDeletions].map(
7075
+ ([identity, kind]) => wireEntityRef(kind, idOf(identity))
7076
+ ),
7077
+ nodeUpserts,
7078
+ edgeDeletes: [...plan.edgeDeletions].map(
7079
+ ([identity, kind]) => wireEntityRef(kind, idOf(identity))
7080
+ ),
7081
+ edgeUpserts,
7082
+ identityAssertions: plan.identityAssertions,
7083
+ identityRetractions: plan.identityRetractions
7084
+ },
7085
+ guards: {
7086
+ canonicalMappings: [...plan.canonicalOf].sort(([left], [right]) => compareMergeKeys(left, right)).map(([member, canonical]) => ({
7087
+ member: wireEntityRef(kindOf(member), idOf(member)),
7088
+ canonical: wireEntityRef(kindOf(canonical), idOf(canonical))
7089
+ })),
7090
+ retypes: [...plan.retypeMap].sort(([left], [right]) => compareMergeKeys(left, right)).map(([entity, toKind]) => ({
7091
+ entity: wireEntityRef(kindOf(entity), idOf(entity)),
7092
+ toKind
7093
+ })),
7094
+ deletedNodes: [...plan.nodeDeletions].map(
7095
+ ([identity, kind]) => wireEntityRef(kind, idOf(identity))
7096
+ ),
7097
+ ...resolved.incrementalGuard === void 0 ? {} : {
7098
+ incremental: {
7099
+ tombstoneResurrection: "refuse",
7100
+ lossyUpdates: "refuse",
7101
+ edgeIdentity: "preserve"
7102
+ }
7103
+ }
7104
+ },
7105
+ review: {
7106
+ resolutions: plan.resolutions,
7107
+ conflicts: plan.propertyConflicts,
7108
+ deleteModifyConflicts: plan.deleteModifyConflicts,
7109
+ typeReconciliations: plan.typeReconciliations,
7110
+ dropped: plan.dropped,
7111
+ validityEnds: plan.validityEnds,
7112
+ baseAmbiguities: plan.baseAmbiguities,
7113
+ provenanceRecords: plan.provenanceRecords,
7114
+ warnings: plan.warnings,
7115
+ ...plan.candidateDiagnostics === void 0 ? {} : { diagnostics: plan.candidateDiagnostics }
7116
+ },
7117
+ provenance: {
7118
+ includeInReport: options.provenance,
7119
+ persist: options.persistProvenance
7120
+ }
7121
+ };
7122
+ return constructMergePlanArtifact(input);
7123
+ }
7124
+ async function resolveMerge(store, target, branches, options, useBaseSources, incremental, expectedBaseVersion, complete) {
5254
7125
  let targetBranchSeen = false;
5255
7126
  for (const branch2 of branches) {
5256
7127
  if (branch2.id === BASE_PROVENANCE_BRANCH) {
@@ -5303,10 +7174,6 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
5303
7174
  );
5304
7175
  }
5305
7176
  }
5306
- const provenanceStore = options.persistProvenance ? await tryOpenProvenanceStore(target) : void 0;
5307
- if (provenanceStore !== void 0 && isErr(provenanceStore)) {
5308
- return provenanceStore;
5309
- }
5310
7177
  try {
5311
7178
  const preferredBranchId = incremental?.targetBranchId;
5312
7179
  const staging = await stageBranches(store, branches, preferredBranchId);
@@ -5375,10 +7242,12 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
5375
7242
  sameIdAcrossKinds: target.graph.identity?.sameIdAcrossKinds,
5376
7243
  areDisjoint: (left, right) => target.registry.areDisjoint(left, right)
5377
7244
  };
5378
- const plan = planMerge(
7245
+ const plan = buildInternalMergePlan(
5379
7246
  staging,
5380
7247
  candidates.data.edges,
5381
7248
  candidates.data.warnings,
7249
+ candidates.data.diagnostics,
7250
+ candidates.data.diagnosticsTotal,
5382
7251
  candidates.data.baseMembers,
5383
7252
  options,
5384
7253
  branchRank,
@@ -5434,29 +7303,727 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
5434
7303
  );
5435
7304
  identityGuard = built.probe;
5436
7305
  }
5437
- const merged = incremental === void 0 ? await commitPlan(target, plan, expectedBaseVersion) : await commitIncrementalPlan(target, plan, {
7306
+ const incrementalGuard = incremental === void 0 ? void 0 : {
5438
7307
  stagedNewByKind,
5439
7308
  options,
5440
7309
  introspectionKinds,
5441
7310
  forkPoint: incremental.forkPoint,
5442
7311
  ...identityGuard === void 0 ? {} : { identityPeerProbe: identityGuard },
5443
- // The committed (kind, id) keys the base sources matched at PLAN time;
5444
- // anything NEW the in-tx re-probe surfaces is a window write.
5445
7312
  plannedBaseMatchKeys: new Set(
5446
7313
  candidates.data.baseMembers.map((member) => mergeKeyOf(member))
5447
7314
  ),
5448
7315
  targetNodeVersions: staging.targetNodeVersions,
5449
7316
  targetEdgeSignatures: staging.targetEdgeSignatures
5450
- });
5451
- const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
5452
- const warnings = [...plan.warnings];
7317
+ };
7318
+ return ok(
7319
+ await complete({
7320
+ target,
7321
+ plan,
7322
+ options,
7323
+ ...expectedBaseVersion === void 0 ? {} : { expectedBaseVersion },
7324
+ ...incrementalGuard === void 0 ? {} : { incrementalGuard }
7325
+ })
7326
+ );
7327
+ } catch (error) {
7328
+ if (error instanceof MergeError) {
7329
+ return err(error);
7330
+ }
7331
+ return err(
7332
+ new MergeError(
7333
+ `Merge failed while staging or committing: ${describeCause(error)}`,
7334
+ { cause: error }
7335
+ )
7336
+ );
7337
+ }
7338
+ }
7339
+ async function commitResolvedMerge(resolved) {
7340
+ const { target, plan, options } = resolved;
7341
+ const provenanceStore = options.persistProvenance ? await tryOpenProvenanceStore(target) : void 0;
7342
+ if (provenanceStore !== void 0 && isErr(provenanceStore)) {
7343
+ throw provenanceStore.error;
7344
+ }
7345
+ const merged = resolved.incrementalGuard === void 0 ? await commitPlan(target, plan, resolved.expectedBaseVersion) : await commitIncrementalPlan(target, plan, resolved.incrementalGuard);
7346
+ const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
7347
+ const warnings = [...plan.warnings];
7348
+ let provenancePersisted;
7349
+ if (provenanceStore !== void 0 && !isErr(provenanceStore)) {
7350
+ try {
7351
+ const count = await persistProvenanceRecords(
7352
+ provenanceStore.data,
7353
+ target.graphId,
7354
+ plan.provenanceRecords
7355
+ );
7356
+ provenancePersisted = {
7357
+ graphId: provenanceGraphId(target.graphId),
7358
+ count
7359
+ };
7360
+ } catch (error) {
7361
+ warnings.push(
7362
+ "provenance persistence failed (graph committed; provenance not persisted): " + (error instanceof Error ? error.message : String(error))
7363
+ );
7364
+ }
7365
+ }
7366
+ return {
7367
+ merged,
7368
+ resolutions: plan.resolutions,
7369
+ conflicts: plan.propertyConflicts,
7370
+ deleteModifyConflicts: plan.deleteModifyConflicts,
7371
+ typeReconciliations: plan.typeReconciliations,
7372
+ dropped: plan.dropped,
7373
+ validityEnds: plan.validityEnds,
7374
+ baseAmbiguities: plan.baseAmbiguities,
7375
+ provenance,
7376
+ warnings,
7377
+ ...plan.candidateDiagnostics === void 0 ? {} : { candidateDiagnostics: plan.candidateDiagnostics },
7378
+ ...provenancePersisted === void 0 ? {} : { provenancePersisted }
7379
+ };
7380
+ }
7381
+ function incrementalBaseConflictPolicyError(options) {
7382
+ return new InvalidMergeOptionsError(
7383
+ 'mergeIncremental() requires onBasePropertyConflict: "flag" (keep-base); a non-keep-base policy could overwrite a newer committed base value with a stale branch value.',
7384
+ {
7385
+ details: {
7386
+ option: "onBasePropertyConflict",
7387
+ accepted: "flag",
7388
+ received: options.onBasePropertyConflict
7389
+ }
7390
+ }
7391
+ );
7392
+ }
7393
+ function incrementalSchemaError() {
7394
+ return new MergeError(
7395
+ "mergeIncremental() requires target and forkPoint to share a schema; schema drift is not supported (the target content may differ \u2014 only the schema must match).",
7396
+ { details: {} }
7397
+ );
7398
+ }
7399
+ async function planMerge(store, branches, optionsInput = {}) {
7400
+ const normalized = tryNormalize(optionsInput);
7401
+ if (isErr(normalized)) return err(normalized.error);
7402
+ const options = normalized.data;
7403
+ const target = options.target ?? store;
7404
+ let targetFence;
7405
+ try {
7406
+ targetFence = await captureMergePlanTargetFence(target);
7407
+ } catch (error) {
7408
+ return err(
7409
+ error instanceof MergeError ? error : new MergePlanCapabilityError(
7410
+ `Unable to capture the target's durable plan fence: ${describeCause(error)}`,
7411
+ { cause: error }
7412
+ )
7413
+ );
7414
+ }
7415
+ const precondition = await validateBaseVersions(target, branches);
7416
+ if (isErr(precondition)) return err(precondition.error);
7417
+ const anchors = {
7418
+ kind: "snapshot",
7419
+ base: { graphId: store.graphId, baseVersion: precondition.data },
7420
+ branches: [...branches].sort((left, right) => compareStrings(left.id, right.id)).map((branch2) => ({
7421
+ branchId: branch2.id,
7422
+ baseVersion: branch2.base
7423
+ }))
7424
+ };
7425
+ return resolveMerge(
7426
+ store,
7427
+ target,
7428
+ branches,
7429
+ options,
7430
+ false,
7431
+ void 0,
7432
+ precondition.data,
7433
+ async (resolved) => {
7434
+ await assertPlanningFenceUnchanged(target, targetFence);
7435
+ return resolvedMergeArtifact(resolved, "snapshot", targetFence, anchors);
7436
+ }
7437
+ );
7438
+ }
7439
+ async function planMergeIncremental(args) {
7440
+ const { forkPoint, target, branches } = args;
7441
+ const normalized = tryNormalize(args.options ?? {}, ["target"]);
7442
+ if (isErr(normalized)) return err(normalized.error);
7443
+ const options = normalized.data;
7444
+ if (options.onBasePropertyConflict !== "flag") {
7445
+ return err(incrementalBaseConflictPolicyError(options));
7446
+ }
7447
+ let targetFence;
7448
+ try {
7449
+ targetFence = await captureMergePlanTargetFence(target);
7450
+ } catch (error) {
7451
+ return err(
7452
+ error instanceof MergeError ? error : new MergePlanCapabilityError(
7453
+ `Unable to capture the target's durable plan fence: ${describeCause(error)}`,
7454
+ { cause: error }
7455
+ )
7456
+ );
7457
+ }
7458
+ const forkPrecondition = await validateForkPointVersions(forkPoint, branches);
7459
+ if (isErr(forkPrecondition)) return err(forkPrecondition.error);
7460
+ const forkVersion = forkPrecondition.data;
7461
+ const [forkSchema, targetSchema] = await Promise.all([
7462
+ computeSchemaComponent(forkPoint),
7463
+ computeSchemaComponent(target)
7464
+ ]);
7465
+ if (forkSchema !== targetSchema) return err(incrementalSchemaError());
7466
+ const forkActiveSchema = await storeBackend(forkPoint).getActiveSchema(
7467
+ forkPoint.graphId
7468
+ );
7469
+ const targetBranch = {
7470
+ id: COMMITTED_TARGET_BRANCH,
7471
+ base: forkVersion,
7472
+ store: target
7473
+ };
7474
+ const anchors = {
7475
+ kind: "incremental",
7476
+ forkPoint: {
7477
+ graphId: forkPoint.graphId,
7478
+ baseVersion: forkVersion,
7479
+ schema: {
7480
+ managed: forkActiveSchema !== void 0,
7481
+ version: forkActiveSchema?.version ?? 1,
7482
+ hash: forkActiveSchema?.schema_hash ?? forkSchema
7483
+ }
7484
+ },
7485
+ branches: [...branches].sort((left, right) => compareStrings(left.id, right.id)).map((branch2) => ({
7486
+ branchId: branch2.id,
7487
+ baseVersion: branch2.base
7488
+ }))
7489
+ };
7490
+ return resolveMerge(
7491
+ forkPoint,
7492
+ target,
7493
+ [targetBranch, ...branches],
7494
+ options,
7495
+ true,
7496
+ {
7497
+ targetBranchId: COMMITTED_TARGET_BRANCH,
7498
+ forkPoint: { store: forkPoint, version: forkVersion }
7499
+ },
7500
+ void 0,
7501
+ async (resolved) => {
7502
+ await assertPlanningFenceUnchanged(target, targetFence);
7503
+ await assertForkPointUnchanged({
7504
+ store: forkPoint,
7505
+ version: forkVersion
7506
+ });
7507
+ return resolvedMergeArtifact(
7508
+ resolved,
7509
+ "incremental",
7510
+ targetFence,
7511
+ anchors
7512
+ );
7513
+ }
7514
+ );
7515
+ }
7516
+ function mergePlanValidationError(failure) {
7517
+ switch (failure.kind) {
7518
+ case "unsupported-version": {
7519
+ return new UnsupportedMergePlanVersionError(
7520
+ `Unsupported merge plan format version ${String(failure.received)}.`,
7521
+ { details: { received: failure.received } }
7522
+ );
7523
+ }
7524
+ case "digest-mismatch": {
7525
+ return new MergePlanDigestMismatchError(
7526
+ "The merge plan digest does not match its canonical content.",
7527
+ { details: { expected: failure.expected, received: failure.received } }
7528
+ );
7529
+ }
7530
+ case "malformed": {
7531
+ return new InvalidMergePlanError("The merge plan is malformed.", {
7532
+ details: { issues: failure.issues }
7533
+ });
7534
+ }
7535
+ default: {
7536
+ const exhaustive = failure;
7537
+ return exhaustive;
7538
+ }
7539
+ }
7540
+ }
7541
+ function wireWriteProps(setProps, unsetProps) {
7542
+ const props = createDataKeyedBag();
7543
+ for (const [key, value] of Object.entries(setProps)) props[key] = value;
7544
+ for (const key of unsetProps) props[key] = void 0;
7545
+ return props;
7546
+ }
7547
+ function resolvedWireProps(current, setProps, unsetProps) {
7548
+ const props = createDataKeyedBag();
7549
+ const unset = new Set(unsetProps);
7550
+ for (const [key, value] of Object.entries(current)) {
7551
+ if (!unset.has(key)) props[key] = value;
7552
+ }
7553
+ for (const [key, value] of Object.entries(setProps)) props[key] = value;
7554
+ return props;
7555
+ }
7556
+ function invalidPlanForTarget(message, details) {
7557
+ return new InvalidMergePlanError(message, { details });
7558
+ }
7559
+ async function preflightWireMergeWrites(target, nodesApi, edgesApi, artifact) {
7560
+ const graphNodes = target.graph.nodes;
7561
+ const graphEdges = target.graph.edges;
7562
+ const assertNodeKind = (kind) => {
7563
+ if (!hasOwnKey(graphNodes, kind)) {
7564
+ throw invalidPlanForTarget(
7565
+ `The merge plan references unknown node kind "${kind}".`,
7566
+ { kind, role: "node" }
7567
+ );
7568
+ }
7569
+ };
7570
+ const assertEdgeKind = (kind) => {
7571
+ if (!hasOwnKey(graphEdges, kind) || graphEdges[kind] === void 0) {
7572
+ throw invalidPlanForTarget(
7573
+ `The merge plan references unknown edge kind "${kind}".`,
7574
+ { kind, role: "edge" }
7575
+ );
7576
+ }
7577
+ };
7578
+ for (const item of [
7579
+ ...artifact.writes.nodeDeletes,
7580
+ ...artifact.writes.nodeUpserts,
7581
+ ...artifact.guards.deletedNodes,
7582
+ ...artifact.guards.canonicalMappings.flatMap((mapping) => [
7583
+ mapping.member,
7584
+ mapping.canonical
7585
+ ]),
7586
+ ...artifact.guards.retypes.map((retype) => retype.entity),
7587
+ ...artifact.writes.identityAssertions.flatMap((assertion) => [
7588
+ assertion.a,
7589
+ assertion.b
7590
+ ]),
7591
+ ...artifact.writes.identityRetractions.flatMap((assertion) => [
7592
+ assertion.a,
7593
+ assertion.b
7594
+ ])
7595
+ ]) {
7596
+ assertNodeKind(item.kind);
7597
+ }
7598
+ for (const retype of artifact.guards.retypes) assertNodeKind(retype.toKind);
7599
+ for (const item of [
7600
+ ...artifact.writes.edgeDeletes,
7601
+ ...artifact.writes.edgeUpserts
7602
+ ]) {
7603
+ assertEdgeKind(item.kind);
7604
+ }
7605
+ const plannedNodes = new Set(
7606
+ artifact.writes.nodeUpserts.map(
7607
+ (upsert) => mergeKey(upsert.kind, upsert.id)
7608
+ )
7609
+ );
7610
+ const deletedNodes = new Set(
7611
+ artifact.writes.nodeDeletes.map(
7612
+ (deletion) => mergeKey(deletion.kind, deletion.id)
7613
+ )
7614
+ );
7615
+ const endpointRows = /* @__PURE__ */ new Map();
7616
+ const readEndpoint = async (endpoint) => {
7617
+ const key = mergeKey(endpoint.kind, endpoint.id);
7618
+ if (endpointRows.has(key)) return endpointRows.get(key);
7619
+ const row = (await nodeCollection(nodesApi, endpoint.kind).getByIds(
7620
+ [endpoint.id],
7621
+ INCLUDE_TOMBSTONES
7622
+ ))[0];
7623
+ endpointRows.set(key, row);
7624
+ return row;
7625
+ };
7626
+ const nodeUpsertsByKind = /* @__PURE__ */ new Map();
7627
+ for (const upsert of artifact.writes.nodeUpserts) {
7628
+ const upserts = nodeUpsertsByKind.get(upsert.kind) ?? [];
7629
+ upserts.push(upsert);
7630
+ nodeUpsertsByKind.set(upsert.kind, upserts);
7631
+ }
7632
+ const currentNodesByIdentity = /* @__PURE__ */ new Map();
7633
+ for (const [kind, upserts] of nodeUpsertsByKind) {
7634
+ const rows = await nodeCollection(nodesApi, kind).getByIds(
7635
+ upserts.map((upsert) => upsert.id),
7636
+ INCLUDE_TOMBSTONES
7637
+ );
7638
+ for (const [index, upsert] of upserts.entries()) {
7639
+ const row = rows[index];
7640
+ const identity = mergeKey(kind, upsert.id);
7641
+ currentNodesByIdentity.set(identity, row);
7642
+ endpointRows.set(identity, row);
7643
+ }
7644
+ }
7645
+ for (const upsert of artifact.writes.nodeUpserts) {
7646
+ const current = currentNodesByIdentity.get(
7647
+ mergeKey(upsert.kind, upsert.id)
7648
+ );
7649
+ if (artifact.guards.incremental !== void 0 && current?.meta.deletedAt !== void 0) {
7650
+ throw invalidPlanForTarget(
7651
+ `The incremental merge plan would resurrect soft-deleted committed node "${upsert.id}".`,
7652
+ { kind: upsert.kind, id: upsert.id, role: "node" }
7653
+ );
7654
+ }
7655
+ const props = resolvedWireProps(
7656
+ current === void 0 ? {} : nodeProps2(current),
7657
+ upsert.setProps,
7658
+ upsert.unsetProps
7659
+ );
7660
+ const schema = nodeSchemaFor(target, upsert.kind);
7661
+ if (schema !== void 0 && !schema.safeParse(props).success) {
7662
+ throw invalidPlanForTarget(
7663
+ `The merge plan's node write for "${upsert.id}" does not validate against the active schema.`,
7664
+ { kind: upsert.kind, id: upsert.id, role: "node" }
7665
+ );
7666
+ }
7667
+ if (artifact.guards.incremental !== void 0 && current !== void 0) {
7668
+ const analysis = analyzeRevalidatingWrite(
7669
+ schema,
7670
+ nodeProps2(current),
7671
+ wireWriteProps(upsert.setProps, upsert.unsetProps)
7672
+ );
7673
+ assertValidRevalidatingWrite(
7674
+ analysis,
7675
+ `The incremental merge plan found committed node "${upsert.id}" with props that do not validate against the active schema.`,
7676
+ { kind: upsert.kind, id: upsert.id, role: "node" }
7677
+ );
7678
+ if (analysis.stripsCurrentProps && analysis.schemaWouldChange) {
7679
+ throw invalidPlanForTarget(
7680
+ `The incremental merge plan would strip existing props while updating committed node "${upsert.id}".`,
7681
+ { kind: upsert.kind, id: upsert.id, role: "node" }
7682
+ );
7683
+ }
7684
+ }
7685
+ }
7686
+ const currentEdgeIds = /* @__PURE__ */ new Set();
7687
+ const edgeUpsertsByKind = /* @__PURE__ */ new Map();
7688
+ for (const upsert of artifact.writes.edgeUpserts) {
7689
+ const upserts = edgeUpsertsByKind.get(upsert.kind) ?? [];
7690
+ upserts.push(upsert);
7691
+ edgeUpsertsByKind.set(upsert.kind, upserts);
7692
+ }
7693
+ const currentEdgesByIdentity = /* @__PURE__ */ new Map();
7694
+ for (const [kind, upserts] of edgeUpsertsByKind) {
7695
+ const rows = await edgeCollection(edgesApi, kind).getByIds(
7696
+ upserts.map((upsert) => upsert.id),
7697
+ INCLUDE_TOMBSTONES
7698
+ );
7699
+ for (const [index, upsert] of upserts.entries()) {
7700
+ const row = rows[index];
7701
+ currentEdgesByIdentity.set(mergeKey(kind, upsert.id), row);
7702
+ if (row !== void 0) currentEdgeIds.add(upsert.id);
7703
+ }
7704
+ }
7705
+ for (const upsert of artifact.writes.edgeUpserts) {
7706
+ const registration = requireDefined(graphEdges[upsert.kind]);
7707
+ const endpointError = validateEdgeEndpoints(
7708
+ upsert.kind,
7709
+ upsert.from.kind,
7710
+ upsert.to.kind,
7711
+ registration,
7712
+ target.registry
7713
+ );
7714
+ if (endpointError !== void 0) {
7715
+ throw invalidPlanForTarget(endpointError.message, {
7716
+ kind: upsert.kind,
7717
+ id: upsert.id,
7718
+ role: "edge"
7719
+ });
7720
+ }
7721
+ for (const endpoint of [upsert.from, upsert.to]) {
7722
+ assertNodeKind(endpoint.kind);
7723
+ const key = mergeKey(endpoint.kind, endpoint.id);
7724
+ if (deletedNodes.has(key)) {
7725
+ throw invalidPlanForTarget(
7726
+ `The merge plan deletes an endpoint required by edge "${upsert.id}".`,
7727
+ { edgeKind: upsert.kind, edgeId: upsert.id, endpoint }
7728
+ );
7729
+ }
7730
+ if (plannedNodes.has(key)) continue;
7731
+ const row = await readEndpoint(endpoint);
7732
+ if (row === void 0 || row.meta.deletedAt !== void 0) {
7733
+ throw invalidPlanForTarget(
7734
+ `The merge plan references a missing edge endpoint "${endpoint.id}".`,
7735
+ { edgeKind: upsert.kind, edgeId: upsert.id, endpoint }
7736
+ );
7737
+ }
7738
+ }
7739
+ const current = currentEdgesByIdentity.get(
7740
+ mergeKey(upsert.kind, upsert.id)
7741
+ );
7742
+ if (artifact.guards.incremental !== void 0 && current?.meta.deletedAt !== void 0) {
7743
+ throw invalidPlanForTarget(
7744
+ `The incremental merge plan would resurrect soft-deleted committed edge "${upsert.id}".`,
7745
+ { kind: upsert.kind, id: upsert.id, role: "edge" }
7746
+ );
7747
+ }
7748
+ const props = resolvedWireProps(
7749
+ current === void 0 ? {} : edgeProps(current),
7750
+ upsert.setProps,
7751
+ upsert.unsetProps
7752
+ );
7753
+ const schema = edgeSchemaFor(target, upsert.kind);
7754
+ if (schema !== void 0 && !schema.safeParse(props).success) {
7755
+ throw invalidPlanForTarget(
7756
+ `The merge plan's edge write for "${upsert.id}" does not validate against the active schema.`,
7757
+ { kind: upsert.kind, id: upsert.id, role: "edge" }
7758
+ );
7759
+ }
7760
+ if (artifact.guards.incremental !== void 0 && current !== void 0) {
7761
+ const analysis = analyzeRevalidatingWrite(
7762
+ schema,
7763
+ edgeProps(current),
7764
+ wireWriteProps(upsert.setProps, upsert.unsetProps)
7765
+ );
7766
+ assertValidRevalidatingWrite(
7767
+ analysis,
7768
+ `The incremental merge plan found committed edge "${upsert.id}" with props that do not validate against the active schema.`,
7769
+ { kind: upsert.kind, id: upsert.id, role: "edge" }
7770
+ );
7771
+ if (analysis.stripsCurrentProps && analysis.schemaWouldChange) {
7772
+ throw invalidPlanForTarget(
7773
+ `The incremental merge plan would strip existing props while updating committed edge "${upsert.id}".`,
7774
+ { kind: upsert.kind, id: upsert.id, role: "edge" }
7775
+ );
7776
+ }
7777
+ }
7778
+ if (current !== void 0 && (current.fromKind !== upsert.from.kind || current.fromId !== upsert.from.id || current.toKind !== upsert.to.kind || current.toId !== upsert.to.id)) {
7779
+ throw invalidPlanForTarget(
7780
+ `The merge plan would change the immutable endpoints of edge "${upsert.id}".`,
7781
+ { kind: upsert.kind, id: upsert.id, role: "edge" }
7782
+ );
7783
+ }
7784
+ }
7785
+ const missingEdgeIds = artifact.writes.edgeUpserts.map((upsert) => upsert.id).filter((id) => !currentEdgeIds.has(id));
7786
+ if (missingEdgeIds.length > 0) {
7787
+ const plannedEdgeById = new Map(
7788
+ artifact.writes.edgeUpserts.map((upsert) => [upsert.id, upsert])
7789
+ );
7790
+ for (const kind of Object.keys(target.graph.edges)) {
7791
+ const rows = await edgeCollection(edgesApi, kind).getByIds(
7792
+ missingEdgeIds,
7793
+ INCLUDE_TOMBSTONES
7794
+ );
7795
+ for (const [index, row] of rows.entries()) {
7796
+ if (row === void 0) continue;
7797
+ const id = requireDefined(missingEdgeIds[index]);
7798
+ const planned = plannedEdgeById.get(id);
7799
+ if (planned !== void 0 && planned.kind !== row.kind) {
7800
+ throw invalidPlanForTarget(
7801
+ `The merge plan reuses edge id "${id}" across kinds.`,
7802
+ { id, committedKind: row.kind, plannedKind: planned.kind }
7803
+ );
7804
+ }
7805
+ }
7806
+ }
7807
+ }
7808
+ }
7809
+ async function assertMergePlanFenceInsideTransaction(target, txBackend, artifact) {
7810
+ await lockRecordedGraphWrite(txBackend, target.graphId);
7811
+ const activeSchema = await txBackend.getActiveSchema(target.graphId);
7812
+ const liveSchema = {
7813
+ managed: activeSchema !== void 0,
7814
+ version: activeSchema?.version ?? 1,
7815
+ hash: activeSchema?.schema_hash ?? await computeSchemaComponent(target)
7816
+ };
7817
+ if (liveSchema.managed !== artifact.target.schema.managed || liveSchema.version !== artifact.target.schema.version || liveSchema.hash !== artifact.target.schema.hash) {
7818
+ throw new MergePlanSchemaMismatchError(
7819
+ "The target's active schema no longer matches the merge plan.",
7820
+ { details: { expected: artifact.target.schema, live: liveSchema } }
7821
+ );
7822
+ }
7823
+ const liveOrigin = await readRevisionOrigin(
7824
+ txBackend,
7825
+ target.revisionSchema,
7826
+ target.graphId
7827
+ );
7828
+ if (liveOrigin !== artifact.target.revision.origin) {
7829
+ throw new MergePlanOriginMismatchError(
7830
+ "The merge plan belongs to a different target revision origin.",
7831
+ {
7832
+ details: {
7833
+ expectedOrigin: artifact.target.revision.origin,
7834
+ liveOrigin
7835
+ }
7836
+ }
7837
+ );
7838
+ }
7839
+ const liveRevision = await readRecordedClock(
7840
+ txBackend,
7841
+ target.revisionSchema,
7842
+ target.graphId
7843
+ ) ?? null;
7844
+ if (liveRevision !== artifact.target.revision.revision) {
7845
+ throw new StaleMergePlanError(
7846
+ "The target revision changed after this merge plan was created; the plan was not applied.",
7847
+ {
7848
+ details: {
7849
+ expectedRevision: artifact.target.revision.revision,
7850
+ liveRevision
7851
+ }
7852
+ }
7853
+ );
7854
+ }
7855
+ }
7856
+ function identityConsistencySeeds(artifact, profile) {
7857
+ const deleted = new Set(
7858
+ artifact.writes.nodeDeletes.map(
7859
+ (entity) => mergeKey(entity.kind, entity.id)
7860
+ )
7861
+ );
7862
+ const byIdentity = /* @__PURE__ */ new Map();
7863
+ const add = (entity) => {
7864
+ const identity = mergeKey(entity.kind, entity.id);
7865
+ if (!deleted.has(identity)) byIdentity.set(identity, entity);
7866
+ };
7867
+ for (const assertion of [
7868
+ ...artifact.writes.identityAssertions,
7869
+ ...artifact.writes.identityRetractions
7870
+ ]) {
7871
+ add(assertion.a);
7872
+ add(assertion.b);
7873
+ }
7874
+ if (profile === "fold") {
7875
+ for (const upsert of artifact.writes.nodeUpserts) add(upsert);
7876
+ }
7877
+ return [...byIdentity].sort(([left], [right]) => compareMergeKeys(left, right)).map(([, entity]) => entity);
7878
+ }
7879
+ async function applyWireMergeWrites(target, nodesApi, edgesApi, txBackend, artifact) {
7880
+ const committedNodes = await applyNodeRows(
7881
+ nodesApi,
7882
+ artifact.writes.nodeDeletes,
7883
+ artifact.writes.nodeUpserts.map((upsert) => ({
7884
+ kind: upsert.kind,
7885
+ id: upsert.id,
7886
+ props: wireWriteProps(upsert.setProps, upsert.unsetProps),
7887
+ ...upsert.validFrom === void 0 ? {} : { validFrom: upsert.validFrom },
7888
+ ...upsert.validTo === void 0 ? {} : { validTo: upsert.validTo }
7889
+ }))
7890
+ );
7891
+ const committedEdges = await applyEdgeRows(
7892
+ edgesApi,
7893
+ artifact.writes.edgeDeletes,
7894
+ artifact.writes.edgeUpserts.map((upsert) => ({
7895
+ kind: upsert.kind,
7896
+ item: {
7897
+ id: upsert.id,
7898
+ from: upsert.from,
7899
+ to: upsert.to,
7900
+ props: wireWriteProps(upsert.setProps, upsert.unsetProps),
7901
+ ...upsert.validFrom === void 0 ? {} : { validFrom: upsert.validFrom },
7902
+ ...upsert.validTo === void 0 ? {} : { validTo: upsert.validTo }
7903
+ }
7904
+ }))
7905
+ );
7906
+ const identityAssertions = artifact.writes.identityAssertions;
7907
+ const identityRetractions = artifact.writes.identityRetractions;
7908
+ const appliedIdentity = await applyIdentityRows(
7909
+ target,
7910
+ txBackend,
7911
+ identityAssertions,
7912
+ identityRetractions,
7913
+ () => storeRuntime(target).assertIdentityClassesConsistentAtTarget(
7914
+ txBackend,
7915
+ identityConsistencySeeds(
7916
+ artifact,
7917
+ target.graph.identity?.sameIdAcrossKinds
7918
+ )
7919
+ )
7920
+ );
7921
+ if (!forceRecordedGraphRevision(txBackend, target.graphId) && !forceWriteTransactionRevision(txBackend)) {
7922
+ await advanceRevisionClock(
7923
+ txBackend,
7924
+ target.revisionSchema,
7925
+ target.graphId,
7926
+ true
7927
+ );
7928
+ }
7929
+ return {
7930
+ nodes: committedNodes,
7931
+ edges: committedEdges,
7932
+ identity: {
7933
+ asserted: appliedIdentity.asserted,
7934
+ retracted: appliedIdentity.retracted
7935
+ }
7936
+ };
7937
+ }
7938
+ function reportFromArtifact(artifact, merged, warnings, provenancePersisted) {
7939
+ const provenanceRecords = artifact.review.provenanceRecords;
7940
+ return {
7941
+ merged,
7942
+ resolutions: artifact.review.resolutions,
7943
+ conflicts: artifact.review.conflicts,
7944
+ deleteModifyConflicts: artifact.review.deleteModifyConflicts,
7945
+ typeReconciliations: artifact.review.typeReconciliations,
7946
+ dropped: artifact.review.dropped,
7947
+ validityEnds: artifact.review.validityEnds,
7948
+ baseAmbiguities: artifact.review.baseAmbiguities,
7949
+ provenance: artifact.provenance.includeInReport ? buildProvenanceIndex(provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) },
7950
+ warnings,
7951
+ ...artifact.review.diagnostics === void 0 ? {} : {
7952
+ candidateDiagnostics: artifact.review.diagnostics
7953
+ },
7954
+ ...provenancePersisted === void 0 ? {} : { provenancePersisted }
7955
+ };
7956
+ }
7957
+ async function applyMergePlan(target, input) {
7958
+ let validation;
7959
+ try {
7960
+ validation = await validateMergePlanArtifact(input);
7961
+ } catch (error) {
7962
+ return err(
7963
+ new InvalidMergePlanError(
7964
+ `Merge plan validation failed: ${describeCause(error)}`,
7965
+ { cause: error }
7966
+ )
7967
+ );
7968
+ }
7969
+ if (!validation.success)
7970
+ return err(mergePlanValidationError(validation.error));
7971
+ const artifact = validation.artifact;
7972
+ if (artifact.target.graphId !== target.graphId) {
7973
+ return err(
7974
+ new MergePlanTargetMismatchError(
7975
+ "The merge plan names a different target graph.",
7976
+ {
7977
+ details: {
7978
+ expectedGraphId: artifact.target.graphId,
7979
+ receivedGraphId: target.graphId
7980
+ }
7981
+ }
7982
+ )
7983
+ );
7984
+ }
7985
+ try {
7986
+ assertPublicPlanCapability(target);
7987
+ const provenanceStore = artifact.provenance.persist ? await tryOpenProvenanceStore(target) : void 0;
7988
+ if (provenanceStore !== void 0 && isErr(provenanceStore)) {
7989
+ return err(provenanceStore.error);
7990
+ }
7991
+ const merged = await withTxConflictRetry(
7992
+ () => target.transaction(async (tx) => {
7993
+ const txBackend = transactionBackend(tx);
7994
+ await assertMergePlanFenceInsideTransaction(
7995
+ target,
7996
+ txBackend,
7997
+ artifact
7998
+ );
7999
+ await preflightWireMergeWrites(
8000
+ target,
8001
+ tx.nodes,
8002
+ tx.edges,
8003
+ artifact
8004
+ );
8005
+ await assertPlannedIdentityIdsFresh(target, txBackend, {
8006
+ identityAssertions: artifact.writes.identityAssertions,
8007
+ identityRetractions: artifact.writes.identityRetractions
8008
+ });
8009
+ return applyWireMergeWrites(
8010
+ target,
8011
+ tx.nodes,
8012
+ tx.edges,
8013
+ txBackend,
8014
+ artifact
8015
+ );
8016
+ }, mergeCommitTransactionOptions(target))
8017
+ );
8018
+ const warnings = [...artifact.review.warnings];
5453
8019
  let provenancePersisted;
5454
8020
  if (provenanceStore !== void 0 && !isErr(provenanceStore)) {
5455
8021
  try {
8022
+ const records = artifact.review.provenanceRecords;
5456
8023
  const count = await persistProvenanceRecords(
5457
8024
  provenanceStore.data,
5458
8025
  target.graphId,
5459
- plan.provenanceRecords
8026
+ records
5460
8027
  );
5461
8028
  provenancePersisted = {
5462
8029
  graphId: provenanceGraphId(target.graphId),
@@ -5464,32 +8031,21 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
5464
8031
  };
5465
8032
  } catch (error) {
5466
8033
  warnings.push(
5467
- "provenance persistence failed (graph committed; provenance not persisted): " + (error instanceof Error ? error.message : String(error))
8034
+ "provenance persistence failed (graph committed; provenance not persisted): " + describeCause(error)
5468
8035
  );
5469
8036
  }
5470
8037
  }
5471
- const report = {
5472
- merged,
5473
- resolutions: plan.resolutions,
5474
- conflicts: plan.propertyConflicts,
5475
- deleteModifyConflicts: plan.deleteModifyConflicts,
5476
- typeReconciliations: plan.typeReconciliations,
5477
- dropped: plan.dropped,
5478
- validityEnds: plan.validityEnds,
5479
- baseAmbiguities: plan.baseAmbiguities,
5480
- provenance,
5481
- warnings,
5482
- ...provenancePersisted === void 0 ? {} : { provenancePersisted }
5483
- };
5484
- return ok(report);
8038
+ return ok(
8039
+ reportFromArtifact(artifact, merged, warnings, provenancePersisted)
8040
+ );
5485
8041
  } catch (error) {
5486
- if (error instanceof MergeError) {
5487
- return err(error);
5488
- }
8042
+ const translated = translateMergeCommitError(error);
5489
8043
  return err(
5490
- new MergeError(
5491
- `Merge failed while staging or committing: ${describeCause(error)}`,
5492
- { cause: error }
8044
+ translated instanceof MergeError ? translated : new MergeError(
8045
+ `Merge plan apply failed: ${describeCause(translated)}`,
8046
+ {
8047
+ cause: translated
8048
+ }
5493
8049
  )
5494
8050
  );
5495
8051
  }
@@ -5512,7 +8068,8 @@ async function merge(store, branches, optionsInput = {}) {
5512
8068
  options,
5513
8069
  false,
5514
8070
  void 0,
5515
- precondition.data
8071
+ precondition.data,
8072
+ commitResolvedMerge
5516
8073
  );
5517
8074
  }
5518
8075
  async function validateForkPointVersions(forkPoint, branches) {
@@ -5931,33 +8488,42 @@ async function commitIncrementalPlan(target, plan, guard) {
5931
8488
  { details: { capability: "transactions" } }
5932
8489
  );
5933
8490
  }
5934
- return withTxConflictRetry(
5935
- () => target.transaction(async (tx) => {
5936
- if (target.revisionTrackingEnabled) {
5937
- await lockRecordedGraphWrite(transactionBackend(tx), target.graphId);
5938
- }
5939
- await assertForkPointUnchanged(guard.forkPoint);
5940
- const nodesApi = tx.nodes;
5941
- const edgesApi = tx.edges;
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
- );
5950
- await assertInheritedTargetUnchanged(nodesApi, edgesApi, guard, plan);
5951
- await validateIncrementalNodeWrites(target, nodesApi, plan);
5952
- await validateIncrementalEdgeWrites(target, edgesApi, plan);
5953
- return applyMergePlan(
5954
- plan,
5955
- nodesApi,
5956
- edgesApi,
5957
- target,
5958
- transactionBackend(tx)
5959
- );
5960
- }, mergeCommitTransactionOptions(target))
8491
+ return runMergeCommit(
8492
+ () => withTxConflictRetry(
8493
+ () => target.transaction(async (tx) => {
8494
+ if (target.revisionTrackingEnabled) {
8495
+ await lockRecordedGraphWrite(transactionBackend(tx), target.graphId);
8496
+ }
8497
+ await assertForkPointUnchanged(guard.forkPoint);
8498
+ const nodesApi = tx.nodes;
8499
+ const edgesApi = tx.edges;
8500
+ await assertBaseResolutionStable(
8501
+ tx,
8502
+ guard
8503
+ );
8504
+ await assertPlannedIdentityIdsFresh(
8505
+ target,
8506
+ transactionBackend(tx),
8507
+ plan
8508
+ );
8509
+ await assertIdentityPeersStable(
8510
+ target,
8511
+ transactionBackend(tx),
8512
+ guard.identityPeerProbe,
8513
+ plan
8514
+ );
8515
+ await assertInheritedTargetUnchanged(nodesApi, edgesApi, guard, plan);
8516
+ await validateIncrementalNodeWrites(target, nodesApi, plan);
8517
+ await validateIncrementalEdgeWrites(target, edgesApi, plan);
8518
+ return applyInternalMergePlan(
8519
+ plan,
8520
+ nodesApi,
8521
+ edgesApi,
8522
+ target,
8523
+ transactionBackend(tx)
8524
+ );
8525
+ }, mergeCommitTransactionOptions(target))
8526
+ )
5961
8527
  );
5962
8528
  }
5963
8529
  async function mergeIncremental(args) {
@@ -5968,18 +8534,7 @@ async function mergeIncremental(args) {
5968
8534
  }
5969
8535
  const options = normalized.data;
5970
8536
  if (options.onBasePropertyConflict !== "flag") {
5971
- return err(
5972
- new InvalidMergeOptionsError(
5973
- 'mergeIncremental() requires onBasePropertyConflict: "flag" (keep-base); a non-keep-base policy could overwrite a newer committed base value with a stale branch value.',
5974
- {
5975
- details: {
5976
- option: "onBasePropertyConflict",
5977
- accepted: "flag",
5978
- received: options.onBasePropertyConflict
5979
- }
5980
- }
5981
- )
5982
- );
8537
+ return err(incrementalBaseConflictPolicyError(options));
5983
8538
  }
5984
8539
  const forkPrecondition = await validateForkPointVersions(forkPoint, branches);
5985
8540
  if (isErr(forkPrecondition)) {
@@ -5991,12 +8546,7 @@ async function mergeIncremental(args) {
5991
8546
  computeSchemaComponent(target)
5992
8547
  ]);
5993
8548
  if (forkSchema !== targetSchema) {
5994
- return err(
5995
- new MergeError(
5996
- "mergeIncremental() requires target and forkPoint to share a schema; schema drift is not supported (the target content may differ \u2014 only the schema must match).",
5997
- { details: {} }
5998
- )
5999
- );
8549
+ return err(incrementalSchemaError());
6000
8550
  }
6001
8551
  const targetBranch = {
6002
8552
  id: COMMITTED_TARGET_BRANCH,
@@ -6015,10 +8565,12 @@ async function mergeIncremental(args) {
6015
8565
  // it is re-established there rather than assumed to have held for the
6016
8566
  // whole of planning (see `assertForkPointUnchanged`).
6017
8567
  forkPoint: { store: forkPoint, version: forkVersion }
6018
- }
8568
+ },
8569
+ void 0,
8570
+ commitResolvedMerge
6019
8571
  );
6020
8572
  }
6021
8573
 
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 };
8574
+ export { BaseVersionMismatchError, BranchError, CandidateSourceError, IdentityMergeConflictError, InvalidMergeOptionsError, InvalidMergePlanError, MERGE_ERROR_CODES, MERGE_OPTION_DEFAULTS, MERGE_PLAN_DIGEST_ALGORITHM, MERGE_PLAN_FORMAT_VERSION, MatchEvidenceError, MergeConflictError, MergeConstraintConflictError, MergeError, MergePlanCapabilityError, MergePlanDigestMismatchError, MergePlanOriginMismatchError, MergePlanSchemaMismatchError, MergePlanTargetMismatchError, MergePlanningStaleError, SimilarityUnavailableError, StaleMergePlanError, UnsupportedMergePlanVersionError, VALIDITY_END_TARGET_PRECEDENCE, applyMergePlan, asBaseVersion, asBranchId, branch, cloneWorkingCopyStrategy, computeBaseVersion, merge, mergeIncremental, normalizeMergeOptions, openProvenanceStore, persistProvenanceRecords, planMerge, planMergeIncremental, provenanceGraphId, readProvenance };
6023
8575
  //# sourceMappingURL=index.js.map
6024
8576
  //# sourceMappingURL=index.js.map