Skip to main content

entrenar/sovereign/distribution/
sovereign.rs

1//! Sovereign distribution manifest
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7use super::{ComponentManifest, DistributionFormat, DistributionTier};
8
9/// Sovereign distribution manifest
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct SovereignDistribution {
12    /// Distribution name
13    pub name: String,
14    /// Distribution version
15    pub version: String,
16    /// Distribution tier
17    pub tier: DistributionTier,
18    /// Distribution format
19    pub format: DistributionFormat,
20    /// Component manifests
21    pub components: Vec<ComponentManifest>,
22    /// SHA-256 checksum of bundle
23    pub checksum: String,
24    /// Creation timestamp
25    pub created_at: DateTime<Utc>,
26}
27
28impl SovereignDistribution {
29    /// Create a new distribution manifest
30    pub fn new(
31        name: impl Into<String>,
32        version: impl Into<String>,
33        tier: DistributionTier,
34        format: DistributionFormat,
35    ) -> Self {
36        Self {
37            name: name.into(),
38            version: version.into(),
39            tier,
40            format,
41            components: Vec::new(),
42            checksum: String::new(),
43            created_at: Utc::now(),
44        }
45    }
46
47    /// Create Core tier distribution (~50MB)
48    pub fn core() -> Self {
49        let version = env!("CARGO_PKG_VERSION");
50        let mut dist = Self::new(
51            "entrenar-sovereign-core",
52            version,
53            DistributionTier::Core,
54            DistributionFormat::Tarball,
55        );
56
57        dist.components = vec![
58            ComponentManifest::entrenar_core(version),
59            ComponentManifest::trueno("0.2"),
60            ComponentManifest::aprender("0.1"),
61        ];
62
63        dist
64    }
65
66    /// Create Standard tier distribution (~200MB)
67    pub fn standard() -> Self {
68        let version = env!("CARGO_PKG_VERSION");
69        let mut dist = Self::new(
70            "entrenar-sovereign-standard",
71            version,
72            DistributionTier::Standard,
73            DistributionFormat::Tarball,
74        );
75
76        dist.components = vec![
77            ComponentManifest::entrenar_core(version),
78            ComponentManifest::trueno("0.2"),
79            ComponentManifest::aprender("0.1"),
80            ComponentManifest::renacer("0.1"),
81            ComponentManifest::new("trueno-db", "0.1", "trueno-db"),
82            ComponentManifest::new("ruchy", "0.1", "ruchy"),
83        ];
84
85        dist
86    }
87
88    /// Create Full tier distribution (~500MB)
89    pub fn full() -> Self {
90        let version = env!("CARGO_PKG_VERSION");
91        let mut dist = Self::new(
92            "entrenar-sovereign-full",
93            version,
94            DistributionTier::Full,
95            DistributionFormat::Tarball,
96        );
97
98        dist.components = vec![
99            ComponentManifest::entrenar_core(version),
100            ComponentManifest::trueno("0.2").with_features(["gpu", "cuda"]),
101            ComponentManifest::aprender("0.1"),
102            ComponentManifest::renacer("0.1"),
103            ComponentManifest::new("trueno-db", "0.1", "trueno-db"),
104            ComponentManifest::new("ruchy", "0.1", "ruchy"),
105            ComponentManifest::new("entrenar-gpu", version, "entrenar-gpu")
106                .with_features(["cuda", "rocm"]),
107            ComponentManifest::new("entrenar-bench", version, "entrenar-bench"),
108            ComponentManifest::new("entrenar-inspect", version, "entrenar-inspect"),
109            ComponentManifest::new("entrenar-lora", version, "entrenar-lora"),
110            ComponentManifest::new("entrenar-shell", version, "entrenar-shell"),
111        ];
112
113        dist
114    }
115
116    /// Set the distribution format
117    pub fn with_format(mut self, format: DistributionFormat) -> Self {
118        self.format = format;
119        self
120    }
121
122    /// Set the checksum
123    pub fn with_checksum(mut self, checksum: impl Into<String>) -> Self {
124        self.checksum = checksum.into();
125        self
126    }
127
128    /// Calculate and set checksum from data
129    pub fn compute_checksum(&mut self, data: &[u8]) {
130        let mut hasher = Sha256::new();
131        hasher.update(data);
132        self.checksum = format!("{:x}", hasher.finalize());
133    }
134
135    /// Verify checksum against data
136    pub fn verify_checksum(&self, data: &[u8]) -> bool {
137        if self.checksum.is_empty() {
138            return false;
139        }
140
141        let mut hasher = Sha256::new();
142        hasher.update(data);
143        let computed = format!("{:x}", hasher.finalize());
144
145        computed == self.checksum
146    }
147
148    /// Serialize to JSON manifest
149    pub fn to_manifest_json(&self) -> String {
150        serde_json::to_string_pretty(self).unwrap_or_else(|_err| "{}".to_string())
151    }
152
153    /// Parse from JSON manifest
154    pub fn from_manifest_json(json: &str) -> Result<Self, serde_json::Error> {
155        serde_json::from_str(json)
156    }
157
158    /// Get the suggested filename for this distribution
159    pub fn suggested_filename(&self) -> String {
160        format!("{}-{}.{}", self.name, self.version, self.format.extension())
161    }
162
163    /// Get total component count
164    pub fn component_count(&self) -> usize {
165        self.components.len()
166    }
167
168    /// Check if distribution includes a specific component
169    pub fn has_component(&self, name: &str) -> bool {
170        self.components.iter().any(|c| c.name == name)
171    }
172}
173
174impl Default for SovereignDistribution {
175    fn default() -> Self {
176        Self::core()
177    }
178}