Skip to main content

batuta/recipes/
sovereign_deployment.rs

1//! Sovereign deployment recipe implementation.
2
3use crate::experiment::{ExperimentError, SovereignArtifact, SovereignDistribution};
4use crate::recipes::RecipeResult;
5use serde::{Deserialize, Serialize};
6
7/// Sovereign deployment recipe configuration
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SovereignDeploymentConfig {
10    /// Distribution name
11    pub name: String,
12    /// Version
13    pub version: String,
14    /// Target platforms
15    pub platforms: Vec<String>,
16    /// Require signatures
17    pub require_signatures: bool,
18    /// Nix flake support
19    pub enable_nix_flake: bool,
20    /// Offline registry path
21    pub offline_registry_path: Option<String>,
22}
23
24impl Default for SovereignDeploymentConfig {
25    fn default() -> Self {
26        Self {
27            name: "sovereign-model".to_string(),
28            version: "0.1.0".to_string(),
29            platforms: vec!["linux-x86_64".to_string()],
30            require_signatures: true,
31            enable_nix_flake: true,
32            offline_registry_path: None,
33        }
34    }
35}
36
37/// Sovereign deployment recipe
38#[derive(Debug)]
39pub struct SovereignDeploymentRecipe {
40    config: SovereignDeploymentConfig,
41    distribution: SovereignDistribution,
42}
43
44impl SovereignDeploymentRecipe {
45    /// Create a new sovereign deployment recipe
46    pub fn new(config: SovereignDeploymentConfig) -> Self {
47        let mut distribution = SovereignDistribution::new(&config.name, &config.version);
48        for platform in &config.platforms {
49            distribution.add_platform(platform);
50        }
51        Self { config, distribution }
52    }
53
54    /// Shared implementation for adding artifacts of any type
55    fn add_artifact_impl(
56        &mut self,
57        name: impl Into<String>,
58        sha256: impl Into<String>,
59        size_bytes: u64,
60        artifact_type: crate::experiment::ArtifactType,
61    ) {
62        self.distribution.add_artifact(SovereignArtifact {
63            name: name.into(),
64            artifact_type,
65            sha256: sha256.into(),
66            size_bytes,
67            source_url: None,
68        });
69    }
70
71    /// Add a model artifact
72    pub fn add_model(
73        &mut self,
74        name: impl Into<String>,
75        sha256: impl Into<String>,
76        size_bytes: u64,
77    ) {
78        self.add_artifact_impl(name, sha256, size_bytes, crate::experiment::ArtifactType::Model);
79    }
80
81    /// Add a binary artifact
82    pub fn add_binary(
83        &mut self,
84        name: impl Into<String>,
85        sha256: impl Into<String>,
86        size_bytes: u64,
87    ) {
88        self.add_artifact_impl(name, sha256, size_bytes, crate::experiment::ArtifactType::Binary);
89    }
90
91    /// Add a dataset artifact
92    pub fn add_dataset(
93        &mut self,
94        name: impl Into<String>,
95        sha256: impl Into<String>,
96        size_bytes: u64,
97    ) {
98        self.add_artifact_impl(name, sha256, size_bytes, crate::experiment::ArtifactType::Dataset);
99    }
100
101    /// Sign an artifact (placeholder - would use real crypto in production)
102    pub fn sign_artifact(&mut self, artifact_name: impl Into<String>, key_id: impl Into<String>) {
103        let name = artifact_name.into();
104        // In production, this would actually compute the signature
105        let signature = format!("sig_placeholder_{}", &name);
106        self.distribution.signatures.push(crate::experiment::ArtifactSignature {
107            artifact_name: name,
108            algorithm: crate::experiment::SignatureAlgorithm::Ed25519,
109            signature,
110            key_id: key_id.into(),
111        });
112    }
113
114    /// Validate and build the distribution
115    pub fn build(&self) -> Result<RecipeResult, ExperimentError> {
116        // Validate signatures if required
117        if self.config.require_signatures {
118            self.distribution.validate_signatures()?;
119        }
120
121        let mut result = RecipeResult::success("sovereign-deployment");
122        result = result.with_metric("artifact_count", self.distribution.artifacts.len() as f64);
123        result =
124            result.with_metric("total_size_bytes", self.distribution.total_size_bytes() as f64);
125        result = result.with_metric("platform_count", self.distribution.platforms.len() as f64);
126
127        // Add artifacts to result
128        for artifact in &self.distribution.artifacts {
129            result = result.with_artifact(&artifact.name);
130        }
131
132        Ok(result)
133    }
134
135    /// Get the distribution
136    pub fn distribution(&self) -> &SovereignDistribution {
137        &self.distribution
138    }
139
140    /// Export distribution manifest as JSON
141    pub fn export_manifest(&self) -> Result<String, ExperimentError> {
142        serde_json::to_string_pretty(&self.distribution)
143            .map_err(|e| ExperimentError::StorageError(e.to_string()))
144    }
145}