Skip to content

refactor(workspace): unify YKeyValueLww reconciliation - #2447

Draft
braden-w wants to merge 3 commits into
mainfrom
codex/y-keyvalue-lww-resolution-pipeline
Draft

refactor(workspace): unify YKeyValueLww reconciliation#2447
braden-w wants to merge 3 commits into
mainfrom
codex/y-keyvalue-lww-resolution-pipeline

Conversation

@braden-w

@braden-w braden-w commented Jul 10, 2026

Copy link
Copy Markdown
Member

YKeyValueLww had two conflict resolvers: constructor hydration and a roughly 180-line observer callback. They implemented the same timestamp and positional policy through different branch trees, while overlay cleanup depended on Yjs net-delta entries that do not include add-then-delete operations.

This makes reconciliation one owned transition:

constructor snapshot ─┐
                      ├─> reconcile affected keys
Y.Array event facts ──┘     -> select one winner per key
                             -> update the confirmed index
                             -> delete visible losers
                             -> emit one original-origin batch

selectWinner is the single policy boundary: higher timestamp wins, with final Y.Array position deciding equal timestamps. The transaction-local read state is one discriminated, transaction-owned overlay rather than independent write and delete collections. Constructor hydration and live events preserve the same stored entry objects, so unchanged values keep the reference identity required by ADR-0077.

The no-conflict path stays delta-sized. Array snapshots and entry-position maps remain lazy, so unique bulk inserts avoid an O(total rows) scan while bulk conflicts still resolve from one snapshot. Single-key writes keep eager cleanup; a final delete now removes every visible version for the key, preventing repeated unconfirmed writes from resurrecting it.

Persisted { key, val, ts } data, the published class and ObservableKvStore surfaces, original transaction origins, cleanup transactions, observer batching, stable references, and existing event actions remain unchanged.

One existing boundary remains deliberately unresolved: a public change handler that writes while conflict cleanup is queued joins the private cleanup transaction in Yjs. Defining whether that write should emit a second public batch, and under which origin, is an event-contract decision outside this internal ownership rewrite.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Route constructor hydration and live Y.Array events through one key-scoped pipeline. A transaction-owned overlay preserves local reads, while one winner boundary owns timestamp and positional conflict policy without sacrificing the no-conflict or bulk paths.

Delete every visible version on a final local delete so repeated writes cannot resurrect a key before observer confirmation.
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

App Preview URL
whispering https://pr-2447-whispering.epicenter.workers.dev
landing https://pr-2447-landing.epicenter.workers.dev
opensidian ❌ build or upload failed (see logs)
honeycrisp https://pr-2447-honeycrisp.epicenter.workers.dev
vocab uploaded (no preview URL returned)

Previews update automatically with new commits to this PR. No cleanup needed: previews are version aliases on the existing workers.

Commit 00accb7

braden-w added 2 commits July 10, 2026 16:16
Use the additions map as the affected-key owner and require every reconciliation caller to name its cleanup origin. This removes duplicate bookkeeping and the optional cleanup branch without changing winner selection or transaction origins.
Deleted as REDUNDANT WITH A CALLER:
- set stores value and get retrieves it
- delete removes value
- has returns true for a key set earlier
- get returns an updated value for an existing key
- delete removes a key set earlier
- set after delete restores the key
- entries yields values set earlier
- entries prefers the transaction-local value
- multiple transactions preserve prior keys
- both standalone observer batching tests
- transaction-local LWW still works
- rapid set/get cycles
- delete-then-get and delete-then-set
- remote set after local delete
- final set-delete-set and delete-set-delete
- delete overlay wins after set-delete

Deleted overlay-cleanup implementation checks as STRUCTURE-COUPLED, and removed the remote-different-key case because its setup had already closed the delete transaction before the remote update.

The resolution matrix, mixed-operation test, and accessor-specific entries tests retain the observable coverage. Add, update, and delete events now share one lifecycle test.
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.

1 participant