You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SqliteStore::merge_nodes_in_tx (the node-ID-conflict/dedup merge path, packages/core/src/db/sqlite_store/conflicts.rs around line 461) re-points relationship edges when merging a duplicate node into a survivor via a raw UPDATE relationship SET in_node = ?, out_node = ? — it never checks cardinality or reverse_cardinality, and bypasses create_relationship/create_relationship_in_tx entirely.
Why it matters
core#2804 added enforcement of cardinality: One and reverse_cardinality: One at create_relationship/create_relationship_in_tx — the only two write paths that create a relationship edge through the normal API. merge_nodes_in_tx is a third path that also produces (repoints) relationship edges, and it has no equivalent check.
Concretely: if node A and node B are both sources of a cardinality: One relationship (or both point at the same reverse_cardinality: One target from different declaring nodes), and a dedup merge re-points one of A's or B's edges onto the survivor of a conflict resolution, the survivor can end up with two live edges into (or out of) a cardinality-one end — silently violating the exact invariant core#2804 exists to enforce, through a code path that was never touched by that fix.
This is pre-existing behavior, not something core#2804 introduced — but core#2804 is what makes this invariant load-bearing for the first time (declared relationships now assume create_relationship is the only door in). Found during review of core#2804's implementation.
Suggested approach
Either route merge_nodes_in_tx's edge re-pointing through the same cardinality/reverse_cardinality checks create_relationship_in_tx uses (reusing that logic rather than duplicating it), or
After a repoint, detect and resolve any resulting cardinality violation (e.g. keep the survivor's edge, drop the loser's, the same way create_relationship's replace semantics would have).
Acceptance criteria
merge_nodes_in_tx cannot leave a cardinality: One source or reverse_cardinality: One target with more than one live edge after a merge
Test: two nodes each hold a cardinality-one-conflicting edge before a merge; after merging one into the other, exactly one edge remains
Existing merge_nodes tests (merge_nodes_test.rs) still pass unchanged
Activity
mstomar125 commentedon Sep 25, 2026
Frontend baseline: 5268 passed, 1 failed (pre-existing flaky perf test architecture-benchmarks.test.ts cold-cache timing, unrelated to this Rust conflict-resolution fix). Test Files: 261 passed, 1 failed (262 total).