# Task 01: Identity Types

## Delivers

`ShardId(u16)`, `RegionId(u16)`, `WalSegmentId { region_id, shard_id, seqno }`, and `NodeRole` enum in `tidal/src/replication/` with full trait derivations, Display impls, and serde support. These types are the atoms of the entire distributed system -- every downstream module depends on them.

## Complexity: S

## Dependencies

- None (this is the foundation task for the phase)

## Technical Design

### ShardId and RegionId

```rust
// tidal/src/replication/shard.rs

/// Uniquely identifies a shard within the cluster.
///
/// A shard owns a contiguous range of EntityIds for a given EntityKind.
/// ShardId(0) is the default single-node shard.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
         serde::Serialize, serde::Deserialize)]
pub struct ShardId(pub u16);

impl ShardId {
    /// The default single-node shard.
    pub const SINGLE: ShardId = ShardId(0);
}

impl fmt::Display for ShardId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "s{}", self.0)
    }
}

/// Uniquely identifies a region in the cluster.
///
/// RegionId(0) is the default single-node region.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
         serde::Serialize, serde::Deserialize)]
pub struct RegionId(pub u16);

impl RegionId {
    /// The default single-node region.
    pub const SINGLE: RegionId = RegionId(0);
}

impl fmt::Display for RegionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "r{}", self.0)
    }
}
```

### WalSegmentId

```rust
// tidal/src/replication/segment_id.rs

/// Globally unique identifier for a WAL segment.
///
/// Ordering: by (region_id, shard_id, seqno) -- allows total ordering
/// across all segments in the cluster.
///
/// Display: "r0:s0:42" -- human-readable for logs and tidalctl output.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash,
         serde::Serialize, serde::Deserialize)]
pub struct WalSegmentId {
    pub region_id: RegionId,
    pub shard_id: ShardId,
    pub seqno: u64,
}

impl WalSegmentId {
    pub fn new(region_id: RegionId, shard_id: ShardId, seqno: u64) -> Self {
        Self { region_id, shard_id, seqno }
    }

    /// Create a segment ID for the default single-node deployment.
    pub fn single_node(seqno: u64) -> Self {
        Self::new(RegionId::SINGLE, ShardId::SINGLE, seqno)
    }
}

impl PartialOrd for WalSegmentId {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for WalSegmentId {
    fn cmp(&self, other: &Self) -> Ordering {
        self.region_id.cmp(&other.region_id)
            .then(self.shard_id.cmp(&other.shard_id))
            .then(self.seqno.cmp(&other.seqno))
    }
}

impl fmt::Display for WalSegmentId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}:{}", self.region_id, self.shard_id, self.seqno)
    }
}
```

### NodeRole

```rust
// tidal/src/db/config.rs (new enum, added here)

/// The role of this node in the cluster.
///
/// `Single` is the default -- a standalone node that acts as both leader
/// and follower. Used for embedded deployments.
///
/// `Leader` accepts writes and ships WAL segments to followers.
///
/// `Follower` only accepts replayed events; write calls return ReadOnly.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default,
         serde::Serialize, serde::Deserialize)]
pub enum NodeRole {
    #[default]
    Single,
    Leader,
    Follower,
}
```

### Module structure

```rust
// tidal/src/replication/mod.rs
//! Replication types and protocols for distributed tidalDB deployments.
//!
//! The `replication` module is empty in single-node deployments --
//! all types default to shard_id=0, region_id=0, and routing is a no-op.

pub mod shard;
pub mod segment_id;
pub mod state;

pub use shard::{ShardId, RegionId};
pub use segment_id::WalSegmentId;
pub use state::ReplicationState;
```

## Acceptance Criteria

- [ ] `ShardId(u16)` and `RegionId(u16)` derive `Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize`
- [ ] `ShardId::SINGLE` = `ShardId(0)`, `RegionId::SINGLE` = `RegionId(0)`
- [ ] `WalSegmentId` has total ordering by `(region_id, shard_id, seqno)`
- [ ] `WalSegmentId::Display` produces `"r0:s0:42"` format
- [ ] `WalSegmentId::single_node(seqno)` creates a single-node segment ID
- [ ] `NodeRole` enum with `Single` (default), `Leader`, `Follower`
- [ ] `tidal/src/replication/mod.rs` exports all types; wired into `tidal/src/lib.rs`
- [ ] Unit tests: ordering, display, single-node defaults
- [ ] `cargo clippy -D warnings` and `cargo fmt` pass

## Test Strategy

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn segment_id_ordering() {
        let a = WalSegmentId::new(RegionId(0), ShardId(0), 1);
        let b = WalSegmentId::new(RegionId(0), ShardId(0), 2);
        let c = WalSegmentId::new(RegionId(0), ShardId(1), 0);
        let d = WalSegmentId::new(RegionId(1), ShardId(0), 0);
        assert!(a < b);
        assert!(b < c);
        assert!(c < d);
    }

    #[test]
    fn segment_id_display() {
        let id = WalSegmentId::new(RegionId(2), ShardId(3), 42);
        assert_eq!(id.to_string(), "r2:s3:42");
    }

    #[test]
    fn single_node_defaults() {
        assert_eq!(ShardId::SINGLE, ShardId(0));
        assert_eq!(RegionId::SINGLE, RegionId(0));
        assert_eq!(WalSegmentId::single_node(99).to_string(), "r0:s0:99");
    }
}
```
