1use serde::{Deserialize, Serialize};
28use sha2::{Digest, Sha256};
29use std::collections::HashMap;
30use std::fs;
31use std::path::{Path, PathBuf};
32use std::sync::Mutex;
33use chrono::{DateTime, Utc};
34use hmac::{Hmac, Mac};
35use hex;
36
37type HmacSha256 = Hmac<Sha256>;
38
39static GRAVEYARD_REGISTRY: Mutex<Option<GraveyardRegistry>> = Mutex::new(None);
41
42#[derive(Debug, Clone)]
44pub struct GraveyardRegistry {
45 dead_ids: HashMap<String, PathBuf>,
47}
48
49impl GraveyardRegistry {
50 pub fn initialize(graveyard_path: &Path) -> Result<Self, GraveyardError> {
52 let mut dead_ids = HashMap::new();
53
54 if graveyard_path.exists() {
55 for entry in fs::read_dir(graveyard_path)
56 .map_err(|e| GraveyardError::IoError(e.to_string()))?
57 {
58 let entry = entry.map_err(|e| GraveyardError::IoError(e.to_string()))?;
59 let path = entry.path();
60
61 if path.extension().map_or(false, |ext| ext == "tomb") {
62 if let Some(file_stem) = path.file_stem().and_then(|s| s.to_str()) {
63 dead_ids.insert(file_stem.to_string(), path);
64 }
65 }
66 }
67 }
68
69 Ok(GraveyardRegistry { dead_ids })
70 }
71
72 pub fn is_dead(&self, id: &str) -> bool {
74 self.dead_ids.contains_key(id)
75 }
76
77 pub fn bury(&mut self, id: String, path: PathBuf) {
79 self.dead_ids.insert(id, path);
80 }
81
82 pub fn list_all(&self) -> Vec<String> {
84 self.dead_ids.keys().cloned().collect()
85 }
86
87 pub fn get_tombstone_path(&self, id: &str) -> Option<PathBuf> {
89 self.dead_ids.get(id).cloned()
90 }
91}
92
93#[derive(Debug, Clone)]
95pub enum GraveyardError {
96 IoError(String),
98 TombstoneExists { id: String },
100 NotFound { id: String },
102 SerializationError(String),
104 TamperingDetected { id: String },
106 DirectoryError(String),
108}
109
110impl std::fmt::Display for GraveyardError {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 GraveyardError::IoError(e) => write!(f, "IO Error: {}", e),
114 GraveyardError::TombstoneExists { id } => {
115 write!(f, "Tombstone already exists for ID: {}", id)
116 }
117 GraveyardError::NotFound { id } => write!(f, "No tombstone found for ID: {}", id),
118 GraveyardError::SerializationError(e) => write!(f, "Serialization error: {}", e),
119 GraveyardError::TamperingDetected { id } => {
120 write!(f, "Tampering detected in tombstone: {}", id)
121 }
122 GraveyardError::DirectoryError(e) => write!(f, "Directory error: {}", e),
123 }
124 }
125}
126
127impl std::error::Error for GraveyardError {}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct IdentityBlock {
132 pub id: String,
134 pub creation_time: DateTime<Utc>,
136 pub identity_hash: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct MetabolicRecord {
143 pub final_energy: u64,
145 pub peak_energy: u64,
147 pub initial_energy: u64,
149 pub efficiency_ratio: f64,
151 pub tasks_completed: u32,
153}
154
155impl MetabolicRecord {
156 pub fn calculate_efficiency(tasks: u32, energy_burned: u64) -> f64 {
158 if energy_burned == 0 {
159 0.0
160 } else {
161 tasks as f64 / energy_burned as f64
162 }
163 }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct PathologyReport {
169 pub scars: Vec<ScarRecord>,
171 pub scar_count: usize,
173 pub cause_of_death: String,
175 pub death_timestamp: DateTime<Utc>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ScarRecord {
182 pub timestamp: DateTime<Utc>,
184 pub severity: String,
186 pub description: String,
188 pub context: Option<String>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct CausalChain {
195 pub merkle_root: String,
197 pub event_hashes: Vec<String>,
199 pub total_events: usize,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ParentageRecord {
206 pub parent_id: Option<String>,
208 pub inherited_capacity: Option<u64>,
210 pub inherited_knowledge: Option<String>,
212 pub generation: u32,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct Tombstone {
226 pub identity: IdentityBlock,
228 pub metabolism: MetabolicRecord,
230 pub pathology: PathologyReport,
232 pub causal_chain: CausalChain,
234 pub parentage: ParentageRecord,
236 pub signature: String,
238 pub burial_timestamp: DateTime<Utc>,
240 pub schema_version: u32,
242}
243
244impl Tombstone {
245 pub fn create(
247 id: String,
248 identity_hash: String,
249 creation_time: DateTime<Utc>,
250 final_energy: u64,
251 peak_energy: u64,
252 initial_energy: u64,
253 tasks_completed: u32,
254 scars: Vec<ScarRecord>,
255 cause_of_death: String,
256 ) -> Self {
257 Self::create_with_parentage(
258 id, identity_hash, creation_time, final_energy, peak_energy,
259 initial_energy, tasks_completed, scars, cause_of_death,
260 None, None, None, 0
261 )
262 }
263
264 pub fn create_with_parentage(
266 id: String,
267 identity_hash: String,
268 creation_time: DateTime<Utc>,
269 final_energy: u64,
270 peak_energy: u64,
271 initial_energy: u64,
272 tasks_completed: u32,
273 scars: Vec<ScarRecord>,
274 cause_of_death: String,
275 parent_id: Option<String>,
276 inherited_capacity: Option<u64>,
277 inherited_knowledge: Option<String>,
278 generation: u32,
279 ) -> Self {
280 let efficiency_ratio =
281 MetabolicRecord::calculate_efficiency(tasks_completed, initial_energy - final_energy);
282
283 let pathology = PathologyReport {
284 scar_count: scars.len(),
285 scars: scars.clone(),
286 cause_of_death,
287 death_timestamp: Utc::now(),
288 };
289
290 let causal_chain = Self::create_causal_chain(&scars);
291
292 let parentage = ParentageRecord {
293 parent_id,
294 inherited_capacity,
295 inherited_knowledge,
296 generation,
297 };
298
299 let mut tombstone = Tombstone {
300 identity: IdentityBlock {
301 id,
302 creation_time,
303 identity_hash,
304 },
305 metabolism: MetabolicRecord {
306 final_energy,
307 peak_energy,
308 initial_energy,
309 efficiency_ratio,
310 tasks_completed,
311 },
312 pathology,
313 causal_chain,
314 parentage,
315 signature: String::new(), burial_timestamp: Utc::now(),
317 schema_version: 1,
318 };
319
320 tombstone.signature = Self::calculate_signature(&tombstone);
322 tombstone
323 }
324
325 fn create_causal_chain(scars: &[ScarRecord]) -> CausalChain {
327 let mut event_hashes = Vec::new();
328 let mut hasher = Sha256::new();
329
330 for scar in scars {
331 let scar_json = serde_json::to_string(scar)
332 .unwrap_or_else(|_| format!("{:?}", scar));
333 hasher.update(scar_json.as_bytes());
334 let hash = format!("{:x}", Sha256::digest(hasher.clone().finalize()));
335 event_hashes.push(hash);
336 }
337
338 let merkle_root = format!("{:x}", hasher.finalize());
339
340 CausalChain {
341 merkle_root,
342 event_hashes,
343 total_events: scars.len(),
344 }
345 }
346
347 fn get_signing_key() -> Result<Vec<u8>, GraveyardError> {
349 let keys_dir = PathBuf::from(".lineage/keys");
350 fs::create_dir_all(&keys_dir)
351 .map_err(|e| GraveyardError::DirectoryError(format!("Failed to create keys directory: {}", e)))?;
352
353 let key_file = keys_dir.join("tombstone.key");
354
355 let key = if key_file.exists() {
356 fs::read(&key_file)
357 .map_err(|e| GraveyardError::IoError(format!("Failed to read signing key: {}", e)))?
358 } else {
359 use sha2::Sha256;
361 let mut hasher = Sha256::new();
362 let timestamp = std::time::SystemTime::now()
363 .duration_since(std::time::UNIX_EPOCH)
364 .map(|d| d.as_nanos().to_le_bytes().to_vec())
365 .unwrap_or_default();
366 hasher.update(×tamp);
367 let key_vec = hasher.finalize().to_vec();
368
369 #[cfg(target_os = "windows")]
371 {
372 fs::write(&key_file, &key_vec)
373 .map_err(|e| GraveyardError::IoError(format!("Failed to write signing key: {}", e)))?;
374 }
375 #[cfg(target_os = "linux")]
376 {
377 fs::write(&key_file, &key_vec)
378 .map_err(|e| GraveyardError::IoError(format!("Failed to write signing key: {}", e)))?;
379 use std::os::unix::fs::PermissionsExt;
380 let perms = std::fs::Permissions::from_mode(0o600); fs::set_permissions(&key_file, perms)
382 .map_err(|e| GraveyardError::IoError(format!("Failed to set key permissions: {}", e)))?;
383 }
384 #[cfg(not(any(target_os = "windows", target_os = "linux")))]
385 {
386 fs::write(&key_file, &key_vec)
387 .map_err(|e| GraveyardError::IoError(format!("Failed to write signing key: {}", e)))?;
388 }
389
390 key_vec
391 };
392
393 Ok(key)
394 }
395
396 fn calculate_signature(tombstone: &Tombstone) -> String {
398 let mut temp = tombstone.clone();
400 temp.signature = String::new();
401
402 let data_to_sign = format!(
404 "{}|{}|{}|{}|{}|{}",
405 temp.identity.id,
406 temp.metabolism.tasks_completed,
407 temp.metabolism.efficiency_ratio,
408 temp.pathology.scar_count,
409 temp.causal_chain.merkle_root,
410 temp.burial_timestamp
411 );
412
413 if let Ok(key) = Self::get_signing_key() {
415 let mut mac = HmacSha256::new_from_slice(&key)
416 .unwrap_or_else(|_| HmacSha256::new_from_slice(&[0u8; 32]).unwrap());
417 mac.update(data_to_sign.as_bytes());
418 hex::encode(mac.finalize().into_bytes())
419 } else {
420 format!("{:x}", Sha256::digest(data_to_sign.as_bytes()))
422 }
423 }
424
425 pub fn verify_signature(&self) -> Result<(), GraveyardError> {
427 let expected_signature = Self::calculate_signature(self);
428
429 if expected_signature != self.signature {
430 return Err(GraveyardError::TamperingDetected {
431 id: self.identity.id.clone(),
432 });
433 }
434
435 Ok(())
436 }
437
438 pub fn verify(&self) -> Result<(), GraveyardError> {
440 let mut hasher = Sha256::new();
442
443 for scar in &self.pathology.scars {
444 let scar_json = serde_json::to_string(scar)
445 .map_err(|e| GraveyardError::SerializationError(e.to_string()))?;
446 hasher.update(scar_json.as_bytes());
447 }
448
449 let calculated_root = format!("{:x}", hasher.finalize());
450
451 if calculated_root != self.causal_chain.merkle_root {
452 return Err(GraveyardError::TamperingDetected {
453 id: self.identity.id.clone(),
454 });
455 }
456
457 self.verify_signature()?;
459
460 Ok(())
461 }
462
463 pub fn legacy_score(&self) -> f64 {
465 let base_score = if self.pathology.scar_count > 0 {
466 self.metabolism.tasks_completed as f64 / self.pathology.scar_count as f64
467 } else {
468 self.metabolism.tasks_completed as f64 + 1.0
469 };
470
471 base_score * self.metabolism.efficiency_ratio
472 }
473
474 pub fn lifespan_seconds(&self) -> i64 {
476 (self.pathology.death_timestamp - self.identity.creation_time).num_seconds()
477 }
478}
479
480pub struct Graveyard;
482
483impl Graveyard {
484 pub fn initialize() -> Result<(), GraveyardError> {
486 let graveyard_path = Graveyard::path();
487
488 fs::create_dir_all(&graveyard_path)
489 .map_err(|e| GraveyardError::DirectoryError(e.to_string()))?;
490
491 let registry = GraveyardRegistry::initialize(&graveyard_path)?;
492
493 let mut global_registry = GRAVEYARD_REGISTRY
494 .lock()
495 .expect("Graveyard registry poisoned");
496 *global_registry = Some(registry);
497
498 Ok(())
499 }
500
501 pub fn path() -> PathBuf {
503 PathBuf::from(".lineage/graveyard")
504 }
505
506 pub fn bury(tombstone: &Tombstone) -> Result<(), GraveyardError> {
508 let graveyard_path = Graveyard::path();
509 let tomb_filename = format!("{}.tomb", tombstone.identity.id);
510 let final_path = graveyard_path.join(&tomb_filename);
511
512 if final_path.exists() {
514 return Err(GraveyardError::TombstoneExists {
515 id: tombstone.identity.id.clone(),
516 });
517 }
518
519 let temp_filename = format!("{}.tmp", tombstone.identity.id);
521 let temp_path = graveyard_path.join(&temp_filename);
522
523 let tombstone_json = serde_json::to_string_pretty(tombstone)
525 .map_err(|e| GraveyardError::SerializationError(e.to_string()))?;
526
527 fs::write(&temp_path, tombstone_json)
529 .map_err(|e| GraveyardError::IoError(e.to_string()))?;
530
531 fs::rename(&temp_path, &final_path)
533 .map_err(|e| GraveyardError::IoError(e.to_string()))?;
534
535 Graveyard::make_readonly(&final_path)?;
537
538 if let Ok(mut global_registry) = GRAVEYARD_REGISTRY.lock() {
540 if let Some(ref mut registry) = *global_registry {
541 registry.bury(tombstone.identity.id.clone(), final_path);
542 }
543 }
544
545 Ok(())
546 }
547
548 #[cfg(target_os = "windows")]
550 fn make_readonly(path: &Path) -> Result<(), GraveyardError> {
551 let mut perms = fs::metadata(path)
552 .map_err(|e| GraveyardError::IoError(e.to_string()))?
553 .permissions();
554 perms.set_readonly(true);
555 fs::set_permissions(path, perms)
556 .map_err(|e| GraveyardError::IoError(e.to_string()))
557 }
558
559 #[cfg(target_os = "linux")]
561 fn make_readonly(path: &Path) -> Result<(), GraveyardError> {
562 use std::fs;
563 use std::os::unix::fs::PermissionsExt;
564
565 let perms = fs::Permissions::from_mode(0o444); fs::set_permissions(path, perms)
567 .map_err(|e| GraveyardError::IoError(e.to_string()))
568 }
569
570 #[cfg(not(any(target_os = "windows", target_os = "linux")))]
572 fn make_readonly(path: &Path) -> Result<(), GraveyardError> {
573 let mut perms = fs::metadata(path)
574 .map_err(|e| GraveyardError::IoError(e.to_string()))?
575 .permissions();
576 perms.set_readonly(true);
577 fs::set_permissions(path, perms)
578 .map_err(|e| GraveyardError::IoError(e.to_string()))
579 }
580
581 pub fn load(id: &str) -> Result<Tombstone, GraveyardError> {
583 let graveyard_path = Graveyard::path();
584 let tomb_path = graveyard_path.join(format!("{}.tomb", id));
585
586 if !tomb_path.exists() {
587 return Err(GraveyardError::NotFound { id: id.to_string() });
588 }
589
590 let content =
591 fs::read_to_string(&tomb_path).map_err(|e| GraveyardError::IoError(e.to_string()))?;
592
593 let tombstone: Tombstone = serde_json::from_str(&content)
594 .map_err(|e| GraveyardError::SerializationError(e.to_string()))?;
595
596 Ok(tombstone)
597 }
598
599 pub fn is_dead(id: &str) -> bool {
601 if let Ok(global_registry) = GRAVEYARD_REGISTRY.lock() {
602 if let Some(ref registry) = *global_registry {
603 return registry.is_dead(id);
604 }
605 }
606 false
607 }
608
609 pub fn list_all() -> Vec<String> {
611 if let Ok(global_registry) = GRAVEYARD_REGISTRY.lock() {
612 if let Some(ref registry) = *global_registry {
613 return registry.list_all();
614 }
615 }
616 Vec::new()
617 }
618
619 pub fn load_all() -> Result<Vec<Tombstone>, GraveyardError> {
621 let mut tombstones = Vec::new();
622
623 for id in Graveyard::list_all() {
624 if let Ok(tombstone) = Graveyard::load(&id) {
625 tombstones.push(tombstone);
626 }
627 }
628
629 Ok(tombstones)
630 }
631
632 pub fn statistics() -> Result<GraveyardStats, GraveyardError> {
634 let tombstones = Graveyard::load_all()?;
635 let mut total_lifespan = 0i64;
636 let mut total_efficiency = 0.0f64;
637 let mut scar_counts = Vec::new();
638 let mut legacy_scores = Vec::new();
639
640 for tombstone in &tombstones {
641 total_lifespan += tombstone.lifespan_seconds();
642 total_efficiency += tombstone.metabolism.efficiency_ratio;
643 scar_counts.push(tombstone.pathology.scar_count);
644 legacy_scores.push(tombstone.legacy_score());
645 }
646
647 let count = tombstones.len() as f64;
648 let avg_lifespan = if tombstones.is_empty() {
649 0
650 } else {
651 total_lifespan / tombstones.len() as i64
652 };
653 let avg_efficiency = total_efficiency / count.max(1.0);
654
655 Ok(GraveyardStats {
656 total_agents: tombstones.len(),
657 average_lifespan_seconds: avg_lifespan,
658 average_efficiency: avg_efficiency,
659 total_scars: scar_counts.iter().sum(),
660 most_common_scar_count: scar_counts.iter().max().cloned().unwrap_or(0),
661 highest_legacy_score: legacy_scores
662 .iter()
663 .cloned()
664 .fold(f64::NEG_INFINITY, f64::max),
665 })
666 }
667}
668
669#[derive(Debug, Clone)]
671pub struct GraveyardStats {
672 pub total_agents: usize,
673 pub average_lifespan_seconds: i64,
674 pub average_efficiency: f64,
675 pub total_scars: usize,
676 pub most_common_scar_count: usize,
677 pub highest_legacy_score: f64,
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683
684 #[test]
685 fn test_legacy_score_calculation() {
686 let metabolic = MetabolicRecord {
687 final_energy: 100,
688 peak_energy: 1000,
689 initial_energy: 1000,
690 efficiency_ratio: 0.5,
691 tasks_completed: 10,
692 };
693
694 assert_eq!(metabolic.efficiency_ratio, 0.5);
695 }
696}