ADR-006-v4: Data Model Architecture - Part 2 (Technical)
Table of Contents​
- Document Specification Block
- 1. Technical Requirements
- 2. Core Models Implementation
- 3. Project Management Models
- 4. AI and Automation Models
- 5. Access Control and Compliance
- 6. Business Operations Models
- 7. Key Patterns and Indexes
- 8. Testing Requirements
- 9. Security Controls
- 10. Performance Specifications
- 11. Monitoring & Observability
- 12. Constraints for AI Implementation
- References
- Approval Signatures
Document Specification Block​
Document: ADR-006-v4-data-model-part2-technical
Version: 1.0.1
Purpose: Provide exact technical specifications for CODITECT's multi-tenant data models
Audience: AI agents, developers implementing the data layer
Date Created: 2025-08-31
Date Modified: 2025-08-31
QA Review Date: 2025-08-31
Status: DRAFT
User Story: As a developer, I need a comprehensive data model that ensures complete tenant isolation while supporting human-AI collaboration
1. Technical Requirements​
1.1 Constraints​
- MUST prefix all keys with tenant_id
- MUST NOT allow cross-tenant queries
- MUST use transactions for related updates
- MUST validate all UUIDs before storage
- MUST maintain audit trail for all mutations
1.2 Dependencies​
[dependencies]
foundationdb = "0.8"
uuid = { version = "1.6", features = ["v4", "serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
argon2 = "0.5"
anyhow = "1.0"
thiserror = "1.0"
# CODI logging integration
coditect-logging = { path = "../logging" }
2. Core Models Implementation​
2.1 User Model​
// src/models/user.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::logging::{CoditecLogger, LogLevel, LogEntry};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct User {
pub user_id: Uuid,
pub tenant_id: Uuid,
pub email: String,
pub first_name: String,
pub last_name: String,
pub company: Option<String>,
pub role: UserRole,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_login: Option<DateTime<Utc>>,
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password_hash: Option<String>, // Argon2id hash
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum UserRole {
Admin, // Full tenant management
Developer, // Create and manage resources
Manager, // Team and project oversight
Viewer, // Read-only access
}
// Key patterns for User
pub struct UserKeys;
impl UserKeys {
// Primary key
pub fn user_key(tenant_id: &Uuid, user_id: &Uuid) -> String {
format!("{}/users/{}", tenant_id, user_id)
}
// Secondary index for email lookup
pub fn email_index_key(tenant_id: &Uuid, email: &str) -> String {
format!("{}/user_by_email/{}", tenant_id, email.to_lowercase())
}
// Range query for all users in tenant
pub fn user_range_prefix(tenant_id: &Uuid) -> String {
format!("{}/users/", tenant_id)
}
}
// JWT/Auth Integration
impl User {
pub fn to_jwt_claims(&self) -> JwtClaims {
JwtClaims {
sub: self.user_id.to_string(),
tenant_id: self.tenant_id.to_string(),
email: self.email.clone(),
role: self.role.to_string(),
exp: (Utc::now() + Duration::hours(24)).timestamp(),
}
}
pub fn validate_password(&self, password: &str) -> Result<bool> {
match &self.password_hash {
Some(hash) => {
let parsed = PasswordHash::new(hash)?;
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
}
None => Ok(false),
}
}
}
2.2 Member Model (Unified Human/AI Abstraction)​
// src/models/member.rs
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Member {
pub member_id: Uuid,
pub tenant_id: Uuid,
pub user_id: Option<Uuid>, // Link to User if human
pub member_type: MemberType,
pub name: String,
pub email: Option<String>, // For humans
pub avatar_url: Option<String>,
pub status: MemberStatus,
pub capabilities: Vec<String>, // Skills/capabilities
pub availability: Availability,
pub capacity: Capacity,
pub metadata: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum MemberType {
Human,
Agent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Capacity {
pub max_concurrent_tasks: i32,
pub current_task_count: i32,
pub weekly_hours: Option<f32>, // For humans
pub tokens_per_hour: Option<i32>, // For agents
}
// Key patterns for Member
pub struct MemberKeys;
impl MemberKeys {
pub fn member_key(tenant_id: &Uuid, member_id: &Uuid) -> String {
format!("{}/members/{}", tenant_id, member_id)
}
pub fn members_by_type(tenant_id: &Uuid, member_type: &MemberType) -> String {
format!("{}/members_by_type/{}/", tenant_id, member_type)
}
pub fn available_members(tenant_id: &Uuid) -> String {
format!("{}/available_members/", tenant_id)
}
}
2.3 Entity Model (Organizational Hierarchy)​
// src/models/entity.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entity {
pub entity_id: Uuid,
pub tenant_id: Uuid,
pub parent_entity_id: Option<Uuid>,
pub entity_type: EntityType,
pub name: String,
pub code: String,
pub description: String,
pub hierarchy_level: HierarchyLevel,
pub is_active: bool,
pub metadata: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum HierarchyLevel {
Corporation,
Division,
Department,
Team,
}
// Key patterns for Entity
pub struct EntityKeys;
impl EntityKeys {
pub fn entity_key(tenant_id: &Uuid, entity_id: &Uuid) -> String {
format!("{}/entities/{}", tenant_id, entity_id)
}
pub fn children_key(tenant_id: &Uuid, parent_id: &Uuid) -> String {
format!("{}/entity_children/{}/", tenant_id, parent_id)
}
pub fn entities_by_level(tenant_id: &Uuid, level: &HierarchyLevel) -> String {
format!("{}/entities_by_level/{}/", tenant_id, level)
}
}
3. Project Management Models​
3.1 Project Model​
// src/models/project.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
pub project_id: Uuid,
pub tenant_id: Uuid,
pub entity_id: Uuid,
pub subsidiary_id: Uuid,
pub name: String,
pub code: String,
pub description: String,
pub status: ProjectStatus,
pub priority: Priority,
pub project_type: ProjectType,
pub manager_id: Uuid,
pub sponsor_id: Option<Uuid>,
pub team_ids: Vec<Uuid>,
pub timeline: ProjectTimeline,
pub budget: Budget,
pub technology_stack: Vec<String>,
pub repository_ids: Vec<Uuid>,
pub compliance_requirements: Vec<String>,
pub risk_tolerance: RiskTolerance,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectTimeline {
pub planned_start: DateTime<Utc>,
pub planned_end: DateTime<Utc>,
pub actual_start: Option<DateTime<Utc>>,
pub actual_end: Option<DateTime<Utc>>,
pub milestones: Vec<Milestone>,
}
// Key patterns for Project
pub struct ProjectKeys;
impl ProjectKeys {
pub fn project_key(tenant_id: &Uuid, project_id: &Uuid) -> String {
format!("{}/projects/{}", tenant_id, project_id)
}
pub fn projects_by_entity(tenant_id: &Uuid, entity_id: &Uuid) -> String {
format!("{}/projects_by_entity/{}/", tenant_id, entity_id)
}
pub fn projects_by_status(tenant_id: &Uuid, status: &ProjectStatus) -> String {
format!("{}/projects_by_status/{}/", tenant_id, status)
}
}
3.2 Task Model​
// src/models/task.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub task_id: Uuid,
pub tenant_id: Uuid,
pub project_id: Uuid,
pub parent_task_id: Option<Uuid>,
pub title: String,
pub description: String,
pub status: TaskStatus,
pub priority: TaskPriority,
pub task_type: TaskType,
pub assigned_to: Option<Uuid>, // Member ID
pub estimated_hours: Option<f32>,
pub actual_hours: Option<f32>,
pub due_date: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub dependencies: Vec<Uuid>,
pub tags: Vec<String>,
pub attachments: Vec<Attachment>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: Uuid,
}
// Key patterns for Task
pub struct TaskKeys;
impl TaskKeys {
pub fn task_key(tenant_id: &Uuid, task_id: &Uuid) -> String {
format!("{}/tasks/{}", tenant_id, task_id)
}
pub fn tasks_by_project(tenant_id: &Uuid, project_id: &Uuid) -> String {
format!("{}/tasks_by_project/{}/", tenant_id, project_id)
}
pub fn tasks_by_assignee(tenant_id: &Uuid, member_id: &Uuid) -> String {
format!("{}/tasks_by_assignee/{}/", tenant_id, member_id)
}
}
4. AI and Automation Models​
4.1 Agent Configuration Model​
// src/models/agent_config.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub config_id: Uuid,
pub tenant_id: Uuid,
pub name: String,
pub agent_type: AgentType,
pub description: String,
pub capabilities: Vec<AgentCapability>,
pub provider: AIProvider,
pub model: String,
pub max_tokens: i32,
pub temperature: f32,
pub tools: Vec<ToolConfig>,
pub cost_per_token: f64,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentType {
Coder,
Reviewer,
Tester,
Documenter,
Orchestrator,
}
// Key patterns for AgentConfig
pub struct AgentConfigKeys;
impl AgentConfigKeys {
pub fn config_key(tenant_id: &Uuid, config_id: &Uuid) -> String {
format!("{}/agent_configs/{}", tenant_id, config_id)
}
pub fn configs_by_type(tenant_id: &Uuid, agent_type: &AgentType) -> String {
format!("{}/agent_configs_by_type/{}/", tenant_id, agent_type)
}
}
4.2 Workflow Model​
// src/models/workflow.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workflow {
pub workflow_id: Uuid,
pub tenant_id: Uuid,
pub name: String,
pub description: String,
pub trigger_type: TriggerType,
pub steps: Vec<WorkflowStep>,
pub error_handling: ErrorHandling,
pub timeout_seconds: i32,
pub max_retries: i32,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowStep {
pub step_id: Uuid,
pub name: String,
pub action_type: ActionType,
pub agent_config_id: Option<Uuid>,
pub inputs: serde_json::Value,
pub outputs: Vec<String>,
pub conditions: Vec<StepCondition>,
pub timeout_seconds: i32,
}
5. Access Control and Compliance​
5.1 RBAC Model​
// src/models/rbac.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
pub role_id: Uuid,
pub tenant_id: Uuid,
pub name: String,
pub description: String,
pub permissions: Vec<Permission>,
pub is_system: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Permission {
pub resource: ResourceType,
pub actions: Vec<Action>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResourceType {
User,
Project,
Task,
Agent,
Workflow,
Audit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Action {
Create,
Read,
Update,
Delete,
Execute,
}
5.2 Audit Model​
// src/models/audit_event.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub event_id: Uuid,
pub tenant_id: Uuid,
pub timestamp: DateTime<Utc>,
pub actor: AuditActor,
pub action: String,
pub resource_type: String,
pub resource_id: String,
pub changes: Option<serde_json::Value>,
pub result: AuditResult,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub request_id: Option<String>,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditActor {
pub actor_type: ActorType,
pub actor_id: Uuid,
pub actor_name: String,
}
// Key pattern for append-only audit log
pub struct AuditKeys;
impl AuditKeys {
pub fn audit_key(tenant_id: &Uuid, timestamp: &DateTime<Utc>, event_id: &Uuid) -> String {
format!("{}/audit_log/{}:{}", tenant_id, timestamp.to_rfc3339(), event_id)
}
}
6. Business Operations Models​
6.1 Usage Model​
// src/models/usage.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
pub usage_id: Uuid,
pub tenant_id: Uuid,
pub user_id: Uuid,
pub resource_type: ResourceType,
pub resource_id: String,
pub action: String,
pub quantity: f64,
pub unit: UsageUnit,
pub cost: f64,
pub timestamp: DateTime<Utc>,
pub provider: Option<String>,
pub metadata: Option<serde_json::Value>,
}
// Key patterns for usage tracking
pub struct UsageKeys;
impl UsageKeys {
pub fn usage_key(tenant_id: &Uuid, date: &str, user_id: &Uuid, usage_id: &Uuid) -> String {
format!("{}/usage/{}/{}/{}", tenant_id, date, user_id, usage_id)
}
pub fn daily_usage(tenant_id: &Uuid, date: &str) -> String {
format!("{}/usage/{}/", tenant_id, date)
}
}
7. Key Patterns and Indexes​
7.1 Primary Key Patterns​
// All primary keys follow this pattern
{tenant_id}/{entity_type}/{entity_id}
// Examples:
"12345/users/67890"
"12345/projects/abcdef"
"12345/tasks/xyz123"
7.2 Secondary Index Patterns​
// Lookup indexes
{tenant_id}/{entity_type}_by_{field}/{field_value} -> entity_id
// Examples:
"12345/user_by_email/john@example.com" -> "67890"
"12345/projects_by_status/active/" -> [project_ids]
7.3 Range Query Patterns​
// List all entities of a type
{tenant_id}/{entity_type}/
// List related entities
{tenant_id}/{parent_type}_{child_type}/{parent_id}/
// Examples:
"12345/users/" -> All users in tenant
"12345/tasks_by_project/proj123/" -> All tasks in project
8. Testing Requirements​
8.1 Multi-Tenant Isolation Tests​
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_tenant_isolation() {
let db = setup_test_fdb().await;
let tenant1 = Uuid::new_v4();
let tenant2 = Uuid::new_v4();
// Create users in different tenants
let user1 = create_user(&db, tenant1, "user1@tenant1.com").await;
let user2 = create_user(&db, tenant2, "user2@tenant2.com").await;
// Verify cannot access across tenants
let result = get_user(&db, tenant1, user2.user_id).await;
assert!(result.is_err());
// Verify range queries respect boundaries
let users = list_users(&db, tenant1).await.unwrap();
assert_eq!(users.len(), 1);
assert_eq!(users[0].user_id, user1.user_id);
}
#[tokio::test]
async fn test_key_pattern_validation() {
let tenant_id = Uuid::new_v4();
let user_id = Uuid::new_v4();
let key = UserKeys::user_key(&tenant_id, &user_id);
assert!(key.starts_with(&tenant_id.to_string()));
assert!(key.contains("/users/"));
assert!(key.ends_with(&user_id.to_string()));
}
}
9. Security Controls​
9.1 Access Validation​
pub async fn validate_tenant_access(
db: &Database,
tenant_id: &Uuid,
user_id: &Uuid,
) -> Result<()> {
// Verify user belongs to tenant
let user_key = UserKeys::user_key(tenant_id, user_id);
let user_data = db.get(&user_key).await?
.ok_or_else(|| anyhow!("User not found in tenant"))?;
let user: User = serde_json::from_slice(&user_data)?;
if user.tenant_id != *tenant_id {
return Err(anyhow!("Tenant mismatch"));
}
Ok(())
}
9.2 Audit Logging​
pub async fn log_data_access(
db: &Database,
tenant_id: &Uuid,
actor: &AuditActor,
action: &str,
resource: &str,
) -> Result<()> {
let event = AuditEvent {
event_id: Uuid::new_v4(),
tenant_id: *tenant_id,
timestamp: Utc::now(),
actor: actor.clone(),
action: action.to_string(),
resource_type: resource.to_string(),
// ... other fields
};
let key = AuditKeys::audit_key(tenant_id, &event.timestamp, &event.event_id);
db.set(&key, &serde_json::to_vec(&event)?).await?;
Ok(())
}
10. Performance Specifications​
10.1 Latency Requirements​
key_operations:
single_key_read: <1ms (p99)
single_key_write: <5ms (p99)
range_query_100_keys: <10ms (p99)
transaction_5_operations: <10ms (p99)
throughput:
reads_per_second: 1,000,000
writes_per_second: 100,000
concurrent_tenants: 10,000+
10.2 Optimization Strategies​
- Use range queries for listing operations
- Batch related updates in transactions
- Cache frequently accessed data
- Use secondary indexes for lookups
10.3 Cloud Run Deployment Considerations​
- Stateless design ensures compatibility with Cloud Run scaling
- Connection pooling to FoundationDB cluster via VPC connector
- Cold start optimization through lazy initialization
- Memory limits considered for batch operations (max 32GB)
- Request timeout of 60 minutes supports long-running migrations
11. Monitoring & Observability​
11.1 CODI Logging Integration​
use crate::logging::{CoditecLogger, LogLevel, LogEntry};
pub async fn log_model_operation(
component: &str,
action: &str,
tenant_id: &Uuid,
details: serde_json::Value,
) {
CoditecLogger::log(LogEntry {
timestamp: Utc::now(),
level: LogLevel::INFO,
component: format!("models.{}", component),
action: action.to_string(),
user_id: None,
tenant_id: Some(tenant_id.to_string()),
request_id: None,
session_id: None,
result: "success".to_string(),
duration_ms: None,
details: Some(details),
error: None,
}).await;
}
12. Constraints for AI Implementation​
12.1 MUST Requirements​
- MUST prefix every key with tenant_id
- MUST validate UUID format before storage
- MUST use transactions for related updates
- MUST log all mutations to audit trail
- MUST handle all error cases gracefully
12.2 MUST NOT Requirements​
- MUST NOT create keys without tenant prefix
- MUST NOT allow cross-tenant queries
- MUST NOT store sensitive data unencrypted
- MUST NOT skip validation checks
- MUST NOT ignore transaction failures
12.3 Test Coverage Requirements​
- Unit tests for all models - 95% minimum coverage
- Integration tests for key patterns - 90% minimum coverage
- Security tests for tenant isolation - 100% critical paths
- Performance tests for latency targets
- Audit tests for compliance
- All tests must validate acceptance criteria from user story
References​
Technical Documentation​
Related ADRs​
Approval Signatures​
Technical Approval​
| Role | Name | Signature | Date |
|---|---|---|---|
| Author | AI System (Claude) | _________________ | 2025-08-31 |
| Tech Lead | _________________ | _________________ | __________ |
| Data Architect | _________________ | _________________ | __________ |
| Security Engineer | _________________ | _________________ | __________ |
| Database Admin | _________________ | _________________ | __________ |
Implementation Sign-off​
| Component | Owner | Test Coverage | Sign-off Date |
|---|---|---|---|
| Core Models | _________________ | ____% | __________ |
| Project Models | _________________ | ____% | __________ |
| AI Models | _________________ | ____% | __________ |
| Access Control | _________________ | ____% | __________ |
| Business Models | _________________ | ____% | __________ |
Review History​
| Version | Date | Changes | Reviewer |
|---|---|---|---|
| 1.0.0 | 2025-08-31 | Initial draft documenting all 21 models | AI System |
| 1.0.1 | 2025-08-31 | Added QA date, user story, JWT details, test coverage %, Cloud Run | AI System |
This technical implementation blueprint provides exact specifications for CODITECT's multi-tenant data model architecture. All code must be implemented as specified with complete test coverage and tenant isolation.