Distributed CRDTs

Eventually-consistent access grants using Conflict-Free Replicated Data Types (CRDTs). Multiple nodes can independently grant/revoke capabilities and merge.

CrdtState

#![allow(unused)]
fn main() {
use schubert::crdt::{CrdtState, CrdtGrant, VersionVector};

let mut node_a = CrdtState::new();
let mut node_b = CrdtState::new();

// Node A grants a capability
node_a.apply(CrdtGrant::grant("alice", "read"))?;

// Node B grants a capability (concurrently)
node_b.apply(CrdtGrant::grant("alice", "write"))?;

// Merge — both grants survive
node_a.merge(&node_b)?;
assert!(node_a.has_grant("alice", "read"));
assert!(node_a.has_grant("alice", "write"));
}

Version Vectors

Each grant carries a version vector tracking causal history:

#![allow(unused)]
fn main() {
let grant = CrdtGrant::grant("alice", "read");
println!("Version: {:?}", grant.version());
}

Last-Write-Wins

Conflicting grants (same principal, same capability) resolve via last-write-wins:

#![allow(unused)]
fn main() {
// Node A grants, Node B revokes concurrently
let grant = CrdtGrant::grant("alice", "read");
let revoke = CrdtGrant::revoke("alice", "read");

// Merge resolves to the operation with the higher timestamp
node_a.apply(grant)?;
node_a.merge(&node_b)?; // state_b has the revoke with higher timestamp
}

Merge Properties

  • Commutative: a.merge(b) == b.merge(a)
  • Associative: (a.merge(b)).merge(c) == a.merge(b.merge(c))
  • Idempotent: a.merge(a) == a