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:
@@ -313,6 +313,10 @@ fn cmd_run(args: &[String]) -> Result<(), CliError> {
|
||||
.unwrap_or_else(|| "<memory>".into()),
|
||||
);
|
||||
|
||||
// Sidecar brahman: nakui se presenta al Init mientras el daemon vive.
|
||||
// No bloquea; si el Init no está, el sidecar termina silenciosamente.
|
||||
brahman_sidecar::spawn(brahman_card_for_nakui());
|
||||
|
||||
let executor = Executor::load_module(&module_dir)
|
||||
.map_err(|e| CliError::Op(format!("load module {}: {}", module_dir.display(), e)))?;
|
||||
let log = EventLog::open(&log_path).map_err(|e| CliError::Op(format!("open log: {}", e)))?;
|
||||
@@ -453,3 +457,59 @@ fn cmd_verify_log(args: &[String]) -> Result<(), CliError> {
|
||||
Err(e) => Err(CliError::Op(format!("verify failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Card que nakui presenta al Init brahman cuando arranca como daemon.
|
||||
///
|
||||
/// Lifecycle Daemon (proceso largo). Flujos JSON: consume `command`
|
||||
/// (queries del UI), produce `report` (resultados de cómputo). Los
|
||||
/// nombres están escogidos para que el broker pueda matchearlos contra
|
||||
/// `user-intent` / `render-data` de yahweh-shell por compatibilidad de
|
||||
/// tipo (todos `json`).
|
||||
fn brahman_card_for_nakui() -> brahman_card::Card {
|
||||
use brahman_card::{
|
||||
Card, Flow, Flows, FsPolicy, IpcPolicy, Lifecycle, Payload, Permissions, Priority,
|
||||
Supervision, TypeRef, CARD_SCHEMA_VERSION,
|
||||
};
|
||||
use std::collections::BTreeSet;
|
||||
use std::time::Duration;
|
||||
|
||||
Card {
|
||||
schema_version: CARD_SCHEMA_VERSION,
|
||||
id: ulid::Ulid::new(),
|
||||
lineage: None,
|
||||
label: "brahman.nakui_erp".into(),
|
||||
provides: BTreeSet::new(),
|
||||
requires: BTreeSet::new(),
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::Restart {
|
||||
initial: Duration::from_millis(200),
|
||||
max: Duration::from_secs(30),
|
||||
},
|
||||
lifecycle: Lifecycle::Daemon,
|
||||
priority: Priority::Normal,
|
||||
permissions: Permissions {
|
||||
filesystem: FsPolicy::ReadWrite,
|
||||
ipc: IpcPolicy {
|
||||
allow: vec!["wit-v1".into()],
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
flow: Flows {
|
||||
input: vec![Flow {
|
||||
name: "command".into(),
|
||||
ty: TypeRef::Primitive {
|
||||
name: "json".into(),
|
||||
},
|
||||
pin_to: None,
|
||||
}],
|
||||
output: vec![Flow {
|
||||
name: "report".into(),
|
||||
ty: TypeRef::Primitive {
|
||||
name: "json".into(),
|
||||
},
|
||||
pin_to: None,
|
||||
}],
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user