Cierra "nakui no tiene UI propia" del audit. Nuevo binario standalone
nakui-explorer (paralelo a nouser-explorer) que renderea el event log
de un repo Nakui: timeline scrollable de seeds + morphisms con sus
parametros, breakdown por entity type, polling cada 2s para detectar
nuevos eventos appended sin restart.
Diseno: lee directamente el archivo .jsonl del nakui_core::event_log::
EventLog. Path por env NAKUI_EVENT_LOG, default "nakui.jsonl" en pwd.
Sin discovery via broker brahman porque nakui hoy es CLI/library/demos,
no daemon. Cuando se daemonice, sustituir el lector de archivo por
un sidecar consumer (mismo patron que nouser-explorer).
UI:
- Header: path, count total + breakdown seeds/morphisms, reload time.
- Breakdown line: top 5 buckets por frecuencia (entities + morphisms).
- Timeline: tarjetas color-coded por kind (azul=seed, verde=morphism)
con #seq, kind, entity/morphism, id corto, preview de data/params,
schema hash o "(legacy)". Mas-recientes-primero, hasta 200 visibles.
- Error banner si lectura falla; el explorer no crashea, sigue
intentando cada 2s.
Wire: nuevo crates/apps/nakui-explorer/ agregado al workspace. Deps
minimas: nakui-core, gpui, serde_json, uuid (feature serde). Sin
deps de brahman (Nakui standalone).
Tests: 7 unitarios cubriendo load_log (in-order, missing-file),
breakdown (counts + buckets), preview_value (truncate + intact),
short_uuid + short_hash.
Activacion:
NAKUI_EVENT_LOG=/tmp/nakui_inv_xxx.jsonl cargo run -p nakui-explorer
Estado del CHANGELOG global tras este commit: cero pendientes
fundamentados activos. Lo unico que queda es minga-vfs (FUSE,
explicitamente diferido) y mejoras nice-to-have (cobertura adicional
per-lenguaje, daemon-izacion de nakui para sidecar discovery).
Cierra el unico debt estructural detectado en el audit de
independencia: nouser-explorer ya no arrastra nouser-core (que
aportaba notify/walkdir/sled/blake3 al grafo de compilacion de una
UI que solo habla JSON contra un socket).
- Cliente movido: engine_socket::client::list_monads (~60 LOC, std +
serde_json puros) emigra de nouser_core::engine_socket a
nouser_card::query::client. Vive donde viven los wire types,
consistente con el principio "un consumer importa el contrato, no
el runtime del productor".
- Drop dep: nouser-explorer deja de depender de nouser-core.
Verificado con cargo tree: notify, sled, blake3 desaparecen del
grafo del binario.
- Fallback "falla hacia la simplicidad": nueva resolve_socket() en
el explorer intenta primero broker discovery; si el broker no
responde / no hay init vivo, fallback directo al
default_socket_path. El explorer queda funcional contra un daemon
huerfano (standalone sin init) — completa "consciente cuando hay
ecosistema, soberano cuando esta solo".
- socket_source gana tercer estado "default-path" para visibilidad.
Audit estructural confirmo que el resto del ecosistema ya respeta
el principio. Brahman es pegamento opcional, no chasis obligatorio
— y ahora el grafo de Cargo lo enforcea, no solo la convencion.
Tests: 4 + 10 + 27 verdes. Cliente movido ejercitado end-to-end
por los 3 tests integracion de engine_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.
Bin GPUI standalone que consulta brahman-admin cada 2s y renderea
todas las sesiones del Init como cards. Cierra el círculo visual
del ecosistema brahman.
- Crate nuevo crates/apps/nouser-explorer (deps: brahman-admin,
brahman-card, gpui).
- Ventana 900x640 con header del estado del Init, banner de error
cuando no conecta, y lista de cards (una por sesión).
- Cada card muestra: kind + label + lifecycle, ULID corto, summary
(si data), keywords, lens hint, service_socket si está, y refs
(RelationshipKind → target_label). El borde izquierdo coloreado
diferencia ente (azul) de data (lavanda).
- cx.spawn(async move |this, cx| { ... }) corre el loop de refresh
en el GPUI executor; query_blocking porque GPUI no tiene runtime
tokio.
- Helper nuevo en brahman-admin: client::query_blocking(path) —
versión sync de query(), para callers con su propio executor.
Uso:
$ ente-zero & nouser daemon crates/core &
$ cargo run -p nouser-explorer
# ventana 900x640, ~6 cards en vivo, refrescando cada 2s.
cargo check --workspace: 0 errores, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
yahweh-shell se presenta al Init brahman como módulo Widget mediante un
sidecar en thread aparte. La GUI GPUI levanta normalmente; el sidecar
mantiene la sesión brahman en paralelo, desacoplado.
Cambios:
- crates/apps/yahweh-shell/Cargo.toml: deps brahman-card, brahman-handshake,
ulid.
- crates/apps/yahweh-shell/src/brahman_client.rs: thread con tokio
current_thread runtime que arma una Card, llama Client::connect, y
loop de pings cada 30s. Si el Init no está disponible, loggea y
termina — yahweh sigue funcionando standalone.
- crates/apps/yahweh-shell/src/main.rs: brahman_client::spawn() antes
de Application::new(). El spawn no bloquea.
Card declarada por yahweh:
- label: "brahman.ui_engine"
- lifecycle: Widget
- payload: Virtual (yahweh no se inicia desde el Init, se presenta)
- supervision: Delegate
- permissions: filesystem read-write (persiste layout.json), IPC wit-v1
- flow.input: render-data (json)
- flow.output: user-intent (json)
Validación end-to-end:
$ ente-zero &
$ probe → session=...8G, init_attached=true
$ yahweh → [brahman] attached: session=...Y7
Ambos clientes (probe + yahweh sidecar) se registran en el broker del
Init en sesiones distintas. yahweh es el primer módulo "real" — no un
tester — que vive como nodo del fractal mientras corre.
Tests: 27/27 verdes. cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>