feat(brahman-handshake): ListSessions endpoint + cliente + UI broker-explorer
Iter 20. Nuevo flujo end-to-end para observabilidad: cualquier módulo
conectado puede pedir al broker la lista de sesiones activas y mostrar
labels + flows in/out por cada una.
brahman-handshake/messages:
- Frame::ListSessions(ListSessions{session}) → Frame::SessionList(SessionList{entries}).
- SessionEntry: session, label, schema_version, outputs, inputs, conscious.
brahman-handshake/server:
- run_post_handshake pasa SessionRegistry a handle_inbound_frame.
- build_session_list helper proyecta el snapshot bajo lock.
- Validación session_id mismatched → Unauthorized.
brahman-handshake/client:
- Client::list_sessions() async, drena MatchEvents intermedios al
pending_events buffer, mismo patrón que ping().
brahman-sidecar/discovery:
- list_sessions / list_sessions_blocking arman Card observer mínima,
piden, Farewell.
brahman-broker-explorer:
- Poll-tick agrega list_sessions_blocking cuando broker está UP*.
- stat_card "Sesiones activas" con count + items ordenados por Ulid:
label · in:[flows] out:[flows] (wit)?.
Test list_sessions_returns_currently_registered: 3 clientes
conectados, observer pide list, verifica labels + schema_version
+ conscious=false. 24 handshake tests + sidecar + broker-explorer
verde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -142,9 +142,61 @@ pub fn await_provider_blocking(
|
||||
.enable_time()
|
||||
.build()
|
||||
.map_err(|e| ConsumerError::Runtime(e.to_string()))?;
|
||||
|
||||
rt.block_on(await_provider(consumer_card, timeout))
|
||||
}
|
||||
|
||||
/// Conecta al brahman-init con una Card observer (sin inputs ni
|
||||
/// outputs) y pide la lista de sesiones activas. Útil para
|
||||
/// herramientas de observabilidad (broker-explorer, CLIs).
|
||||
///
|
||||
/// El observer se identifica con `observer_label`. La sesión se
|
||||
/// cierra con Farewell antes de retornar (best-effort).
|
||||
pub async fn list_sessions(
|
||||
observer_label: impl Into<String>,
|
||||
) -> Result<brahman_handshake::messages::SessionList, ConsumerError> {
|
||||
let init_path = transport::default_socket_path();
|
||||
// Card mínima sin flow.input/output: el observer no participa en
|
||||
// matching, sólo establece sesión para poder consultar.
|
||||
let card = Card {
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::OneShot,
|
||||
lifecycle: Lifecycle::Oneshot,
|
||||
priority: Priority::Normal,
|
||||
kind: CardKind::Ente,
|
||||
flow: Flows {
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
},
|
||||
..Card::new(observer_label)
|
||||
};
|
||||
|
||||
let mut client = Client::connect(&init_path, card)
|
||||
.await
|
||||
.map_err(|source| ConsumerError::Connect {
|
||||
socket: init_path.clone(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let list = client.list_sessions().await?;
|
||||
let _ = client.farewell().await;
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Wrapper bloqueante de [`list_sessions`]. Idéntico patrón a
|
||||
/// `await_provider_blocking`: runtime current_thread efímero.
|
||||
pub fn list_sessions_blocking(
|
||||
observer_label: impl Into<String>,
|
||||
) -> Result<brahman_handshake::messages::SessionList, ConsumerError> {
|
||||
let label = observer_label.into();
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.map_err(|e| ConsumerError::Runtime(e.to_string()))?;
|
||||
rt.block_on(list_sessions(label))
|
||||
}
|
||||
|
||||
fn describe_first_input(card: &Card) -> (String, String) {
|
||||
match card.flow.input.first() {
|
||||
Some(flow) => {
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
pub mod discovery;
|
||||
pub use discovery::{
|
||||
await_provider, await_provider_blocking, build_consumer_card, ConsumerError,
|
||||
await_provider, await_provider_blocking, build_consumer_card, list_sessions,
|
||||
list_sessions_blocking, ConsumerError,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
Reference in New Issue
Block a user