Skip to content

CRDT merge semantics for non-root entities during sync #1977

@xilosada

Description

@xilosada

Context

Discovered during PR #1972 review (cursor[bot] comment).

Issue

In crates/storage/src/interface.rs:save_internal() (lines 980-983), non-root entities use LWW (Last-Write-Wins) when the incoming timestamp is newer:

} else {
    // Incoming is newer - use it (LWW for non-root entities)
    data.to_vec()
}

This means:

  • Root entities: Always CRDT merge ✅
  • Non-root + same timestamp: CRDT merge ✅
  • Non-root + newer incoming: LWW (no merge) ⚠️

Problem

For CRDT types like GCounter, PnCounter, UnorderedMap, etc., LWW can cause data loss:

Node A: counter = 5 (ts=100)
Node B: counter = 3 (ts=200)
After sync A←B: counter = 3 (lost the 5 increments from A)
Correct CRDT merge: counter = 8

Invariant Affected

I5 - No Silent Data Loss: "initialized nodes MUST CRDT-merge; overwrite ONLY for fresh nodes"

Related Work

Suggested Fix

In save_internal(), check metadata.crdt_type and call CRDT merge for non-LwwRegister types regardless of timestamp order.

Notes

/cc @calimero-network/core-team

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions