Skip to main content

agentic_codebase/grounding/
truth.rs

1//! Truth Maintenance — Invention 6.
2//!
3//! Track which claims have been invalidated by recent changes.
4//! Codebase changes; AI's knowledge becomes stale.
5
6use serde::{Deserialize, Serialize};
7
8use crate::graph::CodeGraph;
9
10use super::citation::{CitationEngine, GroundedClaim};
11
12// ── Types ────────────────────────────────────────────────────────────────────
13
14/// Record of a previously-true claim.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct MaintainedTruth {
17    /// The claim.
18    pub claim: GroundedClaim,
19    /// When it was established (unix timestamp).
20    pub established_at: u64,
21    /// Current status.
22    pub status: TruthStatus,
23    /// If invalidated, what changed.
24    pub invalidation: Option<TruthInvalidation>,
25}
26
27/// Current status of a maintained truth.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub enum TruthStatus {
30    /// Still true.
31    Valid,
32    /// Changed, needs review.
33    Stale,
34    /// Definitely no longer true.
35    Invalidated,
36    /// Code was deleted.
37    Deleted,
38}
39
40/// Details about how a truth was invalidated.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TruthInvalidation {
43    /// When it was invalidated (unix timestamp).
44    pub invalidated_at: u64,
45    /// What change invalidated it.
46    pub change: String,
47    /// What's true now.
48    pub new_truth: Option<String>,
49}
50
51// ── TruthMaintainer ──────────────────────────────────────────────────────────
52
53/// Maintains a set of truths and checks them against the current graph.
54pub struct TruthMaintainer<'g> {
55    citation_engine: CitationEngine<'g>,
56    truths: Vec<MaintainedTruth>,
57}
58
59impl<'g> TruthMaintainer<'g> {
60    /// Create a new truth maintainer.
61    pub fn new(graph: &'g CodeGraph) -> Self {
62        Self {
63            citation_engine: CitationEngine::new(graph),
64            truths: Vec::new(),
65        }
66    }
67
68    /// Register a truth that should be maintained.
69    pub fn register_truth(&mut self, claim: &str) -> MaintainedTruth {
70        let grounded = self.citation_engine.ground_claim(claim);
71        let now = std::time::SystemTime::now()
72            .duration_since(std::time::UNIX_EPOCH)
73            .unwrap_or_default()
74            .as_secs();
75
76        let truth = MaintainedTruth {
77            claim: grounded,
78            established_at: now,
79            status: TruthStatus::Valid,
80            invalidation: None,
81        };
82
83        self.truths.push(truth.clone());
84        truth
85    }
86
87    /// Check if a historical claim is still true against the current graph.
88    pub fn check_truth(&self, claim: &str) -> TruthStatus {
89        let grounded = self.citation_engine.ground_claim(claim);
90        if grounded.fully_grounded {
91            TruthStatus::Valid
92        } else if grounded.citations.is_empty() {
93            TruthStatus::Deleted
94        } else {
95            TruthStatus::Stale
96        }
97    }
98
99    /// Refresh all maintained truths against the current graph.
100    pub fn refresh_all(&mut self) -> Vec<MaintainedTruth> {
101        let now = std::time::SystemTime::now()
102            .duration_since(std::time::UNIX_EPOCH)
103            .unwrap_or_default()
104            .as_secs();
105
106        let mut results = Vec::new();
107
108        for truth in &mut self.truths {
109            let new_status = {
110                let grounded = self.citation_engine.ground_claim(&truth.claim.claim);
111                if grounded.fully_grounded {
112                    TruthStatus::Valid
113                } else if grounded.citations.is_empty() {
114                    TruthStatus::Deleted
115                } else {
116                    TruthStatus::Stale
117                }
118            };
119
120            if new_status != TruthStatus::Valid && truth.status == TruthStatus::Valid {
121                truth.status = new_status;
122                truth.invalidation = Some(TruthInvalidation {
123                    invalidated_at: now,
124                    change: "Graph changed since truth was established".to_string(),
125                    new_truth: None,
126                });
127            }
128
129            results.push(truth.clone());
130        }
131
132        results
133    }
134
135    /// Get a diff of what changed between two graph versions.
136    /// Compares the current truth set status against the original.
137    pub fn truth_diff(&self) -> Vec<MaintainedTruth> {
138        self.truths
139            .iter()
140            .filter(|t| t.status != TruthStatus::Valid)
141            .cloned()
142            .collect()
143    }
144
145    /// Get all maintained truths.
146    pub fn truths(&self) -> &[MaintainedTruth] {
147        &self.truths
148    }
149}
150
151// ── Tests ────────────────────────────────────────────────────────────────────
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::types::{CodeUnit, CodeUnitType, Language, Span};
157    use std::path::PathBuf;
158
159    fn test_graph() -> CodeGraph {
160        let mut graph = CodeGraph::with_default_dimension();
161        graph.add_unit(CodeUnit::new(
162            CodeUnitType::Function,
163            Language::Python,
164            "process_payment".to_string(),
165            "payments.stripe.process_payment".to_string(),
166            PathBuf::from("src/payments/stripe.py"),
167            Span::new(10, 0, 30, 0),
168        ));
169        graph
170    }
171
172    #[test]
173    fn register_truth_is_valid() {
174        let graph = test_graph();
175        let mut maintainer = TruthMaintainer::new(&graph);
176        let truth = maintainer.register_truth("process_payment exists");
177        assert_eq!(truth.status, TruthStatus::Valid);
178    }
179
180    #[test]
181    fn check_truth_valid() {
182        let graph = test_graph();
183        let maintainer = TruthMaintainer::new(&graph);
184        let status = maintainer.check_truth("process_payment exists in codebase");
185        assert_eq!(status, TruthStatus::Valid);
186    }
187
188    #[test]
189    fn check_truth_deleted() {
190        let graph = test_graph();
191        let maintainer = TruthMaintainer::new(&graph);
192        let status = maintainer.check_truth("nonexistent_func does something");
193        assert_eq!(status, TruthStatus::Deleted);
194    }
195
196    #[test]
197    fn truth_diff_empty_when_valid() {
198        let graph = test_graph();
199        let mut maintainer = TruthMaintainer::new(&graph);
200        maintainer.register_truth("process_payment exists");
201        let diff = maintainer.truth_diff();
202        assert!(diff.is_empty());
203    }
204}