Skip to content

A merge keeps the identity evidence of the name it dedupes - #1466

Merged
WaylandYang merged 2 commits into
deeplethe:devfrom
Aschenbath:fix/merge-keeps-identity-evidence-1426
Oct 30, 2026
Merged

WaylandYang merged 2 commits into
deeplethe:devfrom
Aschenbath:fix/merge-keeps-identity-evidence-1426

Conversation

@Aschenbath

Copy link
Copy Markdown
Contributor

Closes #1426.

What this does

merge_entities invalidates a duplicate fact (same subject, predicate, object and valid_from after the merge) but copied only its typed_fact_sources, not its fact_evidence. If the duplicate was a name with evidence from an identity source (0071) and the survivor was the same name from prose, recorded earlier, the survivor lost its identity status. Decision 2's exact-name attach and decision 7's hold (#1425) then stopped applying to it.

  • Merge. Before invalidating the duplicates, the merge copies their evidence onto the surviving fact, one row per chunk. Evidence the survivor already has from a chunk is kept as it is (ON CONFLICT DO NOTHING). If a copied row is identity evidence, the survivor's name vector is deleted in the same transaction (0071 decision 5), through a new name_vectors::drop_with_identity_evidence.
  • Ledger. The merge records the pairs it copied in entity_merges.copied_evidence_facts / copied_evidence_chunks (migration 0125).
  • Revert. revert_merge deletes those pairs before it revives the duplicates, which still carry their own evidence. It follows supersedes from each recorded fact, the chain with_rewrites walks, because a corrected interval, a timeline close or the revert's own rehome copies evidence onto a new row. Evidence the survivor had before the merge stays. If the survivor no longer has identity evidence, its name is pending again and the next extraction gives it back a vector, as when a source turns the identity flag off (decision 6).

The duplicate's evidence is copied, not moved, as the typed_fact_sources copy just above it is, so the revert has it in place.

Record

This makes ADR 0071 hold across a merge and doesn't overturn any of its decisions. Its status line now names migration 0125, a revision note below decision 7 records the change, and docs/design/identity.md says what revert_merge takes back.

Open

  • If the same (fact, chunk) pair is written again by an extraction after the merge, the insert sees the copied row and leaves it. A revert then deletes it, and the evidence returns with the next extraction of that chunk. The ledger can't tell those apart without changing every evidence writer, and I think that trade is acceptable.
  • In a chain S→T→C, undoing only S→T leaves S's identity evidence on C's name, because the copy onto C belongs to the T→C ledger. I measured this with a throwaway test: one identity row on C after undoing the first link, none after undoing both. Until then C is treated as an identity entity, so a merge touching it waits for a person and its name has no vector. That errs toward review, so I left it.
  • CURRENT_SCHEMA_VERSION goes to 111. A base can opt into confirming well-grounded mapping definitions, and a person can take any of them back #1332 also takes 111 with 0124, so whichever lands second moves to 112.

How it was checked

In a Linux container with pgvector/pgvector:pg16, commit 516c22cf on dev 1d94b532:

  • cargo fmt --all --check and cargo clippy --locked --workspace --all-targets -- -D warnings: clean.
  • cargo test --locked --workspace --no-fail-fast: 1,809 passed, 6 ignored, 1 failed. The failure is real_postgres_backup_and_restore_require_force_and_preserve_files_on_sql_failure, which needs pg_dump. My check image doesn't have it, so CI is the evidence for that test.
  • With the evidence copy disabled, a_merge_keeps_the_identity_evidence_of_the_name_it_dedupes fails.
  • With the revert deleting only the recorded (fact, chunk) pairs, undoing_a_merge_takes_the_copied_evidence_off_a_rewritten_name fails: the corrected name still holds both chunks. It passes with the supersedes walk, for a survivor from either side of the merge.

🤖 Generated with Claude Code

…surviving fact, and undoing the merge should take back only what it copied.

Signed-off-by: Aschenbath <Aschenbath@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@WaylandYang WaylandYang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this reads well and the supersedes walk is the right call. Merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants