chore(release): memory-core 0.3.1 - durable couch tombstones + resilient syncNow#17
Merged
Merged
Conversation
…ent syncNow
Delete reconciliation for the couch channel and syncNow resilience parity.
FRICTION 1 (data integrity): a failed removeFile tombstone was fire-once and
silently lost, so a deleted note could resurrect on a later pull. Now:
- CouchSyncState carries an optional `deletions` set (loads fine from 0.3.0
snapshots that lack it); removeFile self-enqueues on transport failure and
flushPending retries deletions alongside pending pushes.
- pushAll/syncNow reconcile local deletions using the rev cache as the
disambiguator: a known (rev-cached) path absent from the file set is a local
deletion -> tombstone + drop rev; a couch doc with no rev-cache entry is new
remote content -> pull writes it (unchanged). The pull-delete path already
drops the rev, so it never resurfaces as a phantom local deletion.
- Stale-delete race: a tombstone whose rev advanced (409, or couch content no
longer matches the cached rev) is abandoned; the next pull re-delivers the
newer remote version (edit-wins-over-stale-delete). Never force-deletes.
FRICTION 2 (resilience): syncNow now catches push/pull errors like tick() and
returns a SyncResult { pushed, pulled, error? } instead of throwing.
Tests: +9 channel tests (delete durability, state shape round-trip + old-shape
load, reconciliation both directions, phantom-delete guard, 409 restore,
syncNow swallow). All 154 tests green; doc model and byte-compat fixtures
untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two verified frictions in the couch channel (
src/channel/), surfaced by the CLI integration. Public repo, patch bump 0.3.0 -> 0.3.1. Zero new deps; doc model and byte-compat fixtures untouched.Friction 1 - a failed tombstone was silently lost (data integrity)
CouchSync.removeFilewas fire-once. UnlikepushFileLive(which self-enqueues on failure), a tombstone that failed mid-outage (network down, 401) was dropped:pushAll/syncNowonly reconciled existing files, so the couch doc survived forever and the deleted note could resurrect on a later pull (and persisted on other devices).Fix, built inside the module's existing idioms:
CouchSyncStategains an optionaldeletions: string[]set (parallel topending). Old 0.3.0 snapshots that lack the field load fine (?? []).removeFilenow self-enqueues a pending deletion on transport failure and clears it on a terminal outcome;flushPendingretries deletions alongside pending pushes.pushAll(and thussyncNow) reconcile deletions using the rev cache as the disambiguator: a path present in the rev cache (we synced it) but absent fromFileStore.listMarkdown= locally deleted -> issue the tombstone, then drop the rev-cache entry. A couch doc with no rev-cache entry is new remote content -> pull writes it (unchanged behavior).Friction 2 -
syncNowresilience asymmetrytick()caughtpullOnceerrors;syncNow()did not (it threw).syncNownow catches both push and pull failures (records, does not throw) and returns aSyncResult { pushed, pulled, error? }so callers still learn success/failure. Old callers awaitingvoidremain source-compatible.Tests
+9 channel tests; all 154 green (was 145, none changed):
syncNowswallows pull errors and reports statusBehavior change for existing consumers
None beyond delete durability.
syncNow's return changesvoid->SyncResult(additive; awaiting it still compiles).removeFileis now never-throw (was able to throw on transport error) - strictly safer.Downstream port candidate (not touched here)
The Obsidian plugin (
agentage/obsidian-sync) carries its own copy of these channel modules with the same fire-once tombstone gap. Left untouched per scope; flag as a follow-up port.