Skip to main content

lineage/
graveyard.rs

1//! # Graveyard System - The Eternal Archive
2//!
3//! Persistent storage for deceased agents.
4//!
5//! ## What This Enforces
6//! - Cryptographic sealing of agent tombstones
7//! - Immutable historical records of all dead agents
8//! - Prevention of identity resurrection ("Lazarus Prevention")
9//! - Fast O(1) lookups via in-memory registry
10//! - Tamper-detection via causal chain hashing
11//! - Signature verification to detect fraudulent edits
12//! - Genealogical tracking via parent agent IDs
13//!
14//! ## What This Forbids
15//! - Overwriting existing tombstones
16//! - Reusing a dead agent's identity
17//! - Operating on dead agents
18//! - Modifying sealed records
19//! - Tampering with Legacy Scores or metadata
20//! - Creating agents without proper genealogy
21//!
22//! ## Storage Format
23//! Each tombstone is stored as JSON in `.lineage/graveyard/<ID>.tomb`
24//! Each signature is stored alongside in `.lineage/graveyard/<ID>.sig`
25//! Files are marked read-only at OS level to prevent accidental mutation.
26
27use 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
39/// Global registry of dead agents - prevents resurrection
40static GRAVEYARD_REGISTRY: Mutex<Option<GraveyardRegistry>> = Mutex::new(None);
41
42/// In-memory index of all buried agents (lightning-fast Lazarus checks)
43#[derive(Debug, Clone)]
44pub struct GraveyardRegistry {
45    /// Map of ID -> Tombstone location
46    dead_ids: HashMap<String, PathBuf>,
47}
48
49impl GraveyardRegistry {
50    /// Initialize the registry from disk
51    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    /// Check if an identity has already died (Lazarus prevention)
73    pub fn is_dead(&self, id: &str) -> bool {
74        self.dead_ids.contains_key(id)
75    }
76
77    /// Register a newly buried agent
78    pub fn bury(&mut self, id: String, path: PathBuf) {
79        self.dead_ids.insert(id, path);
80    }
81
82    /// Get all dead agents
83    pub fn list_all(&self) -> Vec<String> {
84        self.dead_ids.keys().cloned().collect()
85    }
86
87    /// Get path to a tombstone
88    pub fn get_tombstone_path(&self, id: &str) -> Option<PathBuf> {
89        self.dead_ids.get(id).cloned()
90    }
91}
92
93/// Errors that can occur in the graveyard system
94#[derive(Debug, Clone)]
95pub enum GraveyardError {
96    /// IO operation failed
97    IoError(String),
98    /// Tombstone already exists (no overwrites allowed)
99    TombstoneExists { id: String },
100    /// ID not found in graveyard
101    NotFound { id: String },
102    /// Serialization/deserialization failed
103    SerializationError(String),
104    /// Hash verification failed (tampering detected)
105    TamperingDetected { id: String },
106    /// Directory initialization failed
107    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/// Identity block in tombstone
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct IdentityBlock {
132    /// Agent's unique identifier
133    pub id: String,
134    /// Creation timestamp
135    pub creation_time: DateTime<Utc>,
136    /// Cryptographic hash of identity proof
137    pub identity_hash: String,
138}
139
140/// Metabolic record in tombstone
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct MetabolicRecord {
143    /// Final energy level when agent died
144    pub final_energy: u64,
145    /// Peak energy ever achieved
146    pub peak_energy: u64,
147    /// Initial energy at birth
148    pub initial_energy: u64,
149    /// Efficiency rating: tasks_completed / energy_burned
150    pub efficiency_ratio: f64,
151    /// Total tasks completed
152    pub tasks_completed: u32,
153}
154
155impl MetabolicRecord {
156    /// Calculate efficiency ratio
157    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/// Pathology report in tombstone
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct PathologyReport {
169    /// List of scars inflicted during lifetime
170    pub scars: Vec<ScarRecord>,
171    /// Total number of scars
172    pub scar_count: usize,
173    /// Cause of death (the final scar)
174    pub cause_of_death: String,
175    /// Time of death
176    pub death_timestamp: DateTime<Utc>,
177}
178
179/// Individual scar record with metadata
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ScarRecord {
182    /// When scar was inflicted
183    pub timestamp: DateTime<Utc>,
184    /// Severity level
185    pub severity: String,
186    /// Description of the injury
187    pub description: String,
188    /// Context/stack trace
189    pub context: Option<String>,
190}
191
192/// Causal chain - cryptographic proof of unaltered history
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct CausalChain {
195    /// Hash of the entire event sequence
196    pub merkle_root: String,
197    /// Ordered list of event hashes
198    pub event_hashes: Vec<String>,
199    /// Total events in chain
200    pub total_events: usize,
201}
202
203/// Genealogical record for descendancy tracking
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ParentageRecord {
206    /// ID of parent agent (if spawned from another agent)
207    pub parent_id: Option<String>,
208    /// Capacity inherited from parent
209    pub inherited_capacity: Option<u64>,
210    /// Knowledge transferred from parent (description)
211    pub inherited_knowledge: Option<String>,
212    /// Generation number (0 = origin, 1 = spawned from origin, etc.)
213    pub generation: u32,
214}
215
216/// A complete tombstone record for a deceased agent
217///
218/// Contains all information needed to:
219/// - Reconstruct an agent's lifetime
220/// - Verify no tampering has occurred
221/// - Prevent resurrection via Lazarus check
222/// - Query historical data
223/// - Track genealogical relationships
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct Tombstone {
226    /// Identity information block
227    pub identity: IdentityBlock,
228    /// Metabolic records from lifetime
229    pub metabolism: MetabolicRecord,
230    /// Pathology report with scars and cause of death
231    pub pathology: PathologyReport,
232    /// Causal chain for tamper detection
233    pub causal_chain: CausalChain,
234    /// Genealogical information (parentage and generation)
235    pub parentage: ParentageRecord,
236    /// Cryptographic signature (HMAC-SHA256) for fraud detection
237    pub signature: String,
238    /// Signature timestamp of burial
239    pub burial_timestamp: DateTime<Utc>,
240    /// Version of graveyard schema
241    pub schema_version: u32,
242}
243
244impl Tombstone {
245    /// Create a new tombstone from final agent state
246    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    /// Create a new tombstone with genealogical information
265    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(), // Will be calculated next
316            burial_timestamp: Utc::now(),
317            schema_version: 1,
318        };
319        
320        // Generate signature
321        tombstone.signature = Self::calculate_signature(&tombstone);
322        tombstone
323    }
324
325    /// Create causal chain from scar sequence
326    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    /// Get or create the cryptographic key for signing tombstones
348    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            // Generate a new key from system entropy
360            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(&timestamp);
367            let key_vec = hasher.finalize().to_vec();
368            
369            // Write key securely (readable only by user)
370            #[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); // rw-------
381                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    /// Calculate HMAC-SHA256 signature for the tombstone
397    fn calculate_signature(tombstone: &Tombstone) -> String {
398        // Create a temporary copy without signature for hashing
399        let mut temp = tombstone.clone();
400        temp.signature = String::new();
401
402        // Serialize the core data
403        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        // Sign with key if available, otherwise use SHA256 hash
414        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            // Fallback to simple SHA256 hash
421            format!("{:x}", Sha256::digest(data_to_sign.as_bytes()))
422        }
423    }
424
425    /// Verify the cryptographic signature of this tombstone
426    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    /// Verify the integrity of this tombstone (no tampering)
439    pub fn verify(&self) -> Result<(), GraveyardError> {
440        // First, verify the causal chain integrity
441        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        // Second, verify the cryptographic signature
458        self.verify_signature()?;
459
460        Ok(())
461    }
462
463    /// Calculate legacy score (Success-to-Scar ratio with efficiency bonus)
464    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    /// Get lifespan in seconds
475    pub fn lifespan_seconds(&self) -> i64 {
476        (self.pathology.death_timestamp - self.identity.creation_time).num_seconds()
477    }
478}
479
480/// The Graveyard manager - handles burial, loading, and queries
481pub struct Graveyard;
482
483impl Graveyard {
484    /// Initialize the graveyard system
485    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    /// Get the graveyard path
502    pub fn path() -> PathBuf {
503        PathBuf::from(".lineage/graveyard")
504    }
505
506    /// Bury an agent (atomic write with no overwrites)
507    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        // Check if already buried (no overwrites)
513        if final_path.exists() {
514            return Err(GraveyardError::TombstoneExists {
515                id: tombstone.identity.id.clone(),
516            });
517        }
518
519        // Atomic write: write to temp file first, then rename
520        let temp_filename = format!("{}.tmp", tombstone.identity.id);
521        let temp_path = graveyard_path.join(&temp_filename);
522
523        // Serialize tombstone
524        let tombstone_json = serde_json::to_string_pretty(tombstone)
525            .map_err(|e| GraveyardError::SerializationError(e.to_string()))?;
526
527        // Write to temp file
528        fs::write(&temp_path, tombstone_json)
529            .map_err(|e| GraveyardError::IoError(e.to_string()))?;
530
531        // Atomic rename
532        fs::rename(&temp_path, &final_path)
533            .map_err(|e| GraveyardError::IoError(e.to_string()))?;
534
535        // Mark as read-only (OS level)
536        Graveyard::make_readonly(&final_path)?;
537
538        // Register in global registry
539        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    /// Mark a file as read-only at OS level
549    #[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    /// Mark a file as read-only at OS level (Unix)
560    #[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); // r--r--r--
566        fs::set_permissions(path, perms)
567            .map_err(|e| GraveyardError::IoError(e.to_string()))
568    }
569
570    /// Mark a file as read-only at OS level (fallback)
571    #[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    /// Load a tombstone from disk
582    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    /// Check if an identity has already died (fast O(1) check)
600    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    /// List all dead agents
610    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    /// Load all tombstones (expensive operation)
620    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    /// Get summary statistics
633    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/// Statistics about the graveyard
670#[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}