Summary
writeHydration in apps/server/src/library/repo/hydrate.ts updates rows with WHERE eq(libraryItems.id, update.id) only — it does not scope by userId.
library_items has a composite primary key (user_id, id), where id = "<mediaType>:<tmdbId>" is unique only within a user (this is the multi-tenant fix from the library backend design — the same title can be owned by many users).
So when two users own the same title, one user's hydrate pass UPDATEs every user's row for that title, overwriting the others' sort_title, year, genres, servers, quality_tiers, watched_state, collection_*, and hydrated_at with the running user's values.
Impact
- Cross-user data corruption of the denormalized browse projection.
- One user's availability/quality chips, watched state, and sort key leak onto another user's row.
- Silent — no error, the projection just shows wrong data for whichever user didn't hydrate last.
Where
writeHydration loops:
await db
.update(libraryItems)
.set({ /* sortTitle, year, genres, servers, qualityTiers, watchedState, collection*, hydratedAt */ })
.where(eq(libraryItems.id, update.id)); // <-- missing userId
Fix
Scope the WHERE by the full composite key, mirroring the sibling writeProjection (added alongside the eager-projection fix), which already does this correctly:
.where(and(eq(libraryItems.userId, userId), eq(libraryItems.id, update.id)))
userId needs threading from the hydrate orchestrator into writeHydration — hydrate already has ctx.userId.
Tests
Add a multi-user regression test: two users own the same tmdbId, hydrate user A, assert user B's row is untouched.
Notes
Found 2026-06-03 while fixing the library first-paint bugs (eager metadata projection). The new writeProjection writer is correctly scoped; writeHydration was left as-is to stay surgical and flagged with an inline NOTE comment in repo/hydrate.ts.
Summary
writeHydrationinapps/server/src/library/repo/hydrate.tsupdates rows withWHERE eq(libraryItems.id, update.id)only — it does not scope byuserId.library_itemshas a composite primary key(user_id, id), whereid = "<mediaType>:<tmdbId>"is unique only within a user (this is the multi-tenant fix from the library backend design — the same title can be owned by many users).So when two users own the same title, one user's hydrate pass
UPDATEs every user's row for that title, overwriting the others'sort_title,year,genres,servers,quality_tiers,watched_state,collection_*, andhydrated_atwith the running user's values.Impact
Where
writeHydrationloops:Fix
Scope the
WHEREby the full composite key, mirroring the siblingwriteProjection(added alongside the eager-projection fix), which already does this correctly:userIdneeds threading from thehydrateorchestrator intowriteHydration—hydratealready hasctx.userId.Tests
Add a multi-user regression test: two users own the same
tmdbId, hydrate user A, assert user B's row is untouched.Notes
Found 2026-06-03 while fixing the library first-paint bugs (eager metadata projection). The new
writeProjectionwriter is correctly scoped;writeHydrationwas left as-is to stay surgical and flagged with an inlineNOTEcomment inrepo/hydrate.ts.