ADR-016: CODI Rust Implementation Architecture (v4) - Part 2: Technical Implementation
Document Specification Block
Document: ADR-016-v4-codi-rust-implementation-part2
Version: 1.0.0
Purpose: Technical implementation blueprint for CODI Rust binary
Audience: AI Agents, Rust Developers, DevOps Engineers
Date Created: 2025-08-29
Date Modified: 2025-08-29
Date Released: DRAFT
QA Reviewed: Pending
Status: DRAFT
Table of Contents
- 8. Implementation Blueprint
- 9. Testing Strategy
- 10. Security Considerations
- 11. Performance Characteristics
- 12. Operational Considerations
- 13. Migration Strategy
8. Implementation Blueprint
8.1 Architecture Diagram
8.2 Dependencies
[package]
name = "codi"
version = "1.0.0"
edition = "2021"
[dependencies]
# CLI Framework
clap = { version = "4.5", features = ["derive", "color", "suggestions"] }
# Async Runtime
tokio = { version = "1.35", features = ["full"] }
# Web Server
actix-web = "4.5"
actix-ws = "0.2"
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Database
rusqlite = { version = "0.31", features = ["bundled"] }
foundationdb = "0.9"
# File Watching
notify = "6.1"
# Utilities
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.6", features = ["v4", "serde"] }
anyhow = "1.0"
colored = "2.1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
[profile.release]
lto = true
opt-level = 3
strip = true
8.3 Core Implementation
// src/main.rs
use clap::{Parser, Subcommand};
use anyhow::Result;
#[derive(Parser)]
#[command(name = "codi", version, about)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Subcommand)]
enum Commands {
Log {
message: String,
#[arg(short, long, default_value = "INFO")]
action: String,
},
Status {
#[arg(short, long)]
detailed: bool,
},
Monitor {
#[arg(short, long)]
actor: Option<String>,
},
Serve {
#[arg(short, long, default_value = "8081")]
port: u16,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// Initialize services
let config = config::load()?;
let db = database::init(&config)?;
let logger = logging::init(&config)?;
match cli.command {
Commands::Log { message, action } => {
commands::log::execute(&db, &logger, &message, &action).await?;
}
Commands::Status { detailed } => {
commands::status::execute(&db, detailed).await?;
}
Commands::Monitor { actor } => {
commands::monitor::execute(&db, actor).await?;
}
Commands::Serve { port } => {
server::start(port, db).await?;
}
}
Ok(())
}
8.4 API Specification
// src/commands/log.rs
use crate::database::Database;
use crate::logging::Logger;
use anyhow::Result;
use chrono::Utc;
use serde_json::json;
pub async fn execute(
db: &Database,
logger: &Logger,
message: &str,
action: &str,
) -> Result<()> {
let entry = LogEntry {
timestamp: Utc::now(),
session_id: get_session_id(),
actor: Actor {
id: "codi-rust",
type_: "tool",
},
action: action.to_string(),
message: message.to_string(),
};
// Write to SQLite
db.insert_log(&entry).await?;
// Write to file
logger.log(&entry).await?;
// Sync to FoundationDB if connected
if let Some(fdb) = &db.fdb_connection {
fdb.sync_log(&entry).await?;
}
println!("✅ Logged to codi-ps.log");
Ok(())
}
8.5 Data Models
// src/models/mod.rs
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct LogEntry {
pub timestamp: DateTime<Utc>,
pub session_id: String,
pub actor: Actor,
pub action: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Actor {
pub id: String,
#[serde(rename = "type")]
pub type_: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Session {
pub id: Uuid,
pub started_at: DateTime<Utc>,
pub ended_at: Option<DateTime<Utc>>,
pub description: Option<String>,
}
8.6 Configuration
// src/config.rs
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize)]
pub struct CodiConfig {
pub log_path: PathBuf,
pub db_path: PathBuf,
pub fdb_cluster_file: Option<PathBuf>,
pub session_timeout: u64,
pub export_directory: PathBuf,
}
impl Default for CodiConfig {
fn default() -> Self {
Self {
log_path: dirs::home_dir()
.unwrap()
.join(".codi/logs/codi-ps.log"),
db_path: dirs::home_dir()
.unwrap()
.join(".codi/codi.db"),
fdb_cluster_file: None,
session_timeout: 3600,
export_directory: dirs::home_dir()
.unwrap()
.join(".codi/exports"),
}
}
}
8.7 Logging Requirements
// src/logging/mod.rs
use tracing::{info, warn, error};
use tracing_subscriber::fmt::format::FmtSpan;
pub fn init(config: &CodiConfig) -> Result<Logger> {
let file_appender = tracing_appender::rolling::daily(
&config.log_path.parent().unwrap(),
"codi.log"
);
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::fmt()
.with_writer(non_blocking)
.with_span_events(FmtSpan::CLOSE)
.json()
.init();
Ok(Logger { config: config.clone() })
}
// Standard logging pattern
pub fn log_operation<T>(
component: &str,
action: &str,
user_id: Option<&str>,
result: Result<T>,
) -> Result<T> {
match result {
Ok(value) => {
info!(
component = component,
action = action,
user_id = user_id,
"Operation successful"
);
Ok(value)
}
Err(e) => {
error!(
component = component,
action = action,
user_id = user_id,
error = %e,
"Operation failed"
);
Err(e)
}
}
}
8.8 Error Handling
// src/error.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum CodiError {
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
#[error("FoundationDB error: {0}")]
FoundationDB(#[from] foundationdb::FdbError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Configuration error: {0}")]
Config(String),
#[error("Session not found: {0}")]
SessionNotFound(String),
}
// User-friendly error messages
impl CodiError {
pub fn user_message(&self) -> String {
match self {
Self::Database(_) => "Unable to access local database. Try 'codi repair'".into(),
Self::FoundationDB(_) => "Cannot connect to CODITECT server. Check your connection".into(),
Self::Io(_) => "File system error. Check permissions".into(),
Self::Config(msg) => format!("Configuration issue: {}", msg),
Self::SessionNotFound(id) => format!("Session '{}' not found. Use 'codi session list'", id),
}
}
}
9. Testing Strategy
9.1 Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_entry_serialization() {
let entry = LogEntry {
timestamp: Utc::now(),
session_id: "test-session".into(),
actor: Actor {
id: "test".into(),
type_: "test".into(),
},
action: "TEST".into(),
message: "Test message".into(),
metadata: None,
};
let json = serde_json::to_string(&entry).unwrap();
let decoded: LogEntry = serde_json::from_str(&json).unwrap();
assert_eq!(entry.session_id, decoded.session_id);
}
}
9.2 Integration Tests
#[tokio::test]
async fn test_log_command() {
let temp_dir = tempdir::TempDir::new("codi-test").unwrap();
let config = CodiConfig {
db_path: temp_dir.path().join("test.db"),
..Default::default()
};
let db = Database::init(&config).await.unwrap();
let logger = Logger::new(&config);
commands::log::execute(&db, &logger, "test", "TEST").await.unwrap();
let logs = db.get_recent_logs(1).await.unwrap();
assert_eq!(logs.len(), 1);
assert_eq!(logs[0].message, "test");
}
9.3 Test Coverage Requirements
- Unit Test Coverage: ≥ 90%
- Integration Test Coverage: ≥ 80%
- Critical Path Coverage: 100%
- Error Path Coverage: 100%
10. Security Considerations
10.1 Authentication & Authorization
- Local SQLite database is user-readable only (0600)
- FoundationDB connection uses secure cluster file
- Web server binds to localhost by default
- MCP server requires authentication tokens
10.2 Data Protection
- Logs sanitized to prevent injection
- No sensitive data in error messages
- Secure temporary file handling
- Input validation on all commands
11. Performance Characteristics
11.1 Expected Metrics
| Metric | Target | Measurement |
|---|---|---|
| Startup Time | < 50ms | Time to first prompt |
| Log Command | < 10ms | Complete execution |
| Memory Usage | < 100MB | RSS at idle |
| Binary Size | < 20MB | Stripped release build |
12. Operational Considerations
12.1 Deployment
# Build release binary
cargo build --release
# Install globally
sudo cp target/release/codi /usr/local/bin/
# Or user-local
cp target/release/codi ~/.local/bin/
12.2 Monitoring
- Built-in metrics via
codi status --metrics - Prometheus endpoint at
/metricswhen server running - Log rotation handled automatically
13. Migration Strategy
13.1 Migration Steps
- Step 1: Create Rust project structure (Day 1)
- Step 2: Implement core commands (Days 2-5)
- Step 3: Add monitoring features (Days 6-8)
- Step 4: Implement web server (Days 9-11)
- Step 5: Add MCP support (Days 12-14)
- Step 6: Testing and optimization (Days 15-18)
- Step 7: Gradual rollout (Days 19-20)
13.2 Compatibility Matrix
| Component | Backward Compatible | Migration Required |
|---|---|---|
| Log format | ✅ Yes | No |
| Commands | ✅ Yes (aliases) | Optional |
| Config | ⚠️ New format | Auto-migration |