tidaldb/docs/planning/milestone-8/phase-3/task-03-lww-register.md
jordan f4cfd6c81f feat: complete M8 replication primitives + forage enhancements + docs
Milestone 8 (phases 1-4):
- Shard-aware WAL segment naming, BatchHeader v2, ShardRouter
- Transport trait, InProcessTransport, WalShipper, FollowerDb
- HLC, PNCounter, LWWRegister, CrdtSignalState, ReconciliationEngine
- Session replication bridge with SeqNo/HWM, idempotency store

Forage application:
- Multi-source discovery engine with MAB exploration
- Embedding-based label system, server handlers, UI refresh

Other:
- QUICKSTART.md, README.md, milestone-8 planning docs
- Hard negative union semantics, RLHF export enhancements
- Recovery benchmark and visibility test expansions
- Split 8 oversized source files per CODING_GUIDELINES §9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:17:19 -07:00

2.7 KiB

Task 03: LWWRegister

Delivers

LWWRegister<T> in tidal/src/replication/crdt/lww_register.rs. HLC-timestamped value with merge taking the higher timestamp. Tie-breaking by node_id. Used for hard negatives (hide/mute/block) which require last-writer-wins semantics across regions.

Complexity: S

Dependencies

  • Task 01 (HlcTimestamp)

Technical Design

// tidal/src/replication/crdt/lww_register.rs

/// Last-Writer-Wins register with HLC timestamp.
///
/// Resolves concurrent writes by `HlcTimestamp` ordering:
/// - Higher `wall_ns` wins
/// - Same wall, higher `logical` wins
/// - Same wall + logical, higher `node_id` wins (deterministic tie-break)
///
/// The value `None` represents "not yet written."
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct LWWRegister<T: Clone + PartialEq> {
    value: Option<T>,
    timestamp: Option<HlcTimestamp>,
}

impl<T: Clone + PartialEq> LWWRegister<T> {
    pub fn empty() -> Self {
        Self { value: None, timestamp: None }
    }

    /// Write a new value with the given HLC timestamp.
    ///
    /// Only advances the register if `ts > self.timestamp`.
    pub fn write(&mut self, value: T, ts: HlcTimestamp) {
        if self.timestamp.map_or(true, |cur| ts > cur) {
            self.value = Some(value);
            self.timestamp = Some(ts);
        }
    }

    /// Merge another register into this one.
    ///
    /// The register with the higher timestamp wins.
    pub fn merge(&mut self, other: &LWWRegister<T>) {
        if let Some(other_ts) = other.timestamp {
            if self.timestamp.map_or(true, |cur| other_ts > cur) {
                self.value = other.value.clone();
                self.timestamp = other.timestamp;
            }
        }
    }

    /// Current value of the register.
    pub fn get(&self) -> Option<&T> {
        self.value.as_ref()
    }

    /// The HLC timestamp of the last write.
    pub fn timestamp(&self) -> Option<HlcTimestamp> {
        self.timestamp
    }
}

impl<T: Clone + PartialEq> Default for LWWRegister<T> {
    fn default() -> Self {
        Self::empty()
    }
}

Acceptance Criteria

  • LWWRegister::write(value, ts) accepts writes with higher timestamps only
  • LWWRegister::merge takes the value with the higher HLC timestamp
  • Concurrent writes at the same wall time resolve by logical then node_id
  • LWWRegister::merge is commutative: merge(A, B) == merge(B, A) (property test)
  • LWWRegister::merge is associative and idempotent (property tests)
  • T: Clone + PartialEq bound is sufficient; no Ord required
  • Used for HardNegAction in Phase 8.4; T will be HardNegAction enum
  • cargo clippy -D warnings and cargo fmt pass