# Task 04: SegmentReceiver ## Delivers `SegmentReceiver` background task in `tidal/src/replication/receiver.rs`. Receives `WalSegmentPayload` from transport, validates BLAKE3 checksum, decodes batches, and replays events through `SignalLedger::apply_wal_event()`. Idempotent via seqno high-water-mark. ## Complexity: M ## Dependencies - Task 01 (Transport trait) - Task 02 (InProcessTransport) - Phase 8.1 (ReplicationState for high-water-mark) ## Technical Design ```rust // tidal/src/replication/receiver.rs /// Receives WAL segments from a leader and replays them locally. /// /// Runs as a background tokio task. The receiver maintains strict /// idempotency: segments with seqno <= `applied_seqno` are skipped. pub struct SegmentReceiver { transport: Arc, signal_ledger: Arc, replication_state: Arc, leader_shard: ShardId, } impl SegmentReceiver { pub fn new( transport: Arc, signal_ledger: Arc, replication_state: Arc, leader_shard: ShardId, ) -> Self { Self { transport, signal_ledger, replication_state, leader_shard } } pub fn start(self: Arc, shutdown_rx: tokio::sync::watch::Receiver) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { self.run(shutdown_rx).await; }) } async fn run(&self, mut shutdown: tokio::sync::watch::Receiver) { loop { tokio::select! { segment = self.transport.recv_segment() => { match segment { Some(payload) => { if let Err(e) = self.apply_segment(payload).await { tracing::error!("SegmentReceiver: apply error: {e}"); } } None => { tracing::info!("SegmentReceiver: transport closed, stopping"); break; } } } Ok(_) = shutdown.changed() => { if *shutdown.borrow() { break; } } } } } async fn apply_segment(&self, payload: WalSegmentPayload) -> Result<(), WalError> { let seqno = payload.id.seqno; let shard = payload.id.shard_id; // Idempotency check: skip segments already applied. let applied = self.replication_state .applied_seqno(shard) .unwrap_or(0); if seqno <= applied { tracing::trace!(seqno, applied, "SegmentReceiver: skipping duplicate segment"); return Ok(()); } // BLAKE3 checksum validation. let expected_checksum = blake3::hash(&payload.bytes); // (Extract checksum from BatchHeader and compare) // Decode and replay each event. let batches = decode_wal_segment(&payload.bytes)?; for batch in batches { for event in batch.events { self.signal_ledger.apply_wal_event( event.entity_id, &event.signal_type, event.weight, event.timestamp, )?; } } // Advance high-water-mark. self.replication_state.advance(shard, seqno); tracing::debug!(seqno, "SegmentReceiver: applied segment"); Ok(()) } } ``` ## Acceptance Criteria - [ ] `SegmentReceiver::start()` spawns a background tokio task that reads from `transport.recv_segment()` - [ ] BLAKE3 checksum validation: corrupted segments return `WalError::Corruption` and are NOT applied - [ ] Idempotency: segments with `seqno <= replication_state.applied_seqno(shard)` are skipped (no double-counting) - [ ] All events in a received segment are replayed through `SignalLedger::apply_wal_event()` - [ ] `replication_state.advance(shard, seqno)` is called after successful replay - [ ] Transport close (`recv_segment` returns `None`) causes the receiver to stop gracefully - [ ] Integration test: ship 100 segments -> receiver applies all -> decay scores match - [ ] `cargo clippy -D warnings` and `cargo fmt` pass