Arranque del proyecto hammer (distro AI-nativa: laboratorio funcional en el sótano, terminal mutable clásica arriba, integración de IA programadora). - Workspace Rust (compila, tests verdes): hammer-core, hammer-build, hammer-cli (bin `hammer`), hammerd. - SDDs 00-10 + 6 ADRs en docs/ con toda la arquitectura. - Esqueletos navegables mapeados a las fases del roadmap; Fase 0/1 listas para implementar el sandbox real. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
3.1 KiB
Rust
122 lines
3.1 KiB
Rust
//! El manifiesto `.swm` (Software Mutación). Ver `docs/06-swm-format.md`.
|
|
//!
|
|
//! Es la unidad de intercambio: receta de transformación sobre fuente pública + ediciones de
|
|
//! config. NUNCA transporta binarios cocidos (salvo `FileDrop` con hash declarado).
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Swm {
|
|
pub swm_version: u32,
|
|
pub base: Base,
|
|
pub mutations: Vec<Mutation>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub signature: Option<Signature>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Base {
|
|
pub distro_version: String,
|
|
#[serde(default)]
|
|
pub pins: std::collections::BTreeMap<String, String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum Mutation {
|
|
SourcePatch {
|
|
repo: String,
|
|
commit: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
patch: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
patch_url: Option<String>,
|
|
build: SwmBuild,
|
|
target_bin: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
expected_hash: Option<String>,
|
|
},
|
|
ConfigEdit {
|
|
file: String,
|
|
inline_diff: String,
|
|
},
|
|
InitRule {
|
|
action: String,
|
|
service: String,
|
|
command: String,
|
|
},
|
|
FileDrop {
|
|
path: String,
|
|
/// hash BLAKE3 del contenido, declarado para verificación.
|
|
content_hash: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
content_url: Option<String>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SwmBuild {
|
|
#[serde(default = "default_compiler")]
|
|
pub compiler: String,
|
|
#[serde(default = "default_target")]
|
|
pub target: String,
|
|
#[serde(default = "default_link")]
|
|
pub link: String,
|
|
#[serde(default)]
|
|
pub flags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Signature {
|
|
pub by: String,
|
|
pub alg: String,
|
|
pub sig: String,
|
|
}
|
|
|
|
fn default_compiler() -> String {
|
|
"zig-cc".into()
|
|
}
|
|
fn default_target() -> String {
|
|
"x86_64-linux-musl".into()
|
|
}
|
|
fn default_link() -> String {
|
|
"static".into()
|
|
}
|
|
|
|
impl Swm {
|
|
pub fn from_yaml(s: &str) -> crate::Result<Swm> {
|
|
serde_yaml::from_str(s).map_err(|e| crate::Error::Serde(e.to_string()))
|
|
}
|
|
|
|
pub fn to_yaml(&self) -> crate::Result<String> {
|
|
serde_yaml::to_string(self).map_err(|e| crate::Error::Serde(e.to_string()))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn roundtrip_yaml() {
|
|
let yaml = r#"
|
|
swm_version: 1
|
|
base:
|
|
distro_version: "2026-06-06"
|
|
pins:
|
|
grep: "a1b2c3d"
|
|
mutations:
|
|
- type: config_edit
|
|
file: "/etc/network.conf"
|
|
inline_diff: |
|
|
- DHCP=yes
|
|
+ IP=192.168.1.100
|
|
"#;
|
|
let swm = Swm::from_yaml(yaml).expect("parse");
|
|
assert_eq!(swm.swm_version, 1);
|
|
assert_eq!(swm.mutations.len(), 1);
|
|
let back = swm.to_yaml().expect("serialize");
|
|
assert!(back.contains("config_edit"));
|
|
}
|
|
}
|