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:
Sergio
2026-05-08 15:21:49 +00:00
parent 595f68e252
commit 70a7a0d46d
20 changed files with 627 additions and 76 deletions
+5 -70
View File
@@ -1,84 +1,19 @@
//! Sidecar brahman: yahweh se presenta al Init como módulo `Widget`.
//! Card de yahweh-shell + spawn del sidecar brahman compartido.
//!
//! Vive en un thread aparte con tokio runtime current_thread, desacoplado
//! de GPUI. Si el Init no está disponible, loggea y termina — yahweh
//! sigue funcionando standalone. Si conecta, mantiene la sesión viva
//! con pings periódicos hasta que la GUI termine o el server caiga.
//!
//! Card declarada:
//! - label: `brahman.ui_engine`
//! - lifecycle: `Widget`
//! - flow.input: `render-data` (json)
//! - flow.output: `user-intent` (json)
//! - permissions: filesystem read-write (yahweh persiste `layout.json`),
//! IPC `wit-v1`.
//! La lógica de thread + tokio + ping-loop vive en `brahman-sidecar`;
//! aquí sólo declaramos la identidad de yahweh como módulo Widget.
use std::collections::BTreeSet;
use std::time::Duration;
use brahman_card::{
Card, Flow, Flows, FsPolicy, IpcPolicy, Lifecycle, Payload, Permissions, Priority, Supervision,
TypeRef, CARD_SCHEMA_VERSION,
};
use brahman_handshake::{client::Client, transport};
use ulid::Ulid;
/// Período entre pings al Init.
const PING_INTERVAL: Duration = Duration::from_secs(30);
/// Spawn del sidecar brahman. No-op si el thread no se puede crear.
/// Devuelve inmediatamente; la conexión se establece en background.
/// Spawn del sidecar con la Card de yahweh.
pub fn spawn() {
let result = std::thread::Builder::new()
.name("brahman-client".into())
.spawn(run_thread);
if let Err(e) = result {
eprintln!("[brahman] no se pudo spawnear el sidecar: {e}");
}
}
fn run_thread() {
let rt = match tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
{
Ok(rt) => rt,
Err(e) => {
eprintln!("[brahman] tokio runtime falló: {e}");
return;
}
};
rt.block_on(run_client());
}
async fn run_client() {
let path = transport::default_socket_path();
let card = build_card();
let mut client = match Client::connect(&path, card).await {
Ok(c) => {
eprintln!(
"[brahman] attached: session={} init_attached={} server={}",
c.session(),
c.server_info().init_attached,
c.server_info().server_version
);
c
}
Err(e) => {
eprintln!("[brahman] no conectado a {} ({e})", path.display());
return;
}
};
loop {
tokio::time::sleep(PING_INTERVAL).await;
if let Err(e) = client.ping().await {
eprintln!("[brahman] ping falló: {e}");
return;
}
}
brahman_sidecar::spawn(build_card());
}
fn build_card() -> Card {