# Task 05: NodeConfig ## Delivers Add `NodeConfig` struct to `tidal/src/db/config.rs` extending `Config` with cluster fields (`role`, `shard_id`, `region_id`, `peer_shards`). Defaults produce a single-node config with zero changes to existing embedders. ## Complexity: S ## Dependencies - Task 01 (ShardId, RegionId, NodeRole types) - Task 02 (ShardRouter) ## Technical Design ```rust // tidal/src/db/config.rs /// Cluster configuration for distributed tidalDB deployments. /// /// Defaults produce a single-node configuration identical to M0-M7 behavior. /// Embedded deployments that do not set any cluster fields get single-node. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct NodeConfig { /// The role of this node. /// /// Default: `NodeRole::Single` (standalone, accepts reads and writes). pub role: NodeRole, /// This node's shard identity. /// /// Default: `ShardId(0)` (single-node shard). pub shard_id: ShardId, /// This node's region identity. /// /// Default: `RegionId(0)` (single-node region). pub region_id: RegionId, /// Shards this node is aware of (including itself). /// /// Empty for single-node deployments. pub peer_shards: Vec, /// Routing strategy for entity-to-shard assignment. /// /// Default: `ShardRouter::single()` (all entities -> ShardId(0)). #[serde(skip)] pub router: ShardRouter, } impl Default for NodeConfig { fn default() -> Self { Self { role: NodeRole::Single, shard_id: ShardId::SINGLE, region_id: RegionId::SINGLE, peer_shards: vec![], router: ShardRouter::single(), } } } impl NodeConfig { /// Returns true if this is a standalone single-node deployment. pub fn is_single_node(&self) -> bool { self.role == NodeRole::Single } /// Returns true if this node accepts writes. pub fn accepts_writes(&self) -> bool { matches!(self.role, NodeRole::Single | NodeRole::Leader) } } ``` ### Integration into Config ```rust // tidal/src/db/config.rs -- extend existing Config struct pub struct Config { // ... existing fields ... /// Cluster configuration. Default: single-node. pub cluster: NodeConfig, } impl Default for Config { fn default() -> Self { Self { // ... existing defaults ... cluster: NodeConfig::default(), } } } ``` ### Builder integration ```rust // TidalDb::builder() -- add optional cluster config method impl TidalDbBuilder { /// Configure this instance for distributed deployment. /// /// Not required for single-node embedded use. pub fn with_cluster(mut self, config: NodeConfig) -> Self { self.config.cluster = config; self } } ``` ## Acceptance Criteria - [ ] `NodeConfig::default()` produces `role=Single`, `shard_id=ShardId(0)`, `region_id=RegionId(0)`, `peer_shards=[]`, `router=ShardRouter::single()` - [ ] `NodeConfig::is_single_node()` returns true for `Single`, false for `Leader`/`Follower` - [ ] `NodeConfig::accepts_writes()` returns true for `Single` and `Leader`, false for `Follower` - [ ] `Config` gains a `cluster: NodeConfig` field with default `NodeConfig::default()` - [ ] All existing tests that construct `Config` or use `TidalDb::builder()` pass unchanged (cluster field defaults to single-node) - [ ] `TidalDbBuilder::with_cluster(config)` sets the cluster config - [ ] `cargo clippy -D warnings` and `cargo fmt` pass