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>
130 lines
4.8 KiB
Rust
130 lines
4.8 KiB
Rust
//! Glue entre el bucle primordial y `ente-brain`.
|
|
//!
|
|
//! Tres responsabilidades:
|
|
//! 1. Traducir eventos del grafo (`GraphEvent`) a `ente_brain::EventKind`
|
|
//! + `SubjectInfo` para el observador y el motor.
|
|
//! 2. Implementar `ActionSink` para que las Acciones del cerebro tengan
|
|
//! un canal de salida hacia el grafo (Spawn → SpawnRequest, etc.).
|
|
//! 3. Encapsular el snapshot de SubjectInfo desde el grafo sin filtrar
|
|
//! detalles internos al cerebro.
|
|
|
|
use crate::events::GraphEvent;
|
|
use crate::graph::EnteGraph;
|
|
use ente_brain::{ActionSink, EventKind as BrainEventKind, SubjectInfo};
|
|
use ente_card::Capability;
|
|
use serde::Deserialize;
|
|
use tokio::sync::mpsc;
|
|
use tracing::warn;
|
|
use ulid::Ulid;
|
|
|
|
/// Traduce un GraphEvent a (EventKind, SubjectInfo) para alimentar el cerebro.
|
|
///
|
|
/// Devuelve `None` para eventos puramente internos del bus (Response, Close)
|
|
/// que no son interesantes para reglas o estadística.
|
|
pub fn graph_event_to_brain<'a>(
|
|
evt: &'a GraphEvent,
|
|
graph: &EnteGraph,
|
|
) -> Option<(BrainEventKind, SubjectInfo)> {
|
|
match evt {
|
|
GraphEvent::EnteDied { id, .. } => {
|
|
Some((BrainEventKind::EnteDied, subject_info_for(graph, *id)))
|
|
}
|
|
GraphEvent::SpawnRequest { card, .. } => {
|
|
// El "sujeto" del spawn es el child que va a nacer.
|
|
let info = SubjectInfo {
|
|
id: Some(card.id),
|
|
label: Some(card.label.clone()),
|
|
capabilities: card.provides.iter().cloned().collect(),
|
|
};
|
|
Some((BrainEventKind::EnteSpawned, info))
|
|
}
|
|
GraphEvent::BusRequest { from, request, .. } => {
|
|
let kind = match request {
|
|
ente_bus::BusRequest::Announce { .. } => BrainEventKind::BusAnnounce,
|
|
ente_bus::BusRequest::Invoke { cap, .. } => {
|
|
BrainEventKind::BusInvokeOf(cap.clone())
|
|
}
|
|
_ => BrainEventKind::BusInvoke,
|
|
};
|
|
let info = match from {
|
|
Some(id) => subject_info_for(graph, *id),
|
|
None => SubjectInfo::default(),
|
|
};
|
|
Some((kind, info))
|
|
}
|
|
GraphEvent::CapabilityRequested { from, .. } => {
|
|
Some((BrainEventKind::BusInvoke, subject_info_for(graph, *from)))
|
|
}
|
|
// Responses, ConnClosed, Shutdown — irrelevantes para reglas
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn subject_info_for(graph: &EnteGraph, id: Ulid) -> SubjectInfo {
|
|
// Acceso de sólo lectura — usamos el método público lookup_pid + cards
|
|
// virtuales en el grafo. Si el Ente no existe (ya disuelto), info vacía.
|
|
if let Some(card) = graph.card_for(&id) {
|
|
SubjectInfo {
|
|
id: Some(id),
|
|
label: Some(card.label.clone()),
|
|
capabilities: card.provides.iter().cloned().collect(),
|
|
}
|
|
} else {
|
|
SubjectInfo { id: Some(id), label: None, capabilities: Vec::new() }
|
|
}
|
|
}
|
|
|
|
/// `ActionSink` que enruta acciones del cerebro al bucle primordial.
|
|
pub struct GraphSink {
|
|
pub graph_tx: mpsc::Sender<GraphEvent>,
|
|
pub requester: Ulid,
|
|
}
|
|
|
|
impl ActionSink for GraphSink {
|
|
fn spawn(&self, card_blob: &str) {
|
|
// El blob es JSON de EntityCard.
|
|
match serde_json::from_str::<ente_card::EntityCard>(card_blob) {
|
|
Ok(card) => {
|
|
let evt = GraphEvent::SpawnRequest { card, requester: self.requester };
|
|
if self.graph_tx.try_send(evt).is_err() {
|
|
warn!("brain spawn: graph_tx lleno o cerrado");
|
|
}
|
|
}
|
|
Err(e) => warn!(?e, "brain spawn: blob no parseable como EntityCard JSON"),
|
|
}
|
|
}
|
|
|
|
fn invoke(&self, target_cap: Capability, blob: Vec<u8>) {
|
|
// Sin BusClient en proceso — el sink registra la intención. Una mejora
|
|
// futura: spawn un BusClient::connect + call. Por ahora log estructurado.
|
|
warn!(?target_cap, blob_len = blob.len(), "brain invoke: no bus client en glue (TODO)");
|
|
}
|
|
|
|
fn notify(&self, target_id: Ulid, message: &str) {
|
|
warn!(%target_id, %message, "brain notify: no implementado en glue");
|
|
}
|
|
|
|
fn inhibit(&self, reason: &str) {
|
|
warn!(%reason, "brain inhibit: no implementado en glue");
|
|
}
|
|
}
|
|
|
|
/// Helper para que el grafo exponga la Card de un Ente vivo. Lo añadimos como
|
|
/// trait extension porque graph::EnteGraph mantiene `incarnated` privado.
|
|
pub trait GraphCardLookup {
|
|
fn card_for(&self, id: &Ulid) -> Option<&ente_card::EntityCard>;
|
|
}
|
|
|
|
impl GraphCardLookup for EnteGraph {
|
|
fn card_for(&self, id: &Ulid) -> Option<&ente_card::EntityCard> {
|
|
// Acceso vía método público que añadiremos en graph/mod.rs.
|
|
self.peek_card(id)
|
|
}
|
|
}
|
|
|
|
// Eliminar el campo `_unused` que rustc puede quejarse — placeholder para
|
|
// evitar warning si algún field queda sin uso.
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize)]
|
|
struct _Touch {}
|