Skip to content

fix: make children filters causally complete#184

Open
marcus-pousette wants to merge 2 commits into
fix/materialization-net-outcomesfrom
fix/children-filter-closure
Open

fix: make children filters causally complete#184
marcus-pousette wants to merge 2 commits into
fix/materialization-net-outcomesfrom
fix/children-filter-closure

Conversation

@marcus-pousette

@marcus-pousette marcus-pousette commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

The bug

opRefs.children(parent) is meant to return the operations needed to replay that parent's direct-child view. It was not always replay-complete.

The stored parent index could follow a requested parent even when a cycle/sentinel move was rejected, and it could miss committed effects such as moving a child away or restoring a deleted ancestor. The WASM adapter had a second problem: it selected only inserts and moves whose requested destination was parent, so it omitted payload, delete, tombstone, move-away, and descendant operations that can change the direct-child result.

A peer replaying that filtered set could therefore show stale or missing children even though full-log replay converged correctly.

What changes

  • Build parent-index entries from committed materialization effects, including actual old/new parents and delete/restore side effects.
  • Exclude rejected structural requests from the requested parent's index.
  • Keep the current payload winner discoverable after a move.
  • Make WASM use the same core ParentOpIndex semantics and return the selected operations in canonical operation order.

Replaying the returned closure from an empty tree now reproduces the same direct-child result as full replay. The closure can include a descendant operation when that operation is what restores a direct child. This is a convergence filter, not a confidentiality boundary; authorization still belongs in the sync/auth layer.

Fast paths and tradeoff

  • SQLite and PostgreSQL continue to answer from their persisted parent-op index; there is no full-log query replay on those adapters.
  • WASM has no persisted parent-op index, so it currently replays and scans the operation log at query time. That is O(n) per opRefs.children query and uses no additional persistent state. It is the correct fallback today and can later be replaced with an in-memory maintained index without changing behavior.
  • Normal operation replay/materialization remains incremental; the full replay is limited to this WASM query path.

Tests

Coverage includes:

  • descendant operations that restore a direct child;
  • moving a child away, payload changes, deletes, and tombstones;
  • rejected cycles and sentinel targets;
  • out-of-order appends and canonical result ordering;
  • shared Memory/SQLite/PostgreSQL conformance scenarios.

Rust, WASM runtime, TypeScript, formatting, and strict Clippy checks pass locally. CI re-runs on the published head.

Size

12 files, +905/-90 relative to #178: +325/-90 production, +574 tests/conformance, and a 6-line changeset.

Stack

Stacked after #178. Merge order: #177#180#178 → this PR.

@marcus-pousette marcus-pousette added bug Something isn't working area:core CRDT semantics, canonical log, ordering, operation identity, and core Rust area:api Public interfaces, bindings, events, and cross-backend contracts labels Jul 12, 2026
@marcus-pousette marcus-pousette changed the title fix: close children filter dependencies fix: make children filters causally complete Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:api Public interfaces, bindings, events, and cross-backend contracts area:core CRDT semantics, canonical log, ordering, operation identity, and core Rust bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant