feat: LWW timestamps for bidirectional federation profile & layout sync

Profile and space layout changes on remote instances were being
overwritten by stale data on reconnect. Adds Last-Writer-Wins
timestamps so the client-relay mesh rejects stale writes:

- profile_updated_at column on users table with migration + backfill
- Server LWW guards on PATCH /users/@me and PUT /space-layout
- Bidirectional profileSync: pulls newer remote profiles to home
- LWW layout sync replaces home-authoritative _layoutFromTrueHome flag
- Layout pushes to ALL connected instances in parallel
This commit is contained in:
Jannis Braun
2026-03-12 18:28:37 +01:00
parent acbcf4d4e8
commit 83699d7e91
10 changed files with 294 additions and 115 deletions
+8 -10
View File
@@ -107,7 +107,7 @@ function handleEvent(origin: string, event: ServerEvent): void {
}
}
populateFromReady(origin, event.spaces, event.folders, event.dmChannels, event.spaceLayout);
populateFromReady(origin, event.spaces, event.folders, event.dmChannels, event.spaceLayout, event.layoutUpdatedAt);
// Cache authoritative identity for this origin (federation-safe)
if (!isHome) {
@@ -698,15 +698,13 @@ function handleEvent(origin: string, event: ServerEvent): void {
}
case 'space_layout_updated': {
// Accept layout updates from browsing instance OR true home
const layoutUser = useAuthStore.getState().user;
const isLayoutTrueHome = !!layoutUser?.homeInstance && origin !== '' && (() => {
try { return new URL(origin).host === layoutUser.homeInstance; } catch { return false; }
})();
if (!isHome && !isLayoutTrueHome) break;
const { setSpaceLayout } = useSpaceStore.getState();
setSpaceLayout(event.layout);
useSpaceStore.setState({ folders: event.folders });
// LWW: only accept if incoming timestamp >= current
const incomingTs = event.updatedAt ?? 0;
const currentTs = useSpaceStore.getState()._layoutUpdatedAt;
if (incomingTs >= currentTs) {
useSpaceStore.getState().setSpaceLayout(event.layout);
useSpaceStore.setState({ folders: event.folders, _layoutUpdatedAt: incomingTs });
}
break;
}