diff --git a/packages/server/src/db/migrate.ts b/packages/server/src/db/migrate.ts index 61c98162..6724799c 100644 --- a/packages/server/src/db/migrate.ts +++ b/packages/server/src/db/migrate.ts @@ -789,19 +789,9 @@ export function runMigrations(db: Database.Database): void { let reason: string | null = null; - // Criterion 1: shared 1-on-1 DM membership - if (!reason) { - const shared = db.prepare(` - SELECT m1.dm_channel_id - FROM dm_members m1 - JOIN dm_members m2 ON m1.dm_channel_id = m2.dm_channel_id - JOIN dm_channels c ON c.id = m1.dm_channel_id - WHERE m1.user_id = ? AND m2.user_id = ? - AND c.owner_id IS NULL - LIMIT 1 - `).get(a.id, b.id) as { dm_channel_id: string } | undefined; - if (shared) reason = `shared 1-on-1 DM channel ${shared.dm_channel_id}`; - } + // Criterion 1 removed: "shared 1-on-1 DM membership" was wrong — + // two users from the same domain sharing a DM channel doesn't mean + // they're duplicates, it means they're having a conversation. // Criterion 2: username cross-reference if (!reason) { diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index 20fde9fd..86f6473d 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -1208,8 +1208,17 @@ export async function federationRoutes(app: FastifyInstance): Promise { sourceUrl: `${localOrigin}/api/uploads/${a.filename}`, })); + // Include federatedId for group DMs so the peer uses the correct + // channel lookup path instead of computing a 1-on-1 pair hash. + const syncChannel = db + .select({ federatedId: schema.dmChannels.federatedId, ownerId: schema.dmChannels.ownerId }) + .from(schema.dmChannels) + .where(eq(schema.dmChannels.id, mutation.context_id)) + .get(); + events.push({ eventType: mutationType, + ...(syncChannel?.federatedId && syncChannel.ownerId ? { federatedId: syncChannel.federatedId } : {}), dmChannelId: mutation.context_id, messageId: message.id, encryptionVersion: 0, @@ -1550,6 +1559,13 @@ function backfillHomeUserId( db: ReturnType, ): typeof schema.users.$inferSelect { if (user.homeUserId === homeUserId) return user; + // Only backfill if the user has no homeUserId yet. If they already have a + // DIFFERENT non-null homeUserId, this means the wrong user was matched — + // overwriting would corrupt their identity. + if (user.homeUserId) { + console.warn(`[federation] Refusing to overwrite homeUserId on user ${user.id} (${user.username}): existing=${user.homeUserId}, incoming=${homeUserId}`); + return user; + } db.update(schema.users) .set({ homeUserId }) .where(eq(schema.users.id, user.id))