Skip to main content

sui_store/
convergence.rs

1//! Convergence store extensions.
2//!
3//! Extends the sui Store trait with convergence-aware queries:
4//! requisites, referrers, impact analysis, and generational tracking.
5//! The convergence store IS the Nix store — attestations are store paths.
6
7use sui_compat::store_path::StorePath;
8use crate::traits::StoreResult;
9
10/// A convergence-aware store path with generation tracking.
11#[derive(Debug, Clone)]
12pub struct GenerationalPath {
13    /// The store path.
14    pub path: StorePath,
15    /// Which generation this convergence attestation belongs to.
16    pub generation: u64,
17    /// Blake3 hash of the previous generation (append-only chain).
18    pub previous_hash: Option<String>,
19}
20
21/// Impact analysis result for a convergence point change.
22#[derive(Debug, Clone, Default)]
23pub struct ImpactReport {
24    /// Store paths that would need re-convergence.
25    pub affected_paths: Vec<StorePath>,
26    /// Number of convergence points affected.
27    pub affected_point_count: usize,
28    /// Estimated re-convergence cost (arbitrary units).
29    pub estimated_cost: f64,
30}
31
32/// Convergence-aware extensions to the sui Store.
33///
34/// These methods compose with the existing Store trait's
35/// query_references() and compute_closure() to provide
36/// convergence-specific queries.
37#[allow(async_fn_in_trait)]
38pub trait ConvergenceStore {
39    /// Forward closure: all convergence points this point depends on.
40    async fn convergence_requisites(&self, path: &StorePath) -> StoreResult<Vec<StorePath>>;
41
42    /// Reverse closure: all points that depend on this point.
43    async fn convergence_referrers(&self, path: &StorePath) -> StoreResult<Vec<StorePath>>;
44
45    /// Impact analysis: what must re-converge if this point changes?
46    async fn convergence_impact(&self, path: &StorePath) -> StoreResult<ImpactReport>;
47
48    /// Latest generation for a convergence point.
49    async fn convergence_generation(&self, path: &StorePath) -> StoreResult<u64>;
50
51    /// Full generation history for a convergence point.
52    async fn convergence_history(
53        &self,
54        path: &StorePath,
55    ) -> StoreResult<Vec<GenerationalPath>>;
56}
57
58/// Default implementation that wraps the standard Store operations.
59/// Real convergence-aware stores override these with optimized queries.
60pub struct DefaultConvergenceStore;
61
62impl ConvergenceStore for DefaultConvergenceStore {
63    async fn convergence_requisites(&self, _path: &StorePath) -> StoreResult<Vec<StorePath>> {
64        // Default: return empty — real implementation uses store's compute_closure
65        Ok(Vec::new())
66    }
67
68    async fn convergence_referrers(&self, _path: &StorePath) -> StoreResult<Vec<StorePath>> {
69        Ok(Vec::new())
70    }
71
72    async fn convergence_impact(&self, _path: &StorePath) -> StoreResult<ImpactReport> {
73        Ok(ImpactReport::default())
74    }
75
76    async fn convergence_generation(&self, _path: &StorePath) -> StoreResult<u64> {
77        Ok(0)
78    }
79
80    async fn convergence_history(
81        &self,
82        _path: &StorePath,
83    ) -> StoreResult<Vec<GenerationalPath>> {
84        Ok(Vec::new())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[tokio::test]
93    async fn test_default_convergence_store() {
94        let store = DefaultConvergenceStore;
95        let path = StorePath::from_basename("0000000000000000000000000000000a-test")
96            .unwrap();
97
98        let requisites = store.convergence_requisites(&path).await.unwrap();
99        assert!(requisites.is_empty());
100
101        let referrers = store.convergence_referrers(&path).await.unwrap();
102        assert!(referrers.is_empty());
103
104        let impact = store.convergence_impact(&path).await.unwrap();
105        assert_eq!(impact.affected_point_count, 0);
106
107        let generation = store.convergence_generation(&path).await.unwrap();
108        assert_eq!(generation, 0);
109
110        let history = store.convergence_history(&path).await.unwrap();
111        assert!(history.is_empty());
112    }
113
114    #[test]
115    fn test_generational_path() {
116        let path = StorePath::from_basename("0000000000000000000000000000000a-test")
117            .unwrap();
118        let gp = GenerationalPath {
119            path,
120            generation: 42,
121            previous_hash: Some("blake3:abc123".into()),
122        };
123        assert_eq!(gp.generation, 42);
124        assert!(gp.previous_hash.is_some());
125    }
126
127    #[test]
128    fn test_impact_report_default() {
129        let report = ImpactReport::default();
130        assert!(report.affected_paths.is_empty());
131        assert_eq!(report.affected_point_count, 0);
132        assert_eq!(report.estimated_cost, 0.0);
133    }
134}