@@ -517,11 +517,12 @@ const fileDocMergeChains = new Map<string, Promise<unknown>>()
517517
518518/**
519519 * How a merge is positioned on the file's version line — mirrors the sim-side `LiveFileDocMergeOrder`
520- * wire fields. A durable `version` is checked AND recorded; a streaming `streamedAt` is checked only.
520+ * wire fields. A durable `version` is checked AND recorded; a streaming `baseVersion` (the durable version
521+ * the snapshot was built from) is checked only — dropped if a newer durable write has since landed.
521522 */
522523interface MergeOrder {
523524 version ?: number
524- streamedAt ?: number
525+ baseVersion ?: number
525526}
526527
527528/**
@@ -568,15 +569,15 @@ async function mergeMarkdownIntoRoom(
568569 name : string ,
569570 fileId : string ,
570571 markdown : string ,
571- { version, streamedAt } : MergeOrder
572+ { version, baseVersion } : MergeOrder
572573) : Promise < 'applied' | 'no-live-room' | 'merge-unavailable' | 'stale' > {
573574 const store = getFileDocStore ( )
574575
575576 // The durable version this merge carries is now incorporated in the live doc — record it (cluster-wide
576577 // in Redis for multi-task, plus this task's room) so the persist If-Match guard treats this write as
577578 // synced rather than an out-of-band conflict. AWAITED so the version is durable before the merge lock
578579 // releases, so the next lock holder's staleness check (below) reads a consistent value. Only a durable
579- // `version` is recorded — a streaming `streamedAt ` orders the merge (below) but is never a checkpoint,
580+ // `version` is recorded — a streaming `baseVersion ` orders the merge (below) but is never a checkpoint,
580581 // so the synced version stays pinned to the last durable write.
581582 const recordVersion = async ( ) => {
582583 if ( version === undefined ) return
@@ -588,21 +589,24 @@ async function mergeMarkdownIntoRoom(
588589 await store . setSyncedVersion ( name , version )
589590 }
590591
591- // Position this merge on the file's monotonic version line: a durable write by its `version`, a
592- // streaming snapshot by its `streamedAt` production time. A merge not NEWER than the version the doc
593- // already incorporates is stale — a newer durable write already landed (possibly on another process,
594- // out of dispatch order). Diffing toward its older markdown would regress the live doc while the
595- // monotonic token stays high, so a later persist could write that stale content back over the durable
596- // file. Skip it. This is what stops a delayed streaming snapshot from clobbering a newer durable merge
597- // across processes (the per-process caller chain cannot). A merge with neither key is never stale.
598- //
599- // Only durable `version` (DB-monotonic `contentUpdatedAt`) is ever recorded, so the durable line is
600- // immune to clock skew. `streamedAt` is best-effort wall-clock used ONLY to order transient streaming
601- // snapshots; correctness never depends on it — a skewed clock at worst drops or briefly regresses the
602- // live preview, which the next durable write reconciles.
603- const orderingVersion = version ?? streamedAt
604- const isStale = ( current : number ) : boolean =>
605- orderingVersion !== undefined && orderingVersion <= current
592+ // Order this merge on the file's version line, where `current` is the durable version the doc already
593+ // incorporates. Both keys are DB-monotonic `contentUpdatedAt` values (no wall-clock), so ordering is
594+ // immune to clock skew:
595+ // - A durable `version` is stale if it is NOT strictly newer than `current` — a newer durable write
596+ // already landed (possibly on another process, out of dispatch order); applying its older markdown
597+ // would regress the doc while the monotonic token stays high.
598+ // - A streaming `baseVersion` (the durable version the snapshot was built from) is stale if `current`
599+ // has moved PAST it — a newer durable write landed since the snapshot's base, so diffing the live
600+ // doc back toward the snapshot would clobber that write's content (which a later persist, still
601+ // holding the current If-Match token, would then write over the durable file). This is what stops a
602+ // concurrent human edit from being silently lost; the per-process caller chain cannot see it.
603+ // A merge with neither key is never stale (legacy, unordered). Only a durable `version` is recorded,
604+ // so a streaming snapshot never advances the synced version — the final `edit_content` write does.
605+ const isStale = ( current : number ) : boolean => {
606+ if ( version !== undefined ) return version <= current
607+ if ( baseVersion !== undefined ) return current > baseVersion
608+ return false
609+ }
606610
607611 if ( store . enabled ) {
608612 // Serialize merges to this file ACROSS tasks — the per-file chain above only covers this process.
0 commit comments