Skip to content

fix(collab): stop /documents/:slug/ops replaying stale DB markdown into live rooms#68

Open
devYRPauli wants to merge 1 commit into
EveryInc:mainfrom
devYRPauli:fix/rest-ops-live-reapply
Open

fix(collab): stop /documents/:slug/ops replaying stale DB markdown into live rooms#68
devYRPauli wants to merge 1 commit into
EveryInc:mainfrom
devYRPauli:fix/rest-ops-live-reapply

Conversation

@devYRPauli

Copy link
Copy Markdown

Problem

POST /documents/:slug/ops reapplies the persisted DB row into the live collab room after any non-rewrite.apply mutation, using source: 'rest-ops':

const applyOptions = {
  markdown: typeof updatedDoc.markdown === 'string' ? updatedDoc.markdown : undefined,
  marks: parseJson(updatedDoc.marks),
  source: 'rest-ops',
};
await applyCanonicalDocumentToCollab(slug, applyOptions);

Unlike every other server-side write source, rest-ops is not covered by shouldBlockLegacyLiveApplySource, so this reapply is not blocked when a live shared doc is present. A point-in-time DB snapshot can then replay stale markdown over concurrent edits that are not yet persisted.

The repo's own collab-security invariant already asserts this reapply should be gone, so that check fails on main:

Expected /documents/:slug/ops to avoid broad DB reapply after engine-backed mutations

Fix

Engine-backed (non-rewrite.apply) ops now only refresh the narrow presence/cursor coupling and no longer push a broad DB snapshot into the live room. rewrite.apply continues to commit through the canonical Yjs path, and the trailing document.updated broadcast still notifies live clients. This mirrors the pattern the agent routes and bridge already follow (presence/verification sync, no broad reapply).

Verification

The collab-security invariant for this reapply flips from failing to passing:

  • before (main): routesSource.includes("source: 'rest-ops'") is true, assertion fails
  • after: the source: 'rest-ops' reapply is gone and the explanatory comment is present, assertion passes

tsc --noEmit -p tsconfig.server.json reports 0 errors before and after (no new type errors). The change is 6 insertions / 8 deletions in one file.

Fixes #63

…to live rooms

After a non-rewrite.apply op succeeds, the route reapplied the persisted DB
row into the live collab room with source 'rest-ops'. Unlike other
server-side write sources, 'rest-ops' is not covered by the legacy
live-apply guard (shouldBlockLegacyLiveApplySource), so a point-in-time DB
snapshot could replay stale markdown over concurrent live edits that were
not yet persisted.

Engine-backed (non-rewrite.apply) ops now only refresh the narrow
presence/cursor coupling and no longer push a broad DB snapshot into the
live room; rewrite.apply continues to commit through the canonical Yjs
path. This restores the collab-security invariant that /documents/:slug/ops
avoids a broad DB reapply after engine-backed mutations.

Fixes EveryInc#63
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

POST /documents/:slug/ops: route-level DB reapply (source: 'rest-ops') can replay stale markdown into live collab rooms

1 participant