0007. The server verifies a result checksum on every push
- Status: Accepted
- Date: 2026-10-01 (recorded with server-verified sync, commit
c9fc15e)
Context
With step-based saving (0006), a step is positional: it means something only relative to the exact document the client holds. If a host has a bug (appends loaded chunks as ordinary edits, syncs before loading finished, uses a wrong version, or a cache is corrupt), its steps may apply cleanly on the server and silently corrupt the shared document. Folio is embedded by many hosts; the server must not trust any of them to be implemented correctly.
Decision
Every push carries checksum: the docChecksum of the client's document after its steps. The server applies the steps to its own copy and compares; on mismatch nothing is applied and it answers rejected: 'diverged'. The SyncClient then stops syncing for good and keeps the edits in the outbox for recovery; the host reopens from the server. Checksums are order-sensitive hashes of the top-level blocks, with each block's hash memoized by ProseMirror node identity. requireChecksum defaults to true and should never be disabled in production.
Alongside it: chunks are verified against their content address on load (IntegrityError on a corrupt response; corrupt cache entries are refetched), and a mass-delete guard refuses pushes that would delete over half of a large document unless the user confirms (needs-confirmation, confirmPending).
Consequences
- A buggy or partial client cannot corrupt the server copy; this is pinned by tests that simulate the bugs (chunks appended as edits, syncing a partial load, tampered cache entries and responses).
- The cost is small: ~60 ms cold for 35k blocks (warmed at start, off the save path) and ~4 ms per save.
- Client and server must compute identical checksums, so backends reuse the DOM-free
docChecksum(and the same schema) rather than reimplementing it. - A diverged client needs a recovery path in the host UI (reopen, recover edits from the outbox).