Files
arje/crates/ente-zero/src/graph/mod.rs
T
Sergio d6b8f18b43 Pausa: 11 crates del fractal Ente #0 con cerebro completo
PID 1 boot + bus interno autenticado + cerebro KCL/Rust:
- 6 lib crates de infra (card, bus, cas, kernel, soma, wasm, snapshot)
- ente-brain: motor de reglas O(1), observer Shannon, cristalización,
  audit hash-chain, persistencia rules.k, Prometheus /metrics
- KCL schemas card.k + rule.k como gramática autoritativa
- compat-logind D-Bus, ente-echo demo provider, ente-zero PID 1
- 22 tests OK, ~3.8k LOC Rust + ~300 LOC KCL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:57:44 +00:00

159 lines
6.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>,
}
pub(in crate::graph) struct GrantedCapability {
pub cap: Capability,
pub provider: Ulid,
pub holder: Ulid,
}
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 });
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);
}
}
/// 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)
}
}