+- **an imported retraction came back as current on the LadybugDB backend.** `superseded_by` was reconstructed from the `SUPERSEDED_BY` edge, and `add()` cannot create an edge towards a claim the store does not have yet — in the natural import order it does not have it, because `all_claims()` returns oldest-first, so a superseded claim arrives before the claim that superseded it. The edge was skipped while `superseded_at` was written, so one row said both things at once: `superseded_at` said retracted and `superseded_by IS NULL` said current, and `Claim.is_current` believed the second. `current("svc")` — which is supposed to answer "what is true now" — returned *both sides of a correction*, and the two other backends disagreed with this one. Every import, replay, backup restore and copy between backends went through that path; `supersede()` never did, which is why every test that exercised it passed. `superseded_by` is a column now and the edge is derived from it: reads project the column, `current()` filters on the column rather than on an `OPTIONAL MATCH`, and `_write` reconciles the edge in both directions — forward when this claim names a superseder that is present, backward when a stored claim names *this* one and could not have an edge until now — so the edge is complete once both ends have arrived, whatever order they arrived in, and it stays walkable for the Cypher path this backend exists for. Two alternatives were costed and rejected: failing closed breaks replaying `all_claims()` in the order it hands you, and a stub target node reads back as a `ValidationError` and has no `seq`. The column needs a schema change, which `CREATE NODE TABLE IF NOT EXISTS` will not perform on an existing database, so opening one adds the column and backfills it from the edges an older database does have — `supersede()` always wrote those. What `add()` dropped is not recoverable; what the edges recorded is.
0 commit comments