feat: segundo módulo (nakui) + admin API + brahman-status
Dos cosas en una sesión, en el orden discutido:
(1) Segundo módulo brahman vivo: nakui-core
- crates/modules/nakui/core/Cargo.toml: deps brahman-card,
brahman-sidecar, ulid.
- crates/modules/nakui/core/src/bin/nakui.rs: brahman_card_for_nakui()
construye una Card como Lifecycle::Daemon, Supervision::Restart,
flow.input "command" (json) + flow.output "report" (json). El
cmd_run llama brahman_sidecar::spawn antes de levantar el server
de nakui.
(2) crates/shared/brahman-sidecar (estrena crates/shared/)
Boilerplate del sidecar extraído (DRY): el thread con tokio current
thread runtime, conexión vía Client::connect, ping loop. Yahweh y
nakui ahora consumen este crate. API:
- spawn(card) fire-and-forget
- spawn_with_handle(config) con JoinHandle
Example "presence" útil para demos: módulo dummy con label tomado
del primer arg que se queda vivo hasta SIGTERM.
(3) crates/core/brahman-admin: observabilidad del broker
Socket Unix paralelo en \$BRAHMAN_ADMIN_SOCKET (default
\$XDG_RUNTIME_DIR/brahman-admin.sock). Cada conexión recibe un
StatusSnapshot JSON line-delimited y se cierra. Compatible con nc/socat.
- StatusSnapshot { server, protocol, init_attached, sessions, matches }
- server::AdminServer
- client::query(path)
- example "brahman-status" CLI
(4) Wiring de ente-zero
En primordial_loop, junto al handshake server, ahora también levanta
AdminServer con misma política de degradación grácil.
(5) brahman-broker: BrokeredCard ahora incluye lifecycle. Endpoint y
Match derivan Serialize/Deserialize. Nuevo método cards() expone
iterador de BrokeredCard para que el admin pueda construir snapshots.
(6) brahman-card: re-export pub use ulid::* para que módulos no
necesiten depender de ulid directamente.
(7) yahweh-shell migrado al sidecar compartido. Su brahman_client.rs
pasa de 96 a 53 líneas: sólo declara la Card, delega el spawn.
Demo end-to-end:
$ ente-zero &
$ presence demo.producer &
$ presence demo.consumer &
$ brahman-status
Init: server=0.1.0 protocol=0.1.0 attached=true
Sessions (2):
01KR42TY1J... demo.producer lifecycle=Daemon priority=Normal
01KR42TY1K... demo.consumer lifecycle=Daemon priority=Normal
Matches (2):
demo.producer.in ← demo.consumer.out via Exact
demo.consumer.in ← demo.producer.out via Exact
El broker matchea bidireccional por tipo. El admin lo expone.
Tests: 27/27. cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
//! `brahman-sidecar` — boilerplate del cliente brahman extraído.
|
||||
//!
|
||||
//! Cualquier módulo que quiera presentarse al Init brahman pero que tenga
|
||||
//! su propio runtime (GPUI, current_thread tokio, std-thread loop, etc.)
|
||||
//! puede llamar [`spawn`] con su [`brahman_card::Card`]. Eso arma un
|
||||
//! thread aparte con un runtime tokio current_thread, conecta al Init,
|
||||
//! y mantiene la sesión viva con pings periódicos.
|
||||
//!
|
||||
//! Si el Init no está disponible, el thread loggea y termina — el módulo
|
||||
//! sigue funcionando standalone.
|
||||
//!
|
||||
//! Errores de conexión / ping se loggean vía `tracing::warn!`. Si querés
|
||||
//! capturar la salida del thread (por ejemplo para test), usá
|
||||
//! [`spawn_with_handle`] que devuelve un `JoinHandle`.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use brahman_card::Card;
|
||||
use brahman_handshake::{client::Client, transport};
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Período entre pings al Init.
|
||||
pub const DEFAULT_PING_INTERVAL: Duration = Duration::from_secs(30);
|
||||
|
||||
/// Configuración del sidecar.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SidecarConfig {
|
||||
/// Card que se presenta al Init.
|
||||
pub card: Card,
|
||||
/// Período entre pings.
|
||||
pub ping_interval: Duration,
|
||||
}
|
||||
|
||||
impl SidecarConfig {
|
||||
/// Configuración con defaults razonables: ping cada 30s.
|
||||
pub fn new(card: Card) -> Self {
|
||||
Self {
|
||||
card,
|
||||
ping_interval: DEFAULT_PING_INTERVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn fire-and-forget. Devuelve inmediatamente; el handle se descarta.
|
||||
/// Si el thread no se puede crear (raro), loggea y sigue.
|
||||
pub fn spawn(card: Card) {
|
||||
if let Err(e) = spawn_with_handle(SidecarConfig::new(card)) {
|
||||
warn!(error = %e, "no se pudo spawnear el sidecar brahman");
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn devolviendo el `JoinHandle` para tests o cleanup explícito.
|
||||
pub fn spawn_with_handle(config: SidecarConfig) -> std::io::Result<JoinHandle<()>> {
|
||||
std::thread::Builder::new()
|
||||
.name("brahman-sidecar".into())
|
||||
.spawn(move || run_thread(config))
|
||||
}
|
||||
|
||||
fn run_thread(config: SidecarConfig) {
|
||||
let rt = match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
{
|
||||
Ok(rt) => rt,
|
||||
Err(e) => {
|
||||
warn!(error = %e, "tokio runtime falló");
|
||||
return;
|
||||
}
|
||||
};
|
||||
rt.block_on(run_client(config));
|
||||
}
|
||||
|
||||
async fn run_client(config: SidecarConfig) {
|
||||
let path = transport::default_socket_path();
|
||||
let mut client = match Client::connect(&path, config.card).await {
|
||||
Ok(c) => {
|
||||
info!(
|
||||
target: "brahman_sidecar",
|
||||
session = %c.session(),
|
||||
init_attached = c.server_info().init_attached,
|
||||
server = %c.server_info().server_version,
|
||||
"attached"
|
||||
);
|
||||
c
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
target: "brahman_sidecar",
|
||||
error = %e,
|
||||
socket = %path.display(),
|
||||
"no conectado"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(config.ping_interval).await;
|
||||
if let Err(e) = client.ping().await {
|
||||
warn!(target: "brahman_sidecar", error = %e, "ping falló — terminando sidecar");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user