feat(sidecar): API reusable de discovery via broker

Promueve el patron ad-hoc discover_producer_socket que vivia inline en
'nouser attract --remote' a un modulo publico brahman_sidecar::discovery.
Cualquier consumer ahora puede preguntar al broker "quien provee este
TypeRef?" sin reimplementar el patron a mano.

API:
- build_consumer_card(label, flow_name, type_name) construye una Card
  minima (Ente, Oneshot, Virtual) con un input flow. Asigna Ulid::new()
  real (no nil), evitando colisiones en el broker.
- await_provider(card, timeout) async: conecta al init, espera
  MatchEvent::Available, devuelve producer_service_socket, manda
  Farewell. Ignora eventos Lost durante el await.
- await_provider_blocking(card, timeout) wrapper para mundos no-async
  (CLIs, std-thread loops). Crea su propio runtime current_thread.
- ConsumerError tipado: Connect{socket,source}, NoProvider{flow,type_ref,
  timeout}, Client(ClientError), Runtime(String). Adios al Box<dyn Error>.

Refactor en nouser daemon: discover_producer_socket inline (60 LOC) ->
5 LOC delegando en el helper. remote_embed ya no construye su propio
runtime.

Tests: 4 unitarios (id no-nil, id unico por llamada, formateo de Wit
TypeRef, fallback sin input). Build verde para sidecar y nouser-core.
This commit is contained in:
Sergio
2026-05-09 02:30:48 +00:00
parent 2725d6a297
commit 006640057a
6 changed files with 294 additions and 75 deletions
+56
View File
@@ -6,6 +6,62 @@ ratio/diff ver `git show <sha>`.
## 2026-05-09
### feat(sidecar): API reusable de discovery vía broker
Promueve el patrón ad-hoc `discover_producer_socket` (que vivía
inline en `nouser attract --remote`) a un módulo público
`brahman_sidecar::discovery`. Cualquier consumer puede ahora
preguntar al broker "¿quién provee este TypeRef?" con dos llamadas:
// Construir un consumer Card mínimo (Ente, Oneshot, Virtual)
let card = brahman_sidecar::build_consumer_card(
"mi-cli",
"embed-result", // flow.input.name
"json", // TypeRef::Primitive { name }
);
// Bloqueante (CLIs, std-thread loops):
let socket: PathBuf = brahman_sidecar::await_provider_blocking(
card, Duration::from_secs(3),
)?;
// O async (módulos con runtime tokio propio):
let socket = brahman_sidecar::await_provider(card, timeout).await?;
API:
- `build_consumer_card(label, flow_name, type_name) -> Card`
abstrae la verbosidad del struct-literal repetido en cada caller.
Genera un `id: Ulid::new()` real (no nil → seguro contra
colisiones en el broker).
- `await_provider(card, timeout) -> Result<PathBuf, ConsumerError>`
conecta al init, espera `MatchEvent::Available`, devuelve
`producer_service_socket`, manda Farewell. Ignora eventos
`Lost` durante el await (no aplican al arranque).
- `await_provider_blocking(card, timeout)` arma su propio
runtime `current_thread` para mundos no-async.
- `ConsumerError` con variantes tipadas: `Connect { socket, source }`,
`NoProvider { flow, type_ref, timeout }`, `Client(ClientError)`,
`Runtime(String)`. Adiós al `Box<dyn Error>` de antes.
Refactor en `nouser daemon`:
- `discover_producer_socket` (60 LOC inline en `bin/nouser.rs`) → 5
líneas que delegan en el helper.
- `remote_embed` ya no construye su propio runtime tokio.
Próximo consumer natural: `nouser-explorer`. Hoy renderea
`StatusSnapshot` vía socket admin (introspección pura). El día que
quiera **interactuar** con un Ente — p. ej., disparar un re-embed
desde la UI — usa este helper para resolver el socket del provider
sin hardcodear paths.
Nota sobre identidad: este commit fuerza `Ulid::new()` para los
consumer Cards generados, evitando la trampa documentada del
`Card::default()` que devuelve `Ulid::nil()`. La fijación global de
`Default` queda como cleanup separado (requiere auditar que ningún
caller dependa del determinismo de `nil`).
Tests: 4 unitarios nuevos en `discovery::tests` (id no-nil, id
único por llamada, formateo de TypeRef::Wit, fallback sin input).
Workspace verde.
### feat(nouser+sidecar): watcher con debounce + re-publish al broker
Cierra las dos limitaciones del watcher previo: ya no spamea N veces por
una sola edición, y el broker ve los cambios estructurales en lugar de