From 37fc52d85bf363ede18f265ee93f418d4e782b82 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:08:22 +0200 Subject: [PATCH] fix(migration): require at least one stub in merge pair to prevent false positives Two real accounts from the same remote instance sharing a 1-on-1 DM are different people, not duplicates. The shared DM is a legitimate relay. Only merge when at least one user has passwordHash = '!federation-replicated' (a relay-created stub). --- packages/server/src/db/migrate.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/server/src/db/migrate.ts b/packages/server/src/db/migrate.ts index 06a34f91..35aa53ba 100644 --- a/packages/server/src/db/migrate.ts +++ b/packages/server/src/db/migrate.ts @@ -698,8 +698,12 @@ export function runMigrations(db: Database.Database): void { // different homeUserId (e.g., auth registration vs S2S relay). Now that // homeInstance is normalized to bare domain, we can detect and merge duplicates. // + // Safety guard: at least one user in the pair must be a stub + // (passwordHash = '!federation-replicated'). Two real accounts from the same + // instance are different people who share a relayed DM, not duplicates. + // // Detection criteria (at least one must match, PLUS same homeInstance domain): - // 1. Shared 1-on-1 DM membership (strongest signal) + // 1. Shared 1-on-1 DM membership (only when at least one is a stub) // 2. Username cross-reference (one's homeUserId in the other's username base) // 3. homeUserId cross-match (same homeUserId, missed due to old format mismatch) // @@ -738,6 +742,13 @@ export function runMigrations(db: Database.Database): void { for (let j = i + 1; j < users.length; j++) { const a = users[i]!; const b = users[j]!; + + // Safety guard: at least one must be a stub. Two real accounts from the + // same instance are different people (they share relayed DMs, not identities). + const aIsStub = a.password_hash === '!federation-replicated'; + const bIsStub = b.password_hash === '!federation-replicated'; + if (!aIsStub && !bIsStub) continue; + let reason: string | null = null; // Criterion 1: shared 1-on-1 DM membership