feat(explorer+daemon): discovery dinamico via broker + query socket
Cierra el "explorer encuentra al daemon de forma totalmente dinamica"
del meta-plan. La UI deja de hardcodear el socket admin: descubre al
daemon nouser via MatchEvent::Available del broker y le consulta sus
Monadas directo.
Pipeline end-to-end:
- Daemon publica engine Card con service_socket = $XDG_RUNTIME_DIR/
nouser-engine.sock y flow.output = monad-list:json.
- Daemon binda Unix socket en ese path con listener blocking que
sirve nouser_card::query::QueryRequest::ListMonads, responde
ListMonadsResponse { engine, monads: Vec<MonadView> }.
- Explorer construye consumer Card con flow.input matched,
brahman_sidecar::await_provider_blocking le devuelve el socket,
y nouser_core::engine_socket::client::list_monads lo consulta.
- Cachea el socket; cualquier fallo de query lo invalida y la
proxima iteracion re-descubre.
Wire types nuevos en nouser_card::query:
- QueryRequest::ListMonads
- ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }
- MonadView: proyeccion slim de MonadManifest SIN centroid ni
members (KB que no tienen por que viajar cada poll).
- transport::default_socket_path() con env override.
Listener en nouser_core::engine_socket: spawn_listener + client
blocking con QueryError tipado. 3 tests integracion verdes.
Refactor explorer:
- Drop dep brahman-admin, add brahman-sidecar/nouser-card/nouser-core.
- State: socket cache + snapshot + socket_source informativo.
- TickOutcome enum desacopla la I/O del UI.
Trade-offs: polling 2s (no streaming — broker no empuja Data cards
hoy), re-discovery full en error (discovery es barato).
Tests: 10 (nouser-card +3 query) + 27 (nouser-core +3 engine_socket)
+ 4 (sidecar) verdes. Explorer compila clean.
This commit is contained in:
@@ -179,13 +179,21 @@ fn cmd_daemon(args: &[String]) -> Cmd {
|
||||
brahman_sidecar::SidecarPool::new().map_err(|e| format!("crear pool: {e}"))?,
|
||||
);
|
||||
|
||||
// 1. Engine como Ente.
|
||||
let engine_card = build_engine_card();
|
||||
// 1. Decidir el path del query socket ANTES de armar el engine
|
||||
// Card (porque viaja como service_socket en la Card).
|
||||
let query_socket = nouser_card::query::transport::default_socket_path();
|
||||
|
||||
// 2. Engine como Ente. Declara service_socket + flow.output para
|
||||
// que el broker pueda emitir MatchEvent::Available a consumers
|
||||
// interesados en `flow.input = monad-list:json`.
|
||||
let engine_card = build_engine_card(query_socket.clone());
|
||||
let engine_id = engine_card.id;
|
||||
let engine_label = engine_card.label.clone();
|
||||
eprintln!(
|
||||
"nouser daemon: publicando engine '{}' (kind=Ente, id={})",
|
||||
engine_label, engine_id
|
||||
"nouser daemon: publicando engine '{}' (kind=Ente, id={}, socket={})",
|
||||
engine_label,
|
||||
engine_id,
|
||||
query_socket.display()
|
||||
);
|
||||
pool.spawn(engine_card);
|
||||
|
||||
@@ -272,11 +280,39 @@ fn cmd_daemon(args: &[String]) -> Cmd {
|
||||
scanned_count, newly_spawned
|
||||
);
|
||||
|
||||
// Engine query socket: bind antes del watcher para que cualquier
|
||||
// consumer descubierto vía broker pueda consultarnos enseguida.
|
||||
// Si el bind falla, seguimos sin él — la UI degrada a "no
|
||||
// alcanzable" pero el daemon sigue procesando cambios.
|
||||
let db_shared = std::sync::Arc::new(std::sync::Mutex::new(db));
|
||||
let _query_listener = match nouser_core::engine_socket::spawn_listener(
|
||||
nouser_core::engine_socket::ListenerConfig {
|
||||
socket_path: query_socket.clone(),
|
||||
engine_id,
|
||||
engine_label: engine_label.clone(),
|
||||
watching: Some(dir.clone()),
|
||||
},
|
||||
db_shared.clone(),
|
||||
) {
|
||||
Ok(h) => {
|
||||
eprintln!(
|
||||
"nouser daemon: query socket activo en {} (proto: nouser_card::query)",
|
||||
query_socket.display()
|
||||
);
|
||||
Some(h)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"nouser daemon: query socket NO disponible ({e}) — explorer no podrá consultar"
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Watcher: cada cambio en el árbol — coalescido con debounce de
|
||||
// 150ms — dispara un re-scan + re-cluster del directorio y
|
||||
// re-publica al broker las Mónadas afectadas (drop + spawn por id,
|
||||
// gracias al replace en `SidecarPool::spawn`).
|
||||
let db_shared = std::sync::Arc::new(std::sync::Mutex::new(db));
|
||||
let _watcher = match spawn_fs_watcher(
|
||||
dir.clone(),
|
||||
db_shared.clone(),
|
||||
@@ -301,6 +337,8 @@ fn cmd_daemon(args: &[String]) -> Cmd {
|
||||
|
||||
std::thread::park();
|
||||
drop(_watcher);
|
||||
drop(_query_listener);
|
||||
let _ = std::fs::remove_file(&query_socket); // best-effort cleanup
|
||||
drop(pool);
|
||||
Ok(())
|
||||
}
|
||||
@@ -726,14 +764,32 @@ fn embed_via(
|
||||
|
||||
/// Card del propio engine (kind=Ente). Es el "ser" que produce y
|
||||
/// administra Mónadas; aparece en brahman-status junto a sus Mónadas.
|
||||
fn build_engine_card() -> brahman_card::Card {
|
||||
use brahman_card::{Card, CardKind, Lifecycle, Payload, Priority, Supervision};
|
||||
///
|
||||
/// Declara `service_socket` y `flow.output = monad-list:json` para
|
||||
/// que un consumer (UI, CLI) pueda descubrir al daemon vía broker
|
||||
/// MatchEvent y consultarle por sus Mónadas sin pasar por
|
||||
/// brahman-admin.
|
||||
fn build_engine_card(service_socket: std::path::PathBuf) -> brahman_card::Card {
|
||||
use brahman_card::{Card, CardKind, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef};
|
||||
use nouser_card::query::{FLOW_MONAD_LIST, FLOW_TYPE_NAME};
|
||||
|
||||
Card {
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::Delegate,
|
||||
lifecycle: Lifecycle::Daemon,
|
||||
priority: Priority::Normal,
|
||||
kind: CardKind::Ente,
|
||||
service_socket: Some(service_socket),
|
||||
flow: Flows {
|
||||
input: vec![],
|
||||
output: vec![Flow {
|
||||
name: FLOW_MONAD_LIST.into(),
|
||||
ty: TypeRef::Primitive {
|
||||
name: FLOW_TYPE_NAME.into(),
|
||||
},
|
||||
pin_to: None,
|
||||
}],
|
||||
},
|
||||
..Card::new("brahman.nouser_engine")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user