gioser
⎇ d6b8f18b43c9bd9bf7268e48b61392a2d62bb229 crates/ente-brain/src/dispatch.rs Rust · 73 líneas · 3.2 KB Historia Quién tocó qué crudo

Pausa: 11 crates del fractal Ente #0 con cerebro completo d6b8f18b4 · Sergio ·

1//! Despacho asíncrono de Actions. El motor entrega `Vec<Arc<Rule>>` matched;
2//! este módulo las traduce a efectos del fractal vía un `ActionSink` trait.
3//!
4//! Esto invierte la dependencia: ente-brain no conoce a ente-zero. El init
5//! implementa `ActionSink` y wirea spawn/invoke/log a sus propias estructuras.
6
7use crate::rules::{Action, LogLevel, Rule};
8use std::sync::Arc;
9use tracing::{debug, error, info, trace, warn};
10
11/// Backend de ejecución de Actions. ente-zero implementa esto delegando a
12/// graph_tx (Spawn → SpawnRequest, Invoke → bus call, etc.).
13pub trait ActionSink: Send + Sync {
14    /// Spawn una Card decodificada. Implementación: GraphEvent::SpawnRequest.
15    fn spawn(&self, card_blob: &str);
16    /// Invoke por bus. blob crudo; el sink lo enruta vía bus_mediator.
17    fn invoke(&self, target_cap: ente_card::Capability, blob: Vec<u8>);
18    /// Notifica a un Ente específico (target_id). Implementación: forward por bus.
19    fn notify(&self, target_id: ulid::Ulid, message: &str);
20    /// Inhibe un comportamiento (placeholder; semántica depende del sink).
21    fn inhibit(&self, reason: &str);
22}
23
24/// Sink por defecto que sólo logea. Útil para tests y dev sin runtime.
25pub struct NullSink;
26
27impl ActionSink for NullSink {
28    fn spawn(&self, card_blob: &str) {
29        info!(blob_len = card_blob.len(), "NullSink::spawn (no-op)");
30    }
31    fn invoke(&self, target_cap: ente_card::Capability, blob: Vec<u8>) {
32        info!(?target_cap, blob_len = blob.len(), "NullSink::invoke (no-op)");
33    }
34    fn notify(&self, target_id: ulid::Ulid, message: &str) {
35        info!(%target_id, %message, "NullSink::notify (no-op)");
36    }
37    fn inhibit(&self, reason: &str) {
38        info!(%reason, "NullSink::inhibit (no-op)");
39    }
40}
41
42/// Ejecuta las reglas matched. Cada Rule puede tener N Actions; ejecutamos
43/// todas. Las acciones de Log se evalúan inline (tracing es async-safe).
44/// Las acciones de Spawn/Invoke/Notify se delegan al sink — el sink decide
45/// si procesarlas sincrónica o asincrónicamente.
46pub async fn dispatch_actions(rules: &[Arc<Rule>], sink: &dyn ActionSink) {
47    for rule in rules {
48        trace!(id = %rule.id, priority = rule.priority, n = rule.then.len(), "dispatching rule");
49        for action in &rule.then {
50            execute_action(action, sink, rule.id).await;
51        }
52    }
53}
54
55async fn execute_action(action: &Action, sink: &dyn ActionSink, rule_id: ulid::Ulid) {
56    match action {
57        Action::Log { level, message } => emit_log(level, message, rule_id),
58        Action::Notify { target_id, message } => sink.notify(*target_id, message),
59        Action::Spawn { card_blob } => sink.spawn(card_blob),
60        Action::Invoke { target_cap, blob } => sink.invoke(target_cap.clone(), blob.clone()),
61        Action::Inhibit { reason } => sink.inhibit(reason),
62    }
63}
64
65fn emit_log(level: &LogLevel, message: &str, rule_id: ulid::Ulid) {
66    match level {
67        LogLevel::Trace => trace!(rule = %rule_id, "{}", message),
68        LogLevel::Debug => debug!(rule = %rule_id, "{}", message),
69        LogLevel::Info  => info! (rule = %rule_id, "{}", message),
70        LogLevel::Warn  => warn! (rule = %rule_id, "{}", message),
71        LogLevel::Error => error!(rule = %rule_id, "{}", message),
72    }
73}