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>
87 lines
2.7 KiB
Markdown
87 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```rust
|
|
// 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
|