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:
@@ -154,12 +154,53 @@ pub enum MatchEventKind {
|
||||
Lost,
|
||||
}
|
||||
|
||||
/// Pedido de listado de sesiones activas registradas en el broker. La
|
||||
/// `session` es el id propio del que pregunta — el server lo valida
|
||||
/// contra la sesión actual de la conexión, mismo patrón que `Ping`.
|
||||
///
|
||||
/// Pensado para herramientas de observabilidad (broker-explorer y
|
||||
/// CLIs de diagnóstico). No expone secrets: sólo metadata pública
|
||||
/// que el módulo ya anunció en su `Hello`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ListSessions {
|
||||
pub session: SessionId,
|
||||
}
|
||||
|
||||
/// Una entrada en la respuesta a `ListSessions`. Slim por diseño —
|
||||
/// el observer arma la UI con esto sin tener que abrir conexiones
|
||||
/// adicionales por sesión.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionEntry {
|
||||
pub session: SessionId,
|
||||
/// Label declarado en `WireCard.label` — el "nombre humano" del
|
||||
/// módulo.
|
||||
pub label: String,
|
||||
/// Versión del schema de Card que el módulo declaró.
|
||||
pub schema_version: u16,
|
||||
/// Nombres de los `flow.output` que la Card declara producir.
|
||||
pub outputs: Vec<String>,
|
||||
/// Nombres de los `flow.input` que la Card declara consumir.
|
||||
pub inputs: Vec<String>,
|
||||
/// `true` si el módulo se anunció como "consciente" (trajo
|
||||
/// `WitInterface` extraída en el Hello).
|
||||
pub conscious: bool,
|
||||
}
|
||||
|
||||
/// Respuesta a `ListSessions`. El orden no está garantizado — los
|
||||
/// clientes que necesiten estabilidad pueden ordenar por `session`
|
||||
/// (Ulid es ordenable temporal).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionList {
|
||||
pub entries: Vec<SessionEntry>,
|
||||
}
|
||||
|
||||
/// Frame único de wire — discriminada por variante. Cada conexión es un
|
||||
/// stream de frames.
|
||||
///
|
||||
/// Direcciones:
|
||||
/// - Cliente → Server: `Hello`, `Ping`, `Farewell`.
|
||||
/// - Server → Cliente: `HelloAck`, `Pong`, `Error`, `MatchEvent`.
|
||||
/// - Cliente → Server: `Hello`, `Ping`, `Farewell`, `ListSessions`.
|
||||
/// - Server → Cliente: `HelloAck`, `Pong`, `Error`, `MatchEvent`,
|
||||
/// `SessionList`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Frame {
|
||||
Hello(Hello),
|
||||
@@ -169,4 +210,6 @@ pub enum Frame {
|
||||
Farewell(Farewell),
|
||||
Error(HandshakeError),
|
||||
MatchEvent(MatchEvent),
|
||||
ListSessions(ListSessions),
|
||||
SessionList(SessionList),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user