chore: monorepo inicial con arje + minga + yahweh absorbidos

Workspace en 4 ejes (core/modules/apps/shared):

- core/: 24 crates de arje (Init systemd-compatible: ente-card, ente-zero,
  ente-kernel, ente-bus, ente-cas, ente-soma, ente-wasm, ente-snapshot,
  ente-brain, ente-echo, ente-policy-provider, + 12 crates *-compat)
- modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST,
  minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli)
- modules/ui_engine/: 11 crates de yahweh (libs/{core,theme,bus,providers},
  widgets/{tree,splitter,tabs,tiled,container_core,text_input})
- apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer,
  image_viewer, yahweh-shell)
- shared_wit/protocol.wit: handshake/lifecycle inicial

Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio
"full", paths intra-workspace de yahweh redirigidos a su nueva ubicación.

cargo check --workspace: 0 errores, 17 warnings (dead code preexistente).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-05-08 04:45:44 +00:00
commit 53dbdf0f1d
176 changed files with 34845 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "ente-card"
version = "0.0.1"
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
ulid = { workspace = true }
+178
View File
@@ -0,0 +1,178 @@
# ============================================================================
# card.k — REFERENCE ONLY. NOT LOADED.
#
# La validación canónica de EntityCard vive en Rust:
# crates/ente-card/src/lib.rs :: EntityCard::validate()
# El loader (crates/ente-brain/src/loader.rs) sólo acepta JSON.
#
# Este archivo se conserva como notas de diseño legibles para humanos sobre
# las invariantes que `validate()` debe garantizar. Si modificas el shape
# en Rust, sincroniza este archivo a mano (o reemplázalo por JSON Schema
# generado vía `schemars`).
# ============================================================================
# ---------- Identidad ----------
schema EntityCard:
"""Tarjeta de Identidad. Inmutable: cambios = nueva Card con nuevo id."""
schema_version: int = 1
id: str # Ulid (26 chars, Crockford base32)
lineage?: str # parent Ulid; None = Ente raíz
label: str # legible, no es identificador
provides: [Capability] = [] # contrato hacia el grafo
requires: [Capability] = [] # contrato del grafo hacia el Ente
soma: SomaSpec # cuerpo: aislamiento + recursos
payload: Payload # cómo encarnar (Wasm/Native/Virtual)
supervision: Supervision # política tras muerte
genesis?: [EntityCard] = [] # hijos a instanciar al encarnar
check:
schema_version == 1, "schema version no soportada"
len(label) > 0, "label vacío"
len(id) == 26, "id debe ser Ulid (26 caracteres)"
# Auto-dependencia: una capacidad no puede estar en requires y provides
all c in requires { c not in provides }, "self-dependency: ${c}"
# ---------- Capacidades (typed enum) ----------
# KCL no tiene sum types nativos; usamos tagged union: `kind` + campos opcionales
# que sólo aplican según el kind. Las invariantes en `check:` aseguran consistencia.
schema Capability:
"""Capacidad tipada del fractal. NUNCA usar strings libres."""
kind: "FilesystemRoot" | "KernelNetlink" | "Endpoint" | "LegacyLogind" | "Device" | "Spawn" | "Journal"
netlink_family?: "Uevent" | "Route" | "Generic" | "Audit"
endpoint_interface?: str # 32-char hex (UUID 16 bytes)
endpoint_version?: int
device_class?: "Block" | "Tty" | "Input" | "Drm" | "Net" | "Hidraw"
check:
kind != "KernelNetlink" or netlink_family is not None, \
"KernelNetlink requiere netlink_family"
kind != "Endpoint" or (endpoint_interface is not None and endpoint_version is not None), \
"Endpoint requiere interface + version"
kind != "Endpoint" or len(endpoint_interface) == 32, \
"endpoint_interface debe ser hex de 32 chars"
kind != "Device" or device_class is not None, \
"Device requiere device_class"
# ---------- Soma: cuerpo + restricciones de recursos ----------
schema SomaSpec:
"""Aislamiento + recursos. Validados por KCL antes de tocar el kernel."""
namespaces: NamespaceSet = NamespaceSet {}
rlimits: ResourceLimits = ResourceLimits {}
cgroup: CgroupSpec = CgroupSpec {}
cpu_affinity?: [int] # CPU pinning
check:
cpu_affinity is None or all c in cpu_affinity { c >= 0 and c < 1024 }, \
"cpu_affinity fuera de rango [0, 1024)"
schema NamespaceSet:
mount: bool = False
pid: bool = False
net: bool = False
uts: bool = False
ipc: bool = False
user: bool = False
cgroup: bool = False
schema ResourceLimits:
"""Restricciones nativas validadas en KCL — el kernel sólo ve valores sanos."""
mem_bytes?: int # RLIMIT_AS
nproc?: int # RLIMIT_NPROC
nofile?: int # RLIMIT_NOFILE
energy_budget_mw?: int # presupuesto energético (futuro)
check:
mem_bytes is None or mem_bytes > 0, "mem_bytes debe ser positivo"
mem_bytes is None or mem_bytes <= 1099511627776, "mem_bytes > 1 TiB sospechoso"
nproc is None or (nproc > 0 and nproc <= 65535), "nproc fuera de rango"
nofile is None or (nofile > 0 and nofile <= 1048576), "nofile fuera de rango"
energy_budget_mw is None or energy_budget_mw > 0, "energy_budget_mw debe ser positivo"
schema CgroupSpec:
"""Cgroup v2: path + weights. cpu_weight 1..10000 según kernel docs."""
path: str = ""
cpu_weight?: int
io_weight?: int
check:
cpu_weight is None or (cpu_weight >= 1 and cpu_weight <= 10000), \
"cpu_weight 1..10000"
io_weight is None or (io_weight >= 1 and io_weight <= 10000), \
"io_weight 1..10000"
# ---------- Payload: tagged union de cómo encarnar ----------
schema Payload:
"""Una variante por Card. Set exactly one of: Wasm, Native, Virtual, Legacy."""
kind: "Wasm" | "Native" | "Virtual" | "Legacy"
# Wasm
module_sha256?: str # hex 64 chars
entry?: str
# Native / Legacy
exec?: str
argv?: [str] = []
envp?: [{str: str}] = []
# Legacy
fakes?: ["SystemdLogind" | "SystemdHostnamed" | "SystemdNotify"] = []
check:
kind != "Wasm" or (module_sha256 is not None and entry is not None), \
"Wasm requiere module_sha256 + entry"
kind != "Wasm" or len(module_sha256) == 64, "module_sha256 debe ser hex de 64 chars"
kind != "Native" or exec is not None, "Native requiere exec"
kind != "Legacy" or exec is not None, "Legacy requiere exec"
# ---------- Supervision ----------
schema Supervision:
kind: "Restart" | "OneShot" | "Delegate"
initial_ms?: int # ms — backoff inicial para Restart
max_ms?: int # ms — backoff máximo
check:
kind != "Restart" or (initial_ms is not None and max_ms is not None), \
"Restart requiere initial_ms + max_ms"
initial_ms is None or initial_ms >= 0, "initial_ms negativo"
max_ms is None or max_ms >= initial_ms or max_ms is None, \
"max_ms < initial_ms es contradictorio"
# ============================================================================
# Herencia: EnteWeb hereda de EnteBase con campos pre-rellenados.
# ============================================================================
schema EnteBase(EntityCard):
"""Base para Entes managed: declara Spawn provider y Journal por defecto."""
schema_version = 1
supervision = Supervision {kind = "Restart", initial_ms = 100, max_ms = 30000}
soma = SomaSpec {
rlimits = ResourceLimits {nofile = 4096}
cgroup = CgroupSpec {path = "ente.slice/managed", cpu_weight = 100}
}
schema EnteWeb(EnteBase):
"""Hereda EnteBase, declara endpoint + cap LegacyLogind como ejemplo."""
provides = [
Capability {kind = "Journal"}
Capability {
kind = "Endpoint"
endpoint_interface = "deadbeefcafe1234deadbeefcafe1234"
endpoint_version = 1
}
]
soma = SomaSpec {
namespaces = NamespaceSet {net = True, mount = True, pid = True}
rlimits = ResourceLimits {nofile = 16384, mem_bytes = 536870912} # 512 MiB
cgroup = CgroupSpec {path = "ente.slice/web", cpu_weight = 200, io_weight = 100}
}
+326
View File
@@ -0,0 +1,326 @@
//! ente-card: definición de la Tarjeta de Identidad del Ente.
//!
//! 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)));
}
}