Current baseline
The protocol already behaves as if SyncBackend.applyOps() is a read-after-write boundary.
In particular:
syncOnce() waits for responder-side applyOps() before sending the final done ack
- protocol tests already assert that uploaded ops are applied before
syncOnce() resolves
However, the SyncBackend interface itself does not currently say whether writes must be visible through reads before applyOps() resolves.
That mismatch has led to confusion in engine/worker-backed environments and to helper wrappers that effectively add a flush/queue layer outside the core interface.
Problem
SyncBackend.applyOps() does not currently document whether the effects of the write must already be observable through:
listOpRefs()
getOpsByOpRefs()
maxLamport()
before the returned promise resolves.
The ambiguity is the bug. The protocol and tests already lean on one answer, but the type-level contract does not state it clearly.
What this issue tracks now
Document and align the contract as:
applyOps() MUST NOT resolve until newly applied ops are visible to subsequent backend reads for the same doc
- that visibility includes at least
listOpRefs(), getOpsByOpRefs(), and maxLamport()
- callers should not need a separate public
flush()/settle() step just to observe their own completed write
Follow-up work
- Update
SyncBackend docs/comments to state this explicitly.
- Align conformance comments/tests that still imply async backends may require post-
applyOps polling.
- Keep helper wrappers like
FlushableSyncBackend as test/adapter utilities rather than the normative interface contract.
Context
This came up while debugging async/worker-backed backend behavior around PR #110. The right fix is to make the contract explicit and align the remaining comments/tests with the behavior the protocol already expects.
Current baseline
The protocol already behaves as if
SyncBackend.applyOps()is a read-after-write boundary.In particular:
syncOnce()waits for responder-sideapplyOps()before sending the final done acksyncOnce()resolvesHowever, the
SyncBackendinterface itself does not currently say whether writes must be visible through reads beforeapplyOps()resolves.That mismatch has led to confusion in engine/worker-backed environments and to helper wrappers that effectively add a flush/queue layer outside the core interface.
Problem
SyncBackend.applyOps()does not currently document whether the effects of the write must already be observable through:listOpRefs()getOpsByOpRefs()maxLamport()before the returned promise resolves.
The ambiguity is the bug. The protocol and tests already lean on one answer, but the type-level contract does not state it clearly.
What this issue tracks now
Document and align the contract as:
applyOps()MUST NOT resolve until newly applied ops are visible to subsequent backend reads for the same doclistOpRefs(),getOpsByOpRefs(), andmaxLamport()flush()/settle()step just to observe their own completed writeFollow-up work
SyncBackenddocs/comments to state this explicitly.applyOpspolling.FlushableSyncBackendas test/adapter utilities rather than the normative interface contract.Context
This came up while debugging async/worker-backed backend behavior around PR #110. The right fix is to make the contract explicit and align the remaining comments/tests with the behavior the protocol already expects.