53dbdf0f1d
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>
226 lines
9.1 KiB
Rust
226 lines
9.1 KiB
Rust
//! `EnteGraph`: estado del fractal vivo en PID 1.
|
||
//!
|
||
//! Diseño:
|
||
//! - Submódulos por concern: lifecycle, topology, shutdown, bus_mediator,
|
||
//! devices, capabilities. Cada uno extiende `impl EnteGraph` con métodos
|
||
//! relacionados.
|
||
//! - Estado plano (no substructs todavía) — la separación es por
|
||
//! comportamiento, no por compartimentación de datos.
|
||
//! - Toda mutación pasa por el bucle primordial vía `GraphEvent`. Los
|
||
//! submódulos se llaman desde `main.rs::primordial_loop`.
|
||
|
||
mod bus_mediator;
|
||
mod capabilities;
|
||
mod devices;
|
||
mod lifecycle;
|
||
mod shutdown;
|
||
mod topology;
|
||
|
||
use ente_bus::{BusMessage, BusResponse};
|
||
use ente_card::{Capability, EntityCard};
|
||
use nix::unistd::Pid;
|
||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||
use tokio::sync::{mpsc, oneshot};
|
||
use ulid::Ulid;
|
||
|
||
pub use shutdown::SHUTDOWN_GRACE;
|
||
|
||
/// Bit alto encendido en `seq` para invokes server-iniciados — evita choque
|
||
/// con secuencias allocadas por clientes.
|
||
pub(in crate::graph) const SERVER_SEQ_FLAG: u64 = 1u64 << 63;
|
||
|
||
pub struct EnteGraph {
|
||
pub(in crate::graph) seed: EntityCard,
|
||
/// Entes encarnados como proceso o nodo virtual. id↔pid bidireccional.
|
||
pub(in crate::graph) incarnated: HashMap<Ulid, Incarnated>,
|
||
pub(in crate::graph) by_pid: HashMap<i32, Ulid>,
|
||
/// Quién provee qué capacidad. Resuelve `requires` y `pick_invokable`.
|
||
pub(in crate::graph) providers: BTreeMap<Capability, BTreeSet<Ulid>>,
|
||
/// Tokens de capability emitidos. Revocables al morir el proveedor.
|
||
pub(in crate::graph) next_token: u64,
|
||
pub(in crate::graph) grants: HashMap<u64, GrantedCapability>,
|
||
/// Dispositivos del kernel presentes (devpath → última UEvent).
|
||
pub(in crate::graph) devices: HashMap<String, ente_kernel::UEvent>,
|
||
/// Cards genesis pendientes de instanciar (extraídas de la Semilla).
|
||
pub(in crate::graph) pending_genesis: Vec<EntityCard>,
|
||
/// Hijos directos por lineage. parent → [child, ...].
|
||
pub(in crate::graph) children: HashMap<Ulid, Vec<Ulid>>,
|
||
/// Conexiones del bus indexadas por la identidad anunciada y verificada
|
||
/// con SO_PEERCRED. El value es el extremo de escritura del writer task.
|
||
pub(in crate::graph) bus_connections: HashMap<Ulid, mpsc::Sender<BusMessage>>,
|
||
/// Invokes forwardeados pendientes de respuesta del proveedor.
|
||
pub(in crate::graph) pending_invokes: HashMap<u64, oneshot::Sender<BusResponse>>,
|
||
pub(in crate::graph) next_invoke_seq: u64,
|
||
}
|
||
|
||
pub(in crate::graph) struct Incarnated {
|
||
pub card: EntityCard,
|
||
pub pid: Option<Pid>,
|
||
/// Capacidades añadidas en runtime vía BusRequest::UpdateCapabilities.
|
||
/// La Card original es immutable; la "vista efectiva" del Ente es
|
||
/// `card.provides ∪ dynamic_provides`.
|
||
pub dynamic_provides: BTreeSet<Capability>,
|
||
}
|
||
|
||
pub(in crate::graph) struct GrantedCapability {
|
||
pub cap: Capability,
|
||
pub provider: Ulid,
|
||
pub holder: Ulid,
|
||
/// Instante en el que el grant deja de ser válido. El garbage collector
|
||
/// del cerebro purga grants con `Instant::now() > expires_at`.
|
||
pub expires_at: std::time::Instant,
|
||
}
|
||
|
||
/// TTL default para grants cuando la cap no tiene override. 60s es un
|
||
/// compromiso: largo enough para evitar churn en patrones interactivos,
|
||
/// corto enough para que credenciales filtradas expiren rápidamente.
|
||
pub const DEFAULT_GRANT_TTL: std::time::Duration = std::time::Duration::from_secs(60);
|
||
|
||
/// Quota máxima de tokens activos por (holder, cap). Caps escaladas tienen
|
||
/// quota baja para limitar fugas de credenciales; caps de uso frecuente
|
||
/// (Endpoint, Journal) son más laxas.
|
||
pub fn quota_for_capability(cap: &Capability) -> u32 {
|
||
match cap {
|
||
// Caps escaladas: pocos tokens, fuerza patrón request-act-release.
|
||
Capability::Spawn => 2,
|
||
Capability::FilesystemRoot => 2,
|
||
Capability::Device { .. } => 4,
|
||
// Caps de propósito general.
|
||
Capability::Endpoint { .. } => 16,
|
||
Capability::KernelNetlink(_) => 4,
|
||
Capability::LegacyLogind => 8,
|
||
// Logging: hasta 32 streams.
|
||
Capability::Journal => 32,
|
||
}
|
||
}
|
||
|
||
/// TTL específico por variante de Capability. Caps de mayor riesgo / costo
|
||
/// (Spawn, FilesystemRoot) tienen TTL más corto; caps "logging" como
|
||
/// Journal pueden vivir más.
|
||
///
|
||
/// Cualquier cap no listada cae al `DEFAULT_GRANT_TTL`.
|
||
pub fn ttl_for_capability(cap: &Capability) -> std::time::Duration {
|
||
use std::time::Duration;
|
||
match cap {
|
||
// Caps escaladas: TTL corto para forzar renovación frecuente.
|
||
Capability::Spawn => Duration::from_secs(30),
|
||
Capability::FilesystemRoot => Duration::from_secs(30),
|
||
Capability::Device { .. } => Duration::from_secs(60),
|
||
// Caps de propósito general.
|
||
Capability::Endpoint { .. } => Duration::from_secs(300), // 5 min
|
||
Capability::KernelNetlink(_) => Duration::from_secs(300),
|
||
Capability::LegacyLogind => Duration::from_secs(300),
|
||
// Logging puede vivir mucho.
|
||
Capability::Journal => Duration::from_secs(3600), // 1h
|
||
}
|
||
}
|
||
|
||
impl EnteGraph {
|
||
pub fn new(mut seed: EntityCard) -> Self {
|
||
// Extraemos genesis antes de almacenar la Semilla — evita duplicación
|
||
// en `incarnated[seed.id]`.
|
||
let pending_genesis = std::mem::take(&mut seed.genesis);
|
||
let mut g = Self {
|
||
seed: seed.clone(),
|
||
incarnated: HashMap::new(),
|
||
by_pid: HashMap::new(),
|
||
providers: BTreeMap::new(),
|
||
next_token: 1,
|
||
grants: HashMap::new(),
|
||
devices: HashMap::new(),
|
||
pending_genesis,
|
||
children: HashMap::new(),
|
||
bus_connections: HashMap::new(),
|
||
pending_invokes: HashMap::new(),
|
||
next_invoke_seq: 0,
|
||
};
|
||
// El Ente #0 se inscribe a sí mismo como proveedor de las capacidades
|
||
// que su Card declara — sólo así los hijos pueden requerirlas.
|
||
g.register_provider(&seed);
|
||
g.incarnated.insert(seed.id, Incarnated {
|
||
card: seed, pid: None,
|
||
dynamic_provides: BTreeSet::new(),
|
||
});
|
||
g
|
||
}
|
||
|
||
pub fn lookup_pid(&self, pid: Pid) -> Option<Ulid> {
|
||
self.by_pid.get(&pid.as_raw()).copied()
|
||
}
|
||
|
||
/// Acceso read-only a la Card de un Ente vivo. Usado por el cerebro
|
||
/// para hidratar `SubjectInfo` sin clonar todo el mapa.
|
||
pub fn peek_card(&self, id: &Ulid) -> Option<&EntityCard> {
|
||
self.incarnated.get(id).map(|i| &i.card)
|
||
}
|
||
|
||
/// Identidad de la Semilla. Usado como `requester` para spawns generados
|
||
/// por reglas auto-cristalizadas (única identidad con Capability::Spawn).
|
||
pub fn seed_id(&self) -> Ulid {
|
||
self.seed.id
|
||
}
|
||
|
||
/// Captura el estado live como snapshot serializable. Excluye la Semilla
|
||
/// (será re-sintetizada al restore con su seed_id preservado).
|
||
pub fn snapshot(&self) -> ente_snapshot::FractalSnapshot {
|
||
let entes: Vec<EntityCard> = self.incarnated.iter()
|
||
.filter(|(id, _)| **id != self.seed.id)
|
||
.map(|(_, inc)| inc.card.clone())
|
||
.collect();
|
||
ente_snapshot::FractalSnapshot {
|
||
version: ente_snapshot::SNAPSHOT_VERSION,
|
||
timestamp_ms: ente_snapshot::now_ms(),
|
||
seed_id: self.seed.id,
|
||
seed_label: self.seed.label.clone(),
|
||
entes,
|
||
}
|
||
}
|
||
|
||
pub(in crate::graph) fn register_provider(&mut self, card: &EntityCard) {
|
||
for cap in &card.provides {
|
||
self.providers.entry(cap.clone()).or_default().insert(card.id);
|
||
}
|
||
}
|
||
|
||
pub(in crate::graph) fn unregister_provider(&mut self, card: &EntityCard) {
|
||
for cap in &card.provides {
|
||
if let Some(set) = self.providers.get_mut(cap) {
|
||
set.remove(&card.id);
|
||
}
|
||
}
|
||
// Revocar grants emitidos por el Ente fallecido.
|
||
let revoked: Vec<u64> = self.grants.iter()
|
||
.filter(|(_, g)| g.provider == card.id)
|
||
.map(|(t, _)| *t)
|
||
.collect();
|
||
for t in revoked {
|
||
self.grants.remove(&t);
|
||
}
|
||
}
|
||
|
||
/// Quita una capacidad dinámica del índice de providers para un Ente
|
||
/// específico. Usado al recibir UpdateCapabilities con `removes`.
|
||
pub(in crate::graph) fn unregister_dynamic_cap(&mut self, ente_id: Ulid, cap: &Capability) {
|
||
if let Some(set) = self.providers.get_mut(cap) {
|
||
set.remove(&ente_id);
|
||
}
|
||
}
|
||
|
||
/// Inserta una capacidad dinámica al índice de providers para un Ente.
|
||
pub(in crate::graph) fn register_dynamic_cap(&mut self, ente_id: Ulid, cap: Capability) {
|
||
self.providers.entry(cap).or_default().insert(ente_id);
|
||
}
|
||
|
||
/// El Ente #0 (semilla) tiene todas sus capacidades declaradas. Otros
|
||
/// las tienen si su Card las declara o si poseen un grant vivo.
|
||
pub(in crate::graph) fn holder_has(&self, holder: Ulid, cap: &Capability) -> bool {
|
||
if holder == self.seed.id {
|
||
return self.seed.provides.contains(cap);
|
||
}
|
||
if let Some(inc) = self.incarnated.get(&holder) {
|
||
if inc.card.provides.contains(cap) { return true; }
|
||
}
|
||
self.grants.values().any(|g| g.holder == holder && &g.cap == cap)
|
||
}
|
||
}
|