fix(spaces): pin visibility and ownerDID as server-authoritative

Automerge CRDT sync could overwrite space visibility when a client
with a stale cached doc reconnects and merges. Now the server
snapshots visibility and ownerDID before processing sync messages
and reverts any client-side changes to these fields.

These fields can only be changed through the authenticated API
(PATCH /api/spaces/:slug), not through CRDT sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-25 18:03:43 -07:00
parent dce608ae1b
commit c0b4250e96
1 changed files with 19 additions and 1 deletions

View File

@ -618,6 +618,10 @@ export function receiveSyncMessage(
const peerState = getPeerSyncState(slug, peerId);
// Snapshot server-authoritative fields before sync merge
const prevVisibility = doc.meta?.visibility;
const prevOwnerDID = doc.meta?.ownerDID;
// Apply incoming sync message
const result = Automerge.receiveSyncMessage(
doc,
@ -625,9 +629,23 @@ export function receiveSyncMessage(
message
);
const newDoc = result[0];
let newDoc = result[0];
const newSyncState = result[1];
// Pin server-authoritative fields — clients must not overwrite these via sync.
// Visibility and ownership can only be changed through the authenticated API.
if (newDoc !== doc) {
const visChanged = newDoc.meta?.visibility !== prevVisibility;
const ownerChanged = newDoc.meta?.ownerDID !== prevOwnerDID;
if (visChanged || ownerChanged) {
console.warn(`[Store] Sync tried to change authoritative fields in ${slug} — reverting (vis: ${prevVisibility}${newDoc.meta?.visibility}, owner: ${prevOwnerDID}${newDoc.meta?.ownerDID})`);
newDoc = Automerge.change(newDoc, 'Pin server-authoritative fields', (d) => {
if (visChanged && prevVisibility) d.meta.visibility = prevVisibility;
if (ownerChanged && prevOwnerDID) d.meta.ownerDID = prevOwnerDID;
});
}
}
communities.set(slug, newDoc);
peerState.syncState = newSyncState;