ADR-029-v4: FoundationDB Issue Tracking Data Model - Part 2 (Technical)
Document Specification Block​
Document: ADR-029-v4-foundationdb-issue-tracking-data-model-part2-technical
Version: 1.1.0
Purpose: Constrain AI implementation with exact technical specifications for FDB issue tracking
Audience: AI agents, developers implementing the system
Date Created: 2025-09-13
Date Modified: 2025-09-13
Status: APPROVED
Approved By: RUST-QA-SESSION1
Approval Date: 2025-09-13
Table of Contents​
- Technical Requirements
- Component Architecture
- Implementation Specifications
- CLI Integration
- Testing Requirements
- Constraints for AI Implementation
- Logging Requirements
- Error Handling
- Performance Specifications
- Deployment Configuration
1. Technical Requirements​
1.1 Constraints​
- MUST use FoundationDB for all persistent state
- MUST NOT use any SQL or relational concepts
- MUST support atomic multi-key transactions
- MUST implement secondary indexes for queries
- MUST handle concurrent operations safely
- MUST include tenant ID prefixing for multi-tenancy
1.2 Dependencies​
- ADR-003: Multi-tenant Architecture
- ADR-006: Data Model Standards
- ADR-026: Error Handling Standards
- LOGGING-STANDARD-v4
- External: foundationdb crate 0.9+
2. Component Architecture​
2.1 System Components​
2.2 Data Flow Diagram​
3. Implementation Specifications​
3.1 Data Models​
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub id: Uuid,
pub tenant_id: String, // REQUIRED for multi-tenancy
pub title: String,
pub description: Option<String>,
pub severity: Severity,
pub category: Category,
pub status: Status,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: String,
pub test_context: Option<TestContext>,
pub build_context: Option<BuildContext>,
pub error_details: Option<ErrorDetails>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Severity {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Category {
Build,
Test,
Runtime,
Config,
Auth,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Status {
Open,
InProgress,
Resolved,
Closed,
}
3.2 Database Schema (Multi-tenant)​
Primary Storage:
/{tenant_id}/codi2/issues/{issue_id} → JSON(Issue)
Secondary Indexes:
/{tenant_id}/codi2/issues_by_date/{YYYY-MM-DD}/{issue_id} → empty
/{tenant_id}/codi2/issues_by_severity/{severity}/{issue_id} → empty
/{tenant_id}/codi2/issues_by_category/{category}/{issue_id} → empty
/{tenant_id}/codi2/issues_by_status/{status}/{issue_id} → empty
/{tenant_id}/codi2/issues_by_session/{session_id}/{issue_id} → empty
Metrics:
/{tenant_id}/codi2/metrics/issues_per_day/{YYYY-MM-DD} → i64
3.3 Repository Implementation​
use anyhow::Result;
use foundationdb::{Database, Transaction};
use tracing::{info, warn, error};
pub struct IssueRepository {
db: Database,
tenant_id: String,
}
impl IssueRepository {
pub async fn create(&self, issue: &Issue) -> Result<()> {
info!(
issue_id = %issue.id,
tenant_id = %self.tenant_id,
severity = ?issue.severity,
"Creating new issue"
);
self.db.run(|trx| {
let issue = issue.clone();
let tenant_id = self.tenant_id.clone();
async move {
// Store primary data with tenant prefix
let key = format!("/{}/codi2/issues/{}", tenant_id, issue.id);
let value = serde_json::to_vec(&issue)?;
trx.set(&key, &value);
// Create indexes with tenant prefix
let date = issue.created_at.format("%Y-%m-%d").to_string();
trx.set(&format!("/{}/codi2/issues_by_date/{}/{}",
tenant_id, date, issue.id), &[]);
trx.set(&format!("/{}/codi2/issues_by_severity/{:?}/{}",
tenant_id, issue.severity, issue.id), &[]);
trx.set(&format!("/{}/codi2/issues_by_category/{:?}/{}",
tenant_id, issue.category, issue.id), &[]);
trx.set(&format!("/{}/codi2/issues_by_status/{:?}/{}",
tenant_id, issue.status, issue.id), &[]);
// Update metrics
let count_key = format!("/{}/codi2/metrics/issues_per_day/{}",
tenant_id, date);
let count = match trx.get(&count_key, false).await? {
Some(bytes) => i64::from_be_bytes(
bytes.try_into().unwrap_or([0; 8])
) + 1,
None => 1,
};
trx.set(&count_key, &count.to_be_bytes());
info!(
issue_id = %issue.id,
"Issue created successfully"
);
Ok(())
}
}).await
}
}
4. CLI Integration​
use clap::{Parser, Subcommand};
#[derive(Subcommand)]
pub enum IssueCommand {
Create {
#[arg(short, long)]
title: String,
#[arg(short, long, default_value = "medium")]
severity: String,
#[arg(short, long, default_value = "runtime")]
category: String,
},
List {
#[arg(long)]
severity: Option<String>,
#[arg(long)]
category: Option<String>,
#[arg(short, long, default_value = "50")]
limit: usize,
},
}
5. Testing Requirements​
5.1 Test Coverage Requirements​
- Unit Tests: 90% minimum coverage
- Integration Tests: 80% minimum coverage
- Critical Paths: 100% coverage required
- Error Cases: 100% coverage required
5.2 Unit Tests​
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_create_issue_with_tenant() {
let repo = IssueRepository::new(test_db(), "test-tenant");
let issue = Issue {
id: Uuid::new_v4(),
tenant_id: "test-tenant".into(),
title: "Test issue".into(),
severity: Severity::High,
category: Category::Test,
status: Status::Open,
created_at: Utc::now(),
updated_at: Utc::now(),
created_by: "test-session".into(),
test_context: None,
build_context: None,
error_details: None,
tags: vec![],
};
assert!(repo.create(&issue).await.is_ok());
// Verify tenant isolation
let other_repo = IssueRepository::new(test_db(), "other-tenant");
let result = other_repo.find_by_id(&issue.id).await.unwrap();
assert!(result.is_none()); // Should not find in other tenant
}
}
6. Constraints for AI Implementation​
6.1 MUST Requirements​
- MUST use FoundationDB transactions for all operations
- MUST update all relevant indexes atomically
- MUST validate all inputs before storage
- MUST handle FDB errors gracefully
- MUST use efficient range queries
- MUST include tenant_id in all keys
- MUST log all operations per LOGGING-STANDARD-v4
- MUST handle errors per ADR-026
6.2 MUST NOT Requirements​
- MUST NOT store large blobs (>100KB) in values
- MUST NOT use blocking operations
- MUST NOT bypass transaction boundaries
- MUST NOT ignore index updates
- MUST NOT use keys longer than 10KB
- MUST NOT leak data across tenants
7. Logging Requirements​
Per LOGGING-STANDARD-v4, all operations MUST include structured logging:
use tracing::{info, warn, error, instrument};
#[instrument(skip(self, issue), fields(
issue_id = %issue.id,
tenant_id = %self.tenant_id,
severity = ?issue.severity
))]
pub async fn create_issue(&self, issue: &Issue) -> Result<IssueId> {
info!("Starting issue creation");
match self.repository.create(issue).await {
Ok(id) => {
info!(
issue_id = %id,
"Issue created successfully"
);
Ok(id)
}
Err(e) => {
error!(
error = %e,
"Failed to create issue"
);
Err(e)
}
}
}
7.1 Required Log Events​
- Issue creation start/success/failure
- Query execution with parameters
- Index update operations
- Transaction commit/rollback
- Performance metrics (duration)
8. Error Handling​
Per ADR-026 Error Handling Standards:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IssueError {
#[error("Issue not found: {id}")]
NotFound { id: Uuid },
#[error("Invalid severity: {0}")]
InvalidSeverity(String),
#[error("Database error: {0}")]
DatabaseError(#[from] foundationdb::Error),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
#[error("Tenant mismatch: expected {expected}, got {actual}")]
TenantMismatch { expected: String, actual: String },
}
// User-friendly error messages
impl IssueError {
pub fn user_message(&self) -> String {
match self {
IssueError::NotFound { .. } =>
"The requested issue could not be found".into(),
IssueError::InvalidSeverity(_) =>
"Please use: critical, high, medium, or low".into(),
IssueError::DatabaseError(_) =>
"A database error occurred. Please try again".into(),
IssueError::SerializationError(_) =>
"Invalid data format".into(),
IssueError::TenantMismatch { .. } =>
"Access denied".into(),
}
}
}
9. Performance Specifications​
9.1 SLA Requirements​
- Issue creation: p99 < 100ms
- Single issue lookup: p99 < 10ms
- Index queries (1000 results): p99 < 50ms
- Concurrent operations: 10,000/sec minimum
9.2 Optimization Strategies​
// Batch operations for efficiency
pub async fn create_batch(&self, issues: Vec<Issue>) -> Result<Vec<Uuid>> {
self.db.run(|trx| {
let issues = issues.clone();
let tenant_id = self.tenant_id.clone();
async move {
let mut ids = Vec::new();
for issue in issues {
// Batch all writes in single transaction
let key = format!("/{}/codi2/issues/{}", tenant_id, issue.id);
let value = serde_json::to_vec(&issue)?;
trx.set(&key, &value);
// Create all indexes
// ... index creation code ...
ids.push(issue.id);
}
Ok(ids)
}
}).await
}
10. Deployment Configuration​
10.1 Environment Variables​
FDB_CLUSTER_FILE: /etc/foundationdb/fdb.cluster
CODI2_TENANT_ID: ${TENANT_ID}
CODI2_LOG_LEVEL: info
CODI2_METRICS_ENABLED: true
10.2 Health Check Endpoint​
pub async fn health_check(&self) -> Result<HealthStatus> {
// Verify FDB connection
let test_key = format!("/{}/codi2/health_check", self.tenant_id);
match self.db.run(|trx| async move {
trx.get(&test_key, false).await
}).await {
Ok(_) => Ok(HealthStatus::Healthy),
Err(e) => {
error!("Health check failed: {}", e);
Err(e.into())
}
}
}
Document Approval
By: _________________________ Date: _____________
By: _________________________ Date: _____________