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 · 2026-05-08T12:10:35Z (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
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()
})
}