ADR-011-v4: Audit & Compliance - Part 2 (Technical)
Document Specification Block​
Document: ADR-011-v4-audit-compliance-part2-technical
Version: 1.0.0
Purpose: Constrain AI implementation with exact technical specifications for audit and compliance
Audience: AI agents, developers implementing the system
Date Created: 2025-08-31
Date Modified: 2025-08-31
QA Review Date: 2025-08-31
Status: APPROVED
Table of Contents​
- Constraints
- Dependencies
- Component Architecture
- Data Models
- Implementation Patterns
- API Specifications
- Testing Requirements
- Performance Benchmarks
- Security Controls
- Logging and Error Handling
- References
- Approval Signatures
1. Constraints​
CONSTRAINT: Mandatory Audit Events​
Every API request, data modification, authentication attempt, and AI operation MUST generate an audit event. No exceptions.
CONSTRAINT: Immutable Storage​
Audit events MUST be append-only. Updates or deletions are forbidden except through compliant retention policies.
CONSTRAINT: Multi-Tenant Isolation​
Each tenant's audit data MUST be cryptographically isolated using tenant-prefixed keys in FoundationDB.
CONSTRAINT: GDPR Compliance​
System MUST support data export, selective deletion, and anonymization while maintaining legally required audit trails.
CONSTRAINT: Performance Requirements​
Audit system MUST NOT add more than 10ms latency to any operation and MUST handle 10,000 events/second per region.
2. Dependencies​
cargo.toml Dependencies​
[dependencies]
# Core async runtime
tokio = { version = "1.35", features = ["full"] }
# Database
foundationdb = { version = "0.8", features = ["embedded-fdb-include"] }
# Web framework
actix-web = "4.4"
actix-rt = "2.9"
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
bincode = "1.3"
# Error handling
anyhow = "1.0"
thiserror = "1.0"
# Utilities
uuid = { version = "1.6", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
# Cryptography
sha2 = "0.10"
chacha20poly1305 = "0.10"
# JWT Authentication
jsonwebtoken = "9.2"
[dev-dependencies]
# Testing
tokio-test = "0.4"
3. Component Architecture​
// File: src/services/audit_service.rs
use std::sync::Arc;
use anyhow::Result;
use uuid::Uuid;
use foundationdb::Database;
// Core audit service structure
pub struct AuditService {
db: Arc<Database>,
enricher: Arc<EventEnricher>,
integrity: Arc<IntegrityService>,
retention: Arc<RetentionManager>,
}
// File: src/services/audit_service.rs
// Event flow pipeline
impl AuditService {
pub async fn log_event(&self, event: AuditEvent) -> Result<Uuid> {
// 1. Enrich with context
let enriched = self.enricher.enrich(event).await?;
// 2. Calculate integrity hash
let hashed = self.integrity.add_proof(enriched).await?;
// 3. Store in FoundationDB
let repo = AuditLogRepository::new(self.db.clone(), hashed.tenant_id);
repo.store_event(&hashed).await?;
// 4. Async retention check
let tenant_id = hashed.tenant_id;
let retention = self.retention.clone();
tokio::spawn(async move {
if let Err(e) = retention.check_policies(tenant_id).await {
tracing::error!("Retention check failed: {}", e);
}
});
Ok(hashed.id)
}
}
4. Data Models​
Audit Event Structure​
// File: src/models/audit_event.rs
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
#[derive(Serialize, Deserialize)]
pub struct AuditEvent {
// Identity
pub id: Uuid,
pub timestamp: DateTime<Utc>,
pub tenant_id: Uuid,
pub shard_key: String, // Format: "{tenant_id}-{YYYYMMDD}"
// Actor context
pub actor: AuditActor,
pub impersonated_by: Option<Box<AuditActor>>,
// Event details
pub event: AuditEventType,
pub resource: ResourceDescriptor,
pub outcome: EventOutcome,
// Compliance metadata
pub compliance: ComplianceMetadata,
pub integrity: IntegrityProof,
}
// File: src/models/compliance.rs
// GDPR-compliant data subject request
#[derive(Serialize, Deserialize)]
pub struct DataSubjectRequest {
pub id: Uuid,
pub tenant_id: Uuid,
pub subject_id: Uuid,
pub request_type: DsrType,
pub status: DsrStatus,
pub data_categories: Vec<DataCategory>,
}
// Retention policy configuration
#[derive(Serialize, Deserialize)]
pub struct RetentionPolicy {
pub id: String,
pub data_type: DataType,
pub retention_period: Duration,
pub action: RetentionAction,
}
5. Implementation Patterns​
Repository Pattern for Audit Storage​
// File: src/db/repositories/audit_log_repository.rs
use foundationdb::{Database, Transaction};
use anyhow::Result;
use uuid::Uuid;
impl AuditLogRepository {
// Store with integrity chain
pub async fn store_event(&self, event: &AuditEvent) -> Result<()> {
let tx = self.db.create_transaction()?;
// Get previous hash for chain
let prev_hash = self.get_latest_hash(&tx).await?;
let event = event.with_integrity(prev_hash);
// Store event
let key = format!("{}/audit/{}/{}",
self.tenant_id,
event.timestamp.format("%Y%m%d"),
event.id
);
let value = bincode::serialize(&event)?;
tx.set(&key, &value);
// Update indices
self.update_actor_index(&tx, &event).await?;
self.update_resource_index(&tx, &event).await?;
tx.commit().await?;
Ok(())
}
// GDPR-compliant data export
pub async fn export_user_data(&self, user_id: Uuid) -> Result<UserDataExport> {
let mut export = UserDataExport::new(user_id);
// Collect all data categories
for category in DataCategory::iter() {
let data = self.collect_category_data(user_id, category).await?;
export.add_category(category, data);
}
Ok(export)
}
}
Compliance Service Implementation​
impl ComplianceService {
// Process GDPR request
pub async fn process_dsr(&self, request: DataSubjectRequest) -> Result<()> {
match request.request_type {
DsrType::Access => self.handle_access_request(request).await,
DsrType::Erasure => self.handle_erasure_request(request).await,
DsrType::Portability => self.handle_portability_request(request).await,
_ => Err(anyhow!("Unsupported request type"))
}
}
// Selective deletion with audit preservation
async fn handle_erasure_request(&self, request: DataSubjectRequest) -> Result<()> {
let tx = self.db.create_transaction()?;
// Find erasable data (not legally required)
let erasable = self.find_erasable_data(&tx, request.subject_id).await?;
for key in erasable {
// Anonymize instead of delete for audit trail
let mut record = self.get_record(&tx, &key).await?;
record.anonymize();
tx.set(&key, &bincode::serialize(&record)?);
}
tx.commit().await?;
Ok(())
}
}
6. API Specifications​
JWT Authentication Middleware​
// File: src/middleware/auth.rs
use actix_web::{FromRequest, HttpRequest, dev::Payload};
use jsonwebtoken::{decode, DecodingKey, Validation};
#[derive(Debug, Deserialize)]
pub struct Claims {
pub user_id: Uuid,
pub tenant_id: Uuid,
pub roles: Vec<String>,
pub exp: usize,
}
impl FromRequest for Claims {
type Error = actix_web::Error;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let auth_header = req.headers().get("Authorization");
match auth_header {
Some(header) => {
let token = header.to_str().unwrap().replace("Bearer ", "");
let key = DecodingKey::from_secret(JWT_SECRET.as_bytes());
match decode::<Claims>(&token, &key, &Validation::default()) {
Ok(token_data) => ready(Ok(token_data.claims)),
Err(_) => ready(Err(ErrorUnauthorized("Invalid token"))),
}
}
None => ready(Err(ErrorUnauthorized("Missing authorization header"))),
}
}
}
Audit Query Endpoints​
// File: src/api/handlers/audit_routes.rs
use actix_web::{web::{Query, Json}, get, post, Result};
use std::sync::Arc;
// Query audit events
#[get("/audit/events")]
pub async fn query_events(
Query(params): Query<AuditQueryParams>,
Extension(audit_service): Extension<Arc<AuditService>>,
Claims(claims): Claims,
) -> Result<Json<Vec<AuditEvent>>> {
// Validate tenant access
validate_tenant_access(&claims, params.tenant_id)?;
let events = audit_service
.query_events(params.tenant_id, params.start, params.end, params.limit)
.await?;
Ok(Json(events))
}
// GDPR data export
#[post("/compliance/gdpr/export")]
pub async fn export_user_data(
Json(request): Json<ExportRequest>,
Extension(compliance_service): Extension<Arc<ComplianceService>>,
Claims(claims): Claims,
) -> Result<Json<ExportResponse>> {
// Verify requester is subject or has permission
verify_gdpr_permission(&claims, request.subject_id)?;
let export = compliance_service
.export_user_data(request.subject_id)
.await?;
Ok(Json(ExportResponse {
export_id: export.id,
download_url: generate_secure_url(export.id),
expires_at: Utc::now() + Duration::hours(24),
}))
}
// Compliance report generation
#[post("/compliance/reports/generate")]
pub async fn generate_report(
Json(request): Json<ReportRequest>,
Extension(report_service): Extension<Arc<ReportService>>,
Claims(claims): Claims,
) -> Result<Json<ReportResponse>> {
validate_compliance_officer(&claims)?;
let report = report_service
.generate_report(request.report_type, request.period)
.await?;
Ok(Json(report))
}
7. Testing Requirements​
Test Coverage Requirements​
- Unit Test Coverage: ≥90% of all code paths
- Integration Test Coverage: ≥80% of component interactions
- Critical Path Coverage: 100% for audit, authentication, and compliance operations
- Performance Test Coverage: All endpoints must be benchmarked
Unit Tests​
// File: src/services/audit_service_tests.rs
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_audit_event_integrity_chain() {
let service = setup_test_service().await;
// Create chain of events
let event1 = create_test_event();
let id1 = service.log_event(event1).await.unwrap();
let event2 = create_test_event();
let id2 = service.log_event(event2).await.unwrap();
// Verify chain integrity
let stored1 = service.get_event(id1).await.unwrap();
let stored2 = service.get_event(id2).await.unwrap();
assert_eq!(stored2.integrity.previous_hash, stored1.integrity.hash);
}
#[tokio::test]
async fn test_gdpr_data_export() {
let service = setup_test_service().await;
let user_id = Uuid::new_v4();
// Create various user data
create_user_audit_trail(&service, user_id).await;
// Export data
let export = service.export_user_data(user_id).await.unwrap();
// Verify all categories present
assert!(export.has_category(DataCategory::Profile));
assert!(export.has_category(DataCategory::Activity));
assert!(export.has_category(DataCategory::AuditLogs));
}
#[tokio::test]
async fn test_retention_policy_enforcement() {
let service = setup_test_service().await;
// Create old events
let old_event = create_event_with_age(Duration::days(100));
service.log_event(old_event).await.unwrap();
// Run retention
service.enforce_retention_policies().await.unwrap();
// Verify anonymization
let events = service.query_old_events(Duration::days(91)).await.unwrap();
assert!(events[0].is_anonymized());
}
}
Integration Tests​
#[tokio::test]
async fn test_compliance_workflow() {
let app = setup_test_app().await;
// Submit GDPR request
let response = app.post("/compliance/gdpr/request")
.json(&json!({
"request_type": "access",
"subject_id": "user123"
}))
.send()
.await
.unwrap();
assert_eq!(response.status(), 200);
// Process request
let request_id = response.json::<DsrResponse>().await.unwrap().id;
// Check status
let status = app.get(&format!("/compliance/gdpr/status/{}", request_id))
.send()
.await
.unwrap()
.json::<DsrStatus>()
.await
.unwrap();
assert_eq!(status, DsrStatus::Completed);
}
7. Performance Benchmarks​
Required Performance Metrics​
// Benchmark configuration
const BENCHMARK_EVENTS: usize = 1_000_000;
const TARGET_THROUGHPUT: usize = 10_000; // events/second
const MAX_LATENCY_MS: u64 = 10;
#[bench]
fn bench_event_ingestion(b: &mut Bencher) {
let runtime = tokio::runtime::Runtime::new().unwrap();
let service = runtime.block_on(setup_service());
b.iter(|| {
runtime.block_on(async {
let events = generate_events(1000);
for event in events {
service.log_event(event).await.unwrap();
}
})
});
}
// Performance requirements
impl PerformanceRequirements {
pub fn validate() -> bool {
let metrics = run_benchmarks();
metrics.throughput >= TARGET_THROUGHPUT &&
metrics.p99_latency_ms <= MAX_LATENCY_MS &&
metrics.storage_per_event <= 100 // bytes after compression
}
}
8. Security Controls​
Access Control​
// Role-based access to audit data
impl AuditAccessControl {
pub fn can_read_audit(&self, claims: &Claims, tenant_id: Uuid) -> bool {
claims.tenant_id == tenant_id &&
(claims.roles.contains("admin") ||
claims.roles.contains("auditor") ||
claims.roles.contains("compliance_officer"))
}
pub fn can_export_data(&self, claims: &Claims, subject_id: Uuid) -> bool {
claims.user_id == subject_id || // Own data
claims.roles.contains("privacy_officer") ||
claims.roles.contains("admin")
}
}
// Encryption for sensitive data
impl EncryptionService {
pub fn encrypt_pii(&self, data: &str) -> Result<Vec<u8>> {
let key = self.get_tenant_key()?;
let nonce = generate_nonce();
let cipher = ChaCha20Poly1305::new(&key);
cipher.encrypt(&nonce, data.as_bytes())
.map_err(|e| anyhow!("Encryption failed: {}", e))
}
}
10. Logging and Error Handling​
Logging Pattern​
// File: src/logging/mod.rs
use tracing::{info, warn, error, debug};
use serde_json::json;
pub fn log_audit_event(event: &AuditEvent) {
info!(
correlation_id = %event.lineage.correlation_id,
tenant_id = %event.tenant_id,
event_type = ?event.event,
actor = %event.actor.display_name(),
"Audit event processed"
);
}
pub fn log_compliance_action(action: &str, user_id: Uuid, result: &Result<()>) {
match result {
Ok(_) => info!(
action = action,
user_id = %user_id,
status = "success",
"Compliance action completed"
),
Err(e) => error!(
action = action,
user_id = %user_id,
error = %e,
status = "failed",
"Compliance action failed"
),
}
}
Error Handling with User-Friendly Messages​
// File: src/errors/compliance_errors.rs
use thiserror::Error;
use actix_web::{ResponseError, HttpResponse};
#[derive(Error, Debug)]
pub enum ComplianceError {
#[error("You don't have permission to view audit logs. Please contact your administrator to request 'Auditor' role access.")]
AuditAccessDenied,
#[error("Your data export request couldn't be completed. This is usually due to high system load. Please try again in 5 minutes or contact support.")]
ExportFailed(#[source] anyhow::Error),
#[error("The compliance report couldn't be generated because some data is still being processed. The report will be ready in approximately 10 minutes.")]
ReportGenerationInProgress,
#[error("This data cannot be deleted due to legal retention requirements. It will be automatically removed on {date} when the retention period expires.")]
RetentionPolicyConflict { date: String },
#[error("The requested data subject operation is not supported: {operation}")]
UnsupportedDsrOperation { operation: String },
}
impl ResponseError for ComplianceError {
fn error_response(&self) -> HttpResponse {
match self {
Self::AuditAccessDenied => HttpResponse::Forbidden().json(json!({
"error": "access_denied",
"message": self.to_string(),
"action_required": "Request 'Auditor' role from administrator"
})),
Self::ExportFailed(_) => HttpResponse::ServiceUnavailable().json(json!({
"error": "export_failed",
"message": self.to_string(),
"retry_after": 300
})),
Self::ReportGenerationInProgress => HttpResponse::Accepted().json(json!({
"error": "report_pending",
"message": self.to_string(),
"estimated_time": 600
})),
Self::RetentionPolicyConflict { .. } => HttpResponse::Conflict().json(json!({
"error": "retention_conflict",
"message": self.to_string()
})),
Self::UnsupportedDsrOperation { .. } => HttpResponse::BadRequest().json(json!({
"error": "unsupported_operation",
"message": self.to_string()
})),
}
}
}
11. References​
- ADR-001-v4: Multi-Tenant Architecture
- ADR-005-v4: Authentication & Authorization
- LOGGING-STANDARD-v4
- ERROR-HANDLING-STANDARD-v4
Version Compatibility​
- FoundationDB: 7.1.0+ required for transaction tagging
- Rust: 1.75.0+ for async trait support
- Actix-web: 4.4+ for enhanced middleware
- Tokio: 1.35+ for improved performance
12. Approval Signatures​
Technical Sign-off​
| Component | Owner | Approved | Date |
|---|---|---|---|
| Architecture | Session5 | ✓ | 2025-08-31 |
| Implementation | Pending | - | - |
| Security Review | Pending | - | - |
| Performance Test | Pending | - | - |
Implementation Checklist​
- Audit event models implemented
- Repository pattern with FDB integration
- GDPR compliance endpoints
- Retention policy engine
- Integration tests passing
- Performance benchmarks met
- Security controls validated