Skip to main content

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​

  1. Technical Requirements
  2. Component Architecture
  3. Implementation Specifications
  4. CLI Integration
  5. Testing Requirements
  6. Constraints for AI Implementation
  7. Logging Requirements
  8. Error Handling
  9. Performance Specifications
  10. Deployment Configuration

↑ Back to Top

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​

↑ Back to Top

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
}
}

↑ Back to Top

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​

  1. MUST use FoundationDB transactions for all operations
  2. MUST update all relevant indexes atomically
  3. MUST validate all inputs before storage
  4. MUST handle FDB errors gracefully
  5. MUST use efficient range queries
  6. MUST include tenant_id in all keys
  7. MUST log all operations per LOGGING-STANDARD-v4
  8. MUST handle errors per ADR-026

6.2 MUST NOT Requirements​

  1. MUST NOT store large blobs (>100KB) in values
  2. MUST NOT use blocking operations
  3. MUST NOT bypass transaction boundaries
  4. MUST NOT ignore index updates
  5. MUST NOT use keys longer than 10KB
  6. MUST NOT leak data across tenants

↑ Back to Top

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(),
}
}
}

↑ Back to Top

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: _____________