gioser

tawasuyu/brahman

Rust 521TOML 202Markdown 57JSON 197505 Definiciones

refactor(arje): migra ente-card a re-export de brahman-card

ente-card pasa a ser un crate-shim que re-exporta los tipos de
brahman-card bajo sus nombres legacy:

- EntityCard ≡ brahman_card::Card (alias)
- Capability, Payload, SomaSpec, Supervision, etc. — pub use directo

Cambios concretos:

- crates/core/brahman-card/src/lib.rs: añade impl Default for Card.
  Permite usar `..Default::default()` en struct-literals para los
  campos aditivos (permissions, lifecycle, priority, flow, extensions).
- crates/core/ente-card/src/lib.rs: reescrito como shim de re-export
  (~25 líneas). Las definiciones, validaciones y tests viven en
  brahman-card.
- crates/core/ente-card/Cargo.toml: deps reducidas a brahman-card; se
  eliminan serde/serde_json/ulid (vienen transitivos vía re-export).
- crates/core/ente-zero/src/seed.rs: 4 struct-literals de EntityCard
  ahora terminan con `..Default::default()` para cubrir los nuevos
  campos del schema híbrido.

Los 21 consumidores de ente-card (ente-zero, ente-bus, ente-brain,
ente-soma, ente-cas, los 12 *-compat, etc.) compilan sin cambios —
sus `use ente_card::EntityCard` y demás imports siguen resolviendo,
ahora a tipos de brahman-card.

cargo test -p brahman-card: 8/8.
cargo build -p ente-zero: OK.
cargo check --workspace: 0 errores.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

ed0e973c81cd60802110f77f09dc3be789787d6d · por Sergio · (hace 5 meses) · padre 0feba7450 · ver el árbol en este commit

Delta semántico · 31

crates/core/brahman-card/src/lib.rs

crates/core/ente-card/src/lib.rs

crates/core/ente-zero/src/seed.rs

5 archivos · +61 −331

diff --git a/Cargo.lock b/Cargo.lock
index de3b45f..5f24f23 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2494,9 +2494,7 @@ dependencies = [
 name = "ente-card"
 version = "0.0.1"
 dependencies = [
- "serde",
- "serde_json",
- "ulid",
+ "brahman-card",
 ]
 
 [[package]]
diff --git a/crates/core/brahman-card/src/lib.rs b/crates/core/brahman-card/src/lib.rs
index 7f19910..0897075 100644
--- a/crates/core/brahman-card/src/lib.rs
+++ b/crates/core/brahman-card/src/lib.rs
@@ -131,6 +131,31 @@ pub struct Card {
     pub extensions: HashMap<String, Value>,
 }
 
+impl Default for Card {
+    /// Default razonable para `..Default::default()` en struct-literals.
+    /// `id` queda en `Ulid::nil()` y `label` vacío — el consumidor debe
+    /// sobreescribirlos antes de validar.
+    fn default() -> Self {
+        Self {
+            schema_version: CARD_SCHEMA_VERSION,
+            id: Ulid::nil(),
+            lineage: None,
+            label: String::new(),
+            provides: BTreeSet::new(),
+            requires: BTreeSet::new(),
+            permissions: Permissions::default(),
+            soma: SomaSpec::default(),
+            payload: Payload::Virtual,
+            supervision: Supervision::OneShot,
+            lifecycle: Lifecycle::default(),
+            priority: Priority::default(),
+            flow: Flows::default(),
+            genesis: Vec::new(),
+            extensions: HashMap::new(),
+        }
+    }
+}
+
 // =====================================================================
 // Capacidades — heredadas de arje, tipadas, no strings
 // =====================================================================
diff --git a/crates/core/ente-card/Cargo.toml b/crates/core/ente-card/Cargo.toml
index f6352b3..b4dcdfc 100644
--- a/crates/core/ente-card/Cargo.toml
+++ b/crates/core/ente-card/Cargo.toml
@@ -4,8 +4,7 @@ version = "0.0.1"
 edition.workspace = true
 license.workspace = true
 publish.workspace = true
+description = "Alias histórico de brahman-card. Re-exporta tipos legacy (EntityCard ≡ Card)."
 
 [dependencies]
-serde = { workspace = true }
-serde_json = { workspace = true }
-ulid = { workspace = true }
+brahman-card = { path = "../brahman-card" }
diff --git a/crates/core/ente-card/src/lib.rs b/crates/core/ente-card/src/lib.rs
index 4449071..887d4b8 100644
--- a/crates/core/ente-card/src/lib.rs
+++ b/crates/core/ente-card/src/lib.rs
@@ -1,326 +1,30 @@
-//! ente-card: definición de la Tarjeta de Identidad del Ente.
+//! `ente-card` — alias histórico de [`brahman_card`].
 //!
-//! Una `EntityCard` no describe un proceso — describe una identidad en el
-//! grafo del fractal. El Init la lee y decide cómo *encarnarla*: como ELF
-//! nativo, módulo Wasm, wrapper legacy, o nodo virtual sin proceso.
-
-use serde::{Deserialize, Serialize};
-use std::collections::BTreeSet;
-use std::fmt;
-use std::time::Duration;
-use ulid::Ulid;
-
-/// Versión del esquema de la Card. Cambiar = romper compatibilidad del fractal.
-pub const CARD_SCHEMA_VERSION: u16 = 1;
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct EntityCard {
-    pub schema_version: u16,
-    pub id: Ulid,
-    pub lineage: Option<Ulid>,
-    pub label: String,
-    pub provides: BTreeSet<Capability>,
-    pub requires: BTreeSet<Capability>,
-    pub soma: SomaSpec,
-    pub payload: Payload,
-    pub supervision: Supervision,
-    /// Hijos a instanciar inmediatamente cuando esta Card se encarna. Se
-    /// consumen una vez (no se replican en restarts del padre — el grafo
-    /// re-emerge desde la Semilla viva, no desde la persistencia de la Card).
-    #[serde(default)]
-    pub genesis: Vec<EntityCard>,
-}
-
-impl EntityCard {
-    pub fn validate(&self) -> Result<(), CardError> {
-        if self.schema_version != CARD_SCHEMA_VERSION {
-            return Err(CardError::SchemaMismatch {
-                got: self.schema_version,
-                expected: CARD_SCHEMA_VERSION,
-            });
-        }
-        if self.label.is_empty() {
-            return Err(CardError::EmptyLabel);
-        }
-        if self.label.len() > 256 {
-            return Err(CardError::LabelTooLong(self.label.len()));
-        }
-        // Una capacidad simultáneamente en `requires` y `provides` indica un
-        // ciclo de auto-dependencia que el grafo no puede resolver.
-        for cap in &self.requires {
-            if self.provides.contains(cap) {
-                return Err(CardError::SelfDependency(cap.clone()));
-            }
-        }
-        // Coherencia del payload con sus invariantes.
-        validate_payload(&self.payload)?;
-        // ResourceLimits: rangos sanos.
-        validate_rlimits(&self.soma.rlimits)?;
-        // Cgroup weights: 1..10000 según docs del kernel cgroup v2.
-        validate_cgroup(&self.soma.cgroup)?;
-        // Validación recursiva de genesis. Si una hija es inválida, la
-        // Semilla entera se rechaza — falla rápida en boot.
-        for child in &self.genesis {
-            child.validate()?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug)]
-pub enum CardError {
-    SchemaMismatch { got: u16, expected: u16 },
-    EmptyLabel,
-    LabelTooLong(usize),
-    SelfDependency(Capability),
-    EmptyExec,
-    SentinelWasmHash,
-    InvalidRlimit(&'static str),
-    InvalidCgroupWeight(&'static str),
-}
-
-impl fmt::Display for CardError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::SchemaMismatch { got, expected } => {
-                write!(f, "schema version mismatch: got {got}, expected {expected}")
-            }
-            Self::EmptyLabel => write!(f, "card label is empty"),
-            Self::LabelTooLong(n) => write!(f, "label demasiado largo ({n} bytes, max 256)"),
-            Self::SelfDependency(c) => write!(f, "card both requires and provides {c:?}"),
-            Self::EmptyExec => write!(f, "Native/Legacy payload con exec vacío"),
-            Self::SentinelWasmHash => write!(f, "Wasm payload con sha256 sentinel (todo ceros)"),
-            Self::InvalidRlimit(s) => write!(f, "rlimit inválido: {s}"),
-            Self::InvalidCgroupWeight(s) => write!(f, "cgroup weight fuera de rango [1,10000]: {s}"),
-        }
-    }
-}
-
-impl std::error::Error for CardError {}
-
-fn validate_payload(p: &Payload) -> Result<(), CardError> {
-    match p {
-        Payload::Native { exec, .. } | Payload::Legacy { exec, .. } => {
-            if exec.trim().is_empty() {
-                return Err(CardError::EmptyExec);
-            }
-        }
-        Payload::Wasm { module_sha256, .. } => {
-            // Sentinel [0u8; 32] indica "no resoluble" — usado en dev como
-            // fallback. En prod debe ser un hash real.
-            if module_sha256.iter().all(|&b| b == 0) {
-                return Err(CardError::SentinelWasmHash);
-            }
-        }
-        Payload::Virtual => {}
-    }
-    Ok(())
-}
-
-fn validate_rlimits(rl: &ResourceLimits) -> Result<(), CardError> {
-    if let Some(m) = rl.mem_bytes {
-        if m == 0 { return Err(CardError::InvalidRlimit("mem_bytes=0")); }
-        if m > 1u64 << 40 { return Err(CardError::InvalidRlimit("mem_bytes>1TiB")); }
-    }
-    if let Some(n) = rl.nproc {
-        if n == 0 || n > 65535 { return Err(CardError::InvalidRlimit("nproc fuera de [1,65535]")); }
-    }
-    if let Some(n) = rl.nofile {
-        if n == 0 || n > 1_048_576 { return Err(CardError::InvalidRlimit("nofile fuera de [1,1M]")); }
-    }
-    Ok(())
-}
-
-fn validate_cgroup(cg: &CgroupSpec) -> Result<(), CardError> {
-    if let Some(w) = cg.cpu_weight {
-        if !(1..=10000).contains(&w) { return Err(CardError::InvalidCgroupWeight("cpu_weight")); }
-    }
-    if let Some(w) = cg.io_weight {
-        if !(1..=10000).contains(&w) { return Err(CardError::InvalidCgroupWeight("io_weight")); }
-    }
-    Ok(())
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub enum Capability {
-    /// Provee un punto de montaje root para Entes hijos.
-    FilesystemRoot,
-    /// Acceso a una familia de netlink.
-    KernelNetlink(NetlinkFamily),
-    /// Endpoint del bus interno del fractal — equivalente tipado de un nombre
-    /// D-Bus, sin la string libre.
-    Endpoint { interface: InterfaceId, version: u16 },
-    /// Reemplazo del shim de systemd-logind. Solo Ente #compat-logind lo provee.
-    LegacyLogind,
-    /// Acceso crudo a una clase de dispositivo. Capacidad escalada.
-    Device { class: DeviceClass },
-    /// Permiso de instanciar Entes hijos. Por defecto solo PID 1 lo tiene.
-    Spawn,
-    /// Acceso a logging estructurado del fractal.
-    Journal,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub enum NetlinkFamily { Uevent, Route, Generic, Audit }
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub enum DeviceClass { Block, Tty, Input, Drm, Net, Hidraw }
-
-/// Identificador de interfaz del bus interno. UUID, no string. Para extender
-/// el protocolo del fractal, generas un UUID nuevo y versionas.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub struct InterfaceId(pub [u8; 16]);
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct SomaSpec {
-    pub namespaces: NamespaceSet,
-    pub rlimits: ResourceLimits,
-    pub cgroup: CgroupSpec,
-    pub cpu_affinity: Option<Vec<u32>>,
-}
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct NamespaceSet {
-    pub mount: bool,
-    pub pid: bool,
-    pub net: bool,
-    pub uts: bool,
-    pub ipc: bool,
-    pub user: bool,
-    pub cgroup: bool,
-}
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct ResourceLimits {
-    pub mem_bytes: Option<u64>,
-    pub nproc: Option<u32>,
-    pub nofile: Option<u32>,
-}
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct CgroupSpec {
-    pub path: String,
-    pub cpu_weight: Option<u32>,
-    pub io_weight: Option<u32>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum Payload {
-    Wasm { module_sha256: [u8; 32], entry: String },
-    Native {
-        exec: String,
-        argv: Vec<String>,
-        envp: Vec<(String, String)>,
-    },
-    /// Sin proceso. Nodo lógico del grafo (agregadores, mediators).
-    Virtual,
-    /// Wrapper de daemon legacy. `fakes` activa shims D-Bus / sd_notify.
-    Legacy {
-        exec: String,
-        argv: Vec<String>,
-        fakes: BTreeSet<LegacyFacade>,
-    },
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub enum LegacyFacade {
-    SystemdLogind,
-    SystemdHostnamed,
-    SystemdNotify,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum Supervision {
-    Restart {
-        #[serde(with = "duration_millis")]
-        initial: Duration,
-        #[serde(with = "duration_millis")]
-        max: Duration,
-    },
-    OneShot,
-    Delegate,
-}
-
-mod duration_millis {
-    use serde::{Deserialize, Deserializer, Serializer};
-    use std::time::Duration;
-
-    pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
-        s.serialize_u64(d.as_millis() as u64)
-    }
-
-    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
-        let ms = u64::deserialize(d)?;
-        Ok(Duration::from_millis(ms))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn seed_card_validates() {
-        let card = EntityCard {
-            schema_version: CARD_SCHEMA_VERSION,
-            id: Ulid::new(),
-            lineage: None,
-            label: "ente-zero".into(),
-            provides: [Capability::Spawn, Capability::Journal].into_iter().collect(),
-            requires: BTreeSet::new(),
-            soma: SomaSpec::default(),
-            payload: Payload::Virtual,
-            supervision: Supervision::OneShot,
-            genesis: vec![],
-        };
-        card.validate().unwrap();
-    }
-
-    #[test]
-    fn self_dependency_rejected() {
-        let mut s = BTreeSet::new();
-        s.insert(Capability::Journal);
-        let card = EntityCard {
-            schema_version: CARD_SCHEMA_VERSION,
-            id: Ulid::new(),
-            lineage: None,
-            label: "bad".into(),
-            provides: s.clone(),
-            requires: s,
-            soma: SomaSpec::default(),
-            payload: Payload::Virtual,
-            supervision: Supervision::OneShot,
-            genesis: vec![],
-        };
-        assert!(matches!(card.validate(), Err(CardError::SelfDependency(_))));
-    }
-
-    #[test]
-    fn invalid_genesis_propagates() {
-        let bad_child = EntityCard {
-            schema_version: CARD_SCHEMA_VERSION,
-            id: Ulid::new(),
-            lineage: None,
-            label: "".into(),
-            provides: BTreeSet::new(),
-            requires: BTreeSet::new(),
-            soma: SomaSpec::default(),
-            payload: Payload::Virtual,
-            supervision: Supervision::OneShot,
-            genesis: vec![],
-        };
-        let parent = EntityCard {
-            schema_version: CARD_SCHEMA_VERSION,
-            id: Ulid::new(),
-            lineage: None,
-            label: "parent".into(),
-            provides: BTreeSet::new(),
-            requires: BTreeSet::new(),
-            soma: SomaSpec::default(),
-            payload: Payload::Virtual,
-            supervision: Supervision::OneShot,
-            genesis: vec![bad_child],
-        };
-        assert!(matches!(parent.validate(), Err(CardError::EmptyLabel)));
-    }
-}
+//! Mantenido como compatibilidad para los crates `ente-*` del Init que
+//! importan `EntityCard`, `Capability`, `Payload`, etc. La fuente de verdad
+//! del schema vive en [`brahman_card`]; este crate sólo re-exporta los tipos
+//! bajo sus nombres legacy:
+//!
+//! - `EntityCard` ≡ [`brahman_card::Card`]
+//! - El resto de tipos conservan el mismo nombre.
+//!
+//! Toda lógica nueva debe consumir directamente `brahman_card`.
+
+#![forbid(unsafe_code)]
+
+pub use brahman_card::{
+    Capability,
+    CardError,
+    Card as EntityCard,
+    CgroupSpec,
+    DeviceClass,
+    InterfaceId,
+    LegacyFacade,
+    NamespaceSet,
+    NetlinkFamily,
+    Payload,
+    ResourceLimits,
+    SomaSpec,
+    Supervision,
+    CARD_SCHEMA_VERSION,
+};
diff --git a/crates/core/ente-zero/src/seed.rs b/crates/core/ente-zero/src/seed.rs
index 376bfcf..fe01689 100644
--- a/crates/core/ente-zero/src/seed.rs
+++ b/crates/core/ente-zero/src/seed.rs
@@ -59,6 +59,7 @@ fn load_from_snapshot(path: &Path) -> anyhow::Result<EntityCard> {
         payload: Payload::Virtual,
         supervision: Supervision::OneShot,
         genesis: snap.entes,
+        ..Default::default()
     })
 }
 
@@ -191,6 +192,7 @@ fn synthesize_dev_seed() -> EntityCard {
         payload: Payload::Virtual,
         supervision: Supervision::OneShot,
         genesis,
+        ..Default::default()
     }
 }
 
@@ -206,6 +208,7 @@ fn make_card(label: &str, payload: Payload, supervision: Supervision) -> EntityC
         payload,
         supervision,
         genesis: vec![],
+        ..Default::default()
     }
 }
 
@@ -234,6 +237,7 @@ fn optional_native_card(
         },
         supervision,
         genesis: vec![],
+        ..Default::default()
     })
 }