1use blake3;
6use dashmap::DashMap; use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{
10 atomic::{AtomicU32, AtomicU64, Ordering},
11 Arc,
12};
13use std::time::{SystemTime, UNIX_EPOCH};
14use thiserror::Error;
15use tokio::fs;
16use tokio::io::{AsyncReadExt, AsyncWriteExt};
17
18#[derive(Debug, Error)]
20pub enum SourceOfTruthError {
21 #[error("Work item not found: {0}")]
22 WorkItemNotFound(String),
23
24 #[error("Duplication detected: {0}")]
25 DuplicationDetected(String),
26
27 #[error("Invalid work item ID format: {0}")]
28 InvalidWorkItemId(String),
29
30 #[error("Canonical document conflict: {0}")]
31 CanonicalConflict(String),
32
33 #[error("Registry corruption detected: {0}")]
34 RegistryCorruption(String),
35
36 #[error("IO error: {0}")]
37 IoError(#[from] std::io::Error),
38
39 #[error("Serialization error: {0}")]
40 SerializationError(#[from] serde_json::Error),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub enum WorkStatus {
46 Planning,
47 InProgress,
48 CodeReview,
49 Testing,
50 Completed,
51 Blocked(String), }
53
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
56pub enum DuplicationCheckStatus {
57 Passed,
58 Failed(String), NotChecked,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64pub enum CanonicalStatus {
65 Draft,
66 Review,
67 Verified,
68 Deprecated,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct WorkItem {
74 pub id: String,
75 pub title: String,
76 pub status: WorkStatus,
77 pub component: String,
78 pub files_modified: Vec<String>,
79 pub duplication_check: DuplicationCheckStatus,
80 pub source_of_truth_updated: bool,
81 pub verification_hash: [u8; 32],
82 pub completion_timestamp: Option<u64>,
83 pub evidence_link: String,
84 pub dependencies: Vec<String>,
85 pub blockers: Vec<String>,
86 pub created: u64,
87 pub last_updated: u64,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct CanonicalDocument {
93 pub file_path: String,
94 pub work_item_id: String,
95 pub verification_hash: [u8; 32],
96 pub last_updated: u64,
97 pub canonical_status: CanonicalStatus,
98 pub authority_level: u8, }
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct SourceOfTruthEntry {
104 pub file_path: String,
105 pub work_item_id: String,
106 pub verification_hash: [u8; 32],
107 pub last_updated: u64,
108 pub canonical_status: CanonicalStatus,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct DuplicationEntry {
114 pub content_hash: [u8; 32],
115 pub file_path: String,
116 pub function_signature: Option<String>,
117 pub first_occurrence: u64, }
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct FunctionSignature {
123 pub name: String,
124 pub parameters: Vec<String>,
125 pub return_type: Option<String>,
126 pub visibility: String,
127 pub file_path: String,
128 pub line_number: u32,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct CodeFingerprint {
134 pub content_hash: [u8; 32],
135 pub normalized_hash: [u8; 32], pub function_count: u32,
137 pub line_count: u32,
138 pub file_path: String,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct DocumentationEntry {
144 pub content_hash: [u8; 32],
145 pub title: String,
146 pub file_path: String,
147 pub section: String,
148}
149
150#[derive(Debug)]
152pub struct SourceOfTruthRegistry {
153 canonical_documents: DashMap<String, CanonicalDocument>,
155 work_items: DashMap<String, WorkItem>,
157 duplication_index: DashMap<String, DuplicationEntry>,
159 function_signatures: DashMap<String, FunctionSignature>,
161 #[allow(dead_code)]
163 code_fingerprints: DashMap<String, CodeFingerprint>,
164 #[allow(dead_code)]
166 documentation_index: DashMap<String, DocumentationEntry>,
167 last_updated: AtomicU64,
169 version: AtomicU32,
171 registry_path: String,
173}
174
175impl SourceOfTruthRegistry {
176 pub async fn new(registry_path: String) -> Result<Self, SourceOfTruthError> {
178 let registry = Self {
179 canonical_documents: DashMap::new(),
180 work_items: DashMap::new(),
181 duplication_index: DashMap::new(),
182 function_signatures: DashMap::new(),
183 code_fingerprints: DashMap::new(),
184 documentation_index: DashMap::new(),
185 last_updated: AtomicU64::new(Self::current_timestamp()),
186 version: AtomicU32::new(1),
187 registry_path,
188 };
189
190 if let Some(parent) = std::path::Path::new(®istry.registry_path).parent() {
192 fs::create_dir_all(parent).await?;
193 }
194
195 if registry.load_from_disk().await.is_err() {
197 registry.save_to_disk().await?;
199 }
200
201 Ok(registry)
202 }
203
204 pub fn generate_work_item_id(&self) -> String {
206 let now = chrono::Utc::now();
207 let date_prefix = now.format("%Y-%m-%d").to_string();
208
209 let mut sequence = 1;
211 loop {
212 let candidate_id = format!("WI-{date_prefix}-{sequence}");
213 if !self.work_items.contains_key(&candidate_id) {
214 return candidate_id;
215 }
216 sequence += 1;
217 }
218 }
219
220 pub async fn create_work_item(
222 &self,
223 title: String,
224 component: String,
225 ) -> Result<WorkItem, SourceOfTruthError> {
226 let work_id = self.generate_work_item_id();
228
229 let duplication_status = self.check_work_item_duplication(&title, &component).await?;
231 if matches!(duplication_status, DuplicationCheckStatus::Failed(_)) {
232 return Err(SourceOfTruthError::DuplicationDetected(format!(
233 "Work item title or component already exists: {title}"
234 )));
235 }
236
237 let work_item = WorkItem {
239 id: work_id.clone(),
240 title,
241 status: WorkStatus::Planning,
242 component,
243 files_modified: Vec::new(),
244 duplication_check: duplication_status,
245 source_of_truth_updated: false,
246 verification_hash: [0u8; 32],
247 completion_timestamp: None,
248 evidence_link: String::new(),
249 dependencies: Vec::new(),
250 blockers: Vec::new(),
251 created: Self::current_timestamp(),
252 last_updated: Self::current_timestamp(),
253 };
254
255 self.work_items.insert(work_id.clone(), work_item.clone());
257 self.update_last_modified();
258
259 self.save_to_disk().await?;
261
262 Ok(work_item)
263 }
264
265 pub async fn update_work_item_status(
267 &self,
268 work_id: &str,
269 new_status: WorkStatus,
270 ) -> Result<(), SourceOfTruthError> {
271 use log::debug;
272 debug!("update_work_item_status called for work_id: {work_id}, new_status: {new_status:?}");
273 let mut work_item = match self.work_items.get_mut(work_id) {
275 Some(item) => item,
276 None => {
277 debug!("Work item not found: {work_id}");
278 return Err(SourceOfTruthError::WorkItemNotFound(work_id.to_string()));
279 }
280 };
281
282 if let Err(e) = self.validate_status_transition(&work_item.status, &new_status) {
284 debug!("Invalid status transition: {e:?}");
285 return Err(e);
286 }
287
288 work_item.status = new_status.clone();
290 work_item.last_updated = Self::current_timestamp();
291
292 if matches!(new_status, WorkStatus::Completed) {
294 work_item.completion_timestamp = Some(Self::current_timestamp());
295 work_item.verification_hash = self.generate_verification_hash(&work_item).await?;
296 work_item.source_of_truth_updated = true;
297 }
298
299 self.update_last_modified();
300 self.save_to_disk().await?;
301
302 debug!("update_work_item_status completed for work_id: {work_id}");
303 Ok(())
304 }
305
306 pub async fn check_code_duplication(
308 &self,
309 file_path: &str,
310 content: &str,
311 ) -> Result<DuplicationCheckStatus, SourceOfTruthError> {
312 let content_hash = blake3::hash(content.as_bytes()).into();
314
315 for entry in self.duplication_index.iter() {
317 if entry.value().content_hash == content_hash && entry.value().file_path != file_path {
318 return Ok(DuplicationCheckStatus::Failed(format!(
319 "Exact content duplication found in {}",
320 entry.value().file_path
321 )));
322 }
323 }
324
325 let functions = self.extract_rust_functions(content)?;
327 for function in functions {
328 let signature_key = format!("{}::{}", function.name, function.parameters.join(","));
329 if let Some(existing) = self.function_signatures.get(&signature_key) {
330 if existing.file_path != file_path {
331 return Ok(DuplicationCheckStatus::Failed(format!(
332 "Function signature duplication: {} in {}",
333 signature_key, existing.file_path
334 )));
335 }
336 }
337 }
338
339 self.duplication_index.insert(
341 file_path.to_string(),
342 DuplicationEntry {
343 content_hash,
344 file_path: file_path.to_string(),
345 function_signature: None,
346 first_occurrence: Self::current_timestamp(),
347 },
348 );
349
350 Ok(DuplicationCheckStatus::Passed)
351 }
352
353 async fn check_work_item_duplication(
355 &self,
356 title: &str,
357 component: &str,
358 ) -> Result<DuplicationCheckStatus, SourceOfTruthError> {
359 for item in self.work_items.iter() {
360 let work_item = item.value();
361 if work_item.title == title && work_item.component == component {
362 return Ok(DuplicationCheckStatus::Failed(format!(
363 "Duplicate work item: {title} in {component}"
364 )));
365 }
366 }
367 Ok(DuplicationCheckStatus::Passed)
368 }
369
370 fn extract_rust_functions(
372 &self,
373 content: &str,
374 ) -> Result<Vec<FunctionSignature>, SourceOfTruthError> {
375 let mut functions = Vec::new();
376 let lines: Vec<&str> = content.lines().collect();
377
378 for (line_num, line) in lines.iter().enumerate() {
379 if let Some(func) = self.parse_rust_function_signature(line, line_num as u32 + 1) {
380 functions.push(func);
381 }
382 }
383
384 Ok(functions)
385 }
386
387 fn parse_rust_function_signature(
389 &self,
390 line: &str,
391 line_number: u32,
392 ) -> Option<FunctionSignature> {
393 let trimmed = line.trim();
394
395 if trimmed.starts_with("pub fn ") || trimmed.starts_with("fn ") {
397 let visibility = if trimmed.starts_with("pub ") {
398 "pub"
399 } else {
400 "private"
401 };
402
403 if let Some(paren_start) = trimmed.find('(') {
405 if let Some(fn_start) = trimmed.find("fn ") {
406 let name_start = fn_start + 3;
407 let name = trimmed[name_start..paren_start].trim().to_string();
408
409 if let Some(paren_end) = trimmed.find(')') {
411 let param_str = &trimmed[paren_start + 1..paren_end];
412 let parameters: Vec<String> = param_str
413 .split(',')
414 .map(|p| p.trim().split(':').next().unwrap_or("").trim().to_string())
415 .filter(|p| !p.is_empty())
416 .collect();
417
418 let return_type = trimmed.find("->").map(|arrow_pos| {
420 trimmed[arrow_pos + 2..]
421 .split_whitespace()
422 .next()
423 .unwrap_or("")
424 .to_string()
425 });
426
427 return Some(FunctionSignature {
428 name,
429 parameters,
430 return_type,
431 visibility: visibility.to_string(),
432 file_path: String::new(), line_number,
434 });
435 }
436 }
437 }
438 }
439
440 None
441 }
442
443 fn validate_status_transition(
445 &self,
446 current: &WorkStatus,
447 new: &WorkStatus,
448 ) -> Result<(), SourceOfTruthError> {
449 use log::debug;
450 match (current, new) {
451 (WorkStatus::Planning, WorkStatus::InProgress) => Ok(()),
452 (WorkStatus::InProgress, WorkStatus::CodeReview) => Ok(()),
453 (WorkStatus::CodeReview, WorkStatus::Testing) => Ok(()),
454 (WorkStatus::Testing, WorkStatus::Completed) => Ok(()),
455 (_, WorkStatus::Blocked(_)) => Ok(()), (WorkStatus::Blocked(_), _) => Ok(()), _ => {
458 debug!("Invalid status transition from {current:?} to {new:?}");
459 Err(SourceOfTruthError::InvalidWorkItemId(format!(
460 "Invalid status transition from {current:?} to {new:?}"
461 )))
462 }
463 }
464 }
465
466 async fn generate_verification_hash(
468 &self,
469 work_item: &WorkItem,
470 ) -> Result<[u8; 32], SourceOfTruthError> {
471 let mut hasher = blake3::Hasher::new();
472 hasher.update(work_item.id.as_bytes());
473 hasher.update(work_item.title.as_bytes());
474 hasher.update(&work_item.completion_timestamp.unwrap_or(0).to_le_bytes());
475
476 for file_path in &work_item.files_modified {
478 if let Ok(content) = fs::read_to_string(file_path).await {
479 hasher.update(content.as_bytes());
480 }
481 }
482
483 Ok(hasher.finalize().into())
484 }
485
486 fn current_timestamp() -> u64 {
488 SystemTime::now()
489 .duration_since(UNIX_EPOCH)
490 .unwrap()
491 .as_nanos() as u64
492 }
493
494 fn update_last_modified(&self) {
496 self.last_updated
497 .store(Self::current_timestamp(), Ordering::Relaxed);
498 self.version.fetch_add(1, Ordering::Relaxed);
499 }
500
501 async fn save_to_disk(&self) -> Result<(), SourceOfTruthError> {
503 let registry_data = RegistryData {
505 canonical_documents: self
506 .canonical_documents
507 .iter()
508 .map(|entry| (entry.key().clone(), entry.value().clone()))
509 .collect(),
510 work_items: self
511 .work_items
512 .iter()
513 .map(|entry| (entry.key().clone(), entry.value().clone()))
514 .collect(),
515 duplication_index: self
516 .duplication_index
517 .iter()
518 .map(|entry| (entry.key().clone(), entry.value().clone()))
519 .collect(),
520 last_updated: self.last_updated.load(Ordering::Relaxed),
521 version: self.version.load(Ordering::Relaxed),
522 };
523
524 let json_data = serde_json::to_string_pretty(®istry_data)?;
525 let mut file = fs::File::create(&self.registry_path).await?;
526 file.write_all(json_data.as_bytes()).await?;
527
528 Ok(())
529 }
530
531 async fn load_from_disk(&self) -> Result<(), SourceOfTruthError> {
533 let mut file = fs::File::open(&self.registry_path).await?;
534 let mut contents = String::new();
535 file.read_to_string(&mut contents).await?;
536
537 let registry_data: RegistryData = serde_json::from_str(&contents)?;
538
539 self.canonical_documents.clear();
541 self.work_items.clear();
542 self.duplication_index.clear();
543
544 for (key, value) in registry_data.canonical_documents {
546 self.canonical_documents.insert(key, value);
547 }
548 for (key, value) in registry_data.work_items {
549 self.work_items.insert(key, value);
550 }
551 for (key, value) in registry_data.duplication_index {
552 self.duplication_index.insert(key, value);
553 }
554
555 self.last_updated
556 .store(registry_data.last_updated, Ordering::Relaxed);
557 self.version.store(registry_data.version, Ordering::Relaxed);
558
559 Ok(())
560 }
561}
562
563#[derive(Debug, Serialize, Deserialize)]
565struct RegistryData {
566 canonical_documents: HashMap<String, CanonicalDocument>,
567 work_items: HashMap<String, WorkItem>,
568 duplication_index: HashMap<String, DuplicationEntry>,
569 last_updated: u64,
570 version: u32,
571}
572
573static GLOBAL_REGISTRY: once_cell::sync::Lazy<
575 Arc<tokio::sync::RwLock<Option<SourceOfTruthRegistry>>>,
576> = once_cell::sync::Lazy::new(|| Arc::new(tokio::sync::RwLock::new(None)));
577
578pub async fn initialize_global_registry(registry_path: String) -> Result<(), SourceOfTruthError> {
580 let registry = SourceOfTruthRegistry::new(registry_path).await?;
581 let mut global = GLOBAL_REGISTRY.write().await;
582 *global = Some(registry);
583 Ok(())
584}
585
586pub async fn get_global_registry() -> Arc<tokio::sync::RwLock<Option<SourceOfTruthRegistry>>> {
588 GLOBAL_REGISTRY.clone()
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use tempfile::tempdir;
595
596 #[tokio::test]
597 async fn test_work_item_creation() {
598 let temp_dir = tempdir().unwrap();
599 let registry_path = temp_dir
600 .path()
601 .join("registry.json")
602 .to_string_lossy()
603 .to_string();
604
605 let registry = SourceOfTruthRegistry::new(registry_path).await.unwrap();
606
607 let work_item = registry
608 .create_work_item("Test work item".to_string(), "test_component".to_string())
609 .await
610 .unwrap();
611
612 assert!(work_item.id.starts_with("WI-"));
613 assert_eq!(work_item.status, WorkStatus::Planning);
614 assert_eq!(work_item.duplication_check, DuplicationCheckStatus::Passed);
615 }
616
617 #[tokio::test]
618 async fn test_duplication_detection() {
619 let temp_dir = tempdir().unwrap();
620 let registry_path = temp_dir
621 .path()
622 .join("registry.json")
623 .to_string_lossy()
624 .to_string();
625
626 let registry = SourceOfTruthRegistry::new(registry_path).await.unwrap();
627
628 let _work_item1 = registry
630 .create_work_item("Unique title".to_string(), "component1".to_string())
631 .await
632 .unwrap();
633
634 let result = registry
636 .create_work_item("Unique title".to_string(), "component1".to_string())
637 .await;
638
639 assert!(result.is_err());
640 assert!(matches!(
641 result.unwrap_err(),
642 SourceOfTruthError::DuplicationDetected(_)
643 ));
644 }
645
646 #[tokio::test]
647 async fn test_status_transition_valid() {
648 use tokio::time::{timeout, Duration};
649 use log::debug;
650 let temp_dir = tempdir().unwrap();
651 let registry_path = temp_dir
652 .path()
653 .join("registry_valid.json")
654 .to_string_lossy()
655 .to_string();
656
657 let registry = SourceOfTruthRegistry::new(registry_path).await.unwrap();
658 let work_item = registry
659 .create_work_item("Status test valid".to_string(), "test_component".to_string())
660 .await
661 .unwrap();
662
663 let valid = timeout(Duration::from_secs(10), registry.update_work_item_status(&work_item.id, WorkStatus::InProgress)).await;
664 match valid {
665 Ok(Ok(_)) => debug!("Valid status transition succeeded"),
666 Ok(Err(e)) => panic!("Valid status transition failed: {e:?}"),
667 Err(_) => panic!("Timeout on valid status transition"),
668 }
669 }
670
671 #[tokio::test]
672 async fn test_status_transition_invalid() {
673 use tokio::time::{timeout, Duration};
674 use log::debug;
675 let temp_dir = tempdir().unwrap();
676 let registry_path = temp_dir
677 .path()
678 .join("registry_invalid.json")
679 .to_string_lossy()
680 .to_string();
681
682 let registry = SourceOfTruthRegistry::new(registry_path).await.unwrap();
683 let work_item = registry
684 .create_work_item("Status test invalid".to_string(), "test_component".to_string())
685 .await
686 .unwrap();
687
688 let invalid = timeout(Duration::from_secs(10), registry.update_work_item_status(&work_item.id, WorkStatus::Completed)).await;
690 match invalid {
691 Ok(Ok(_)) => panic!("Invalid status transition unexpectedly succeeded"),
692 Ok(Err(_)) => debug!("Invalid status transition correctly failed"),
693 Err(_) => panic!("Timeout on invalid status transition"),
694 }
695 }
696}