1# Changelog — misc
2
3Entradas que no encajan en otro proyecto (chore, ci, gitignore, etc.).
4
5### chore(.gitignore): excluir .claude/ (state local de Claude Code)
6Iter 18. Side cleanup tras debugging: `.claude/` aparecía en
7`git status` cada sesión (contenía `scheduled_tasks.lock` y
8`settings.local.json`, ambos local-only). Excluido para que no se
9commitee accidentalmente y para que `git status` quede limpio.
10
11Investigación previa que motivó el cleanup: persiguiendo un supuesto
12deadlock en `drift_check_surfaces_expected_per_record_diffs` con
13eprintlns/macro de log a archivo en `drift.rs` y `run.rs`. Conclusión:
14no hay deadlock — pasa cleanly aislado, en suite nakui-core, y en
15`cargo test --workspace`. El "hang" original venía de procesos cargo
16y test-binaries huérfanos de sesiones anteriores compitiendo por el
17build lock. Source restaurado, ningún cambio funcional. Memoria
18`project_drift_hang.md` reescrita con el playbook correcto.
19
20### feat(explorer+daemon): discovery dinámico vía broker + query socket
21La UI deja de hardcodear el socket admin: ahora descubre al daemon
22nouser vía `MatchEvent::Available` del broker brahman y le consulta
23sus Mónadas directo, sin pasar por brahman-admin. Cierra el "explorer
24encuentra al daemon de forma totalmente dinámica" del meta-plan.
25
26Pipeline end-to-end:
27- Daemon publica engine Card con `service_socket = $XDG_RUNTIME_DIR/nouser-engine.sock`
28 y `flow.output = monad-list:json`.
29- Daemon binda un Unix socket en ese path y monta un listener
30 blocking que sirve `nouser_card::query::QueryRequest::ListMonads`,
31 responde `ListMonadsResponse { engine, monads: Vec<MonadView> }`.
32- Explorer construye un consumer Card con `flow.input = monad-list:json`
33 vía `brahman_sidecar::build_consumer_card`, llama
34 `await_provider_blocking(card, 3s)` y recibe el socket descubierto.
35- Cachea ese socket; cada poll (2s) llama
36 `nouser_core::engine_socket::client::list_monads(socket, 2s)`.
37 Fallo de query → invalida cache → próximo tick re-descubre.
38
39Wire types nuevos en `nouser_card::query`:
40- `QueryRequest::ListMonads` (single variant por ahora).
41- `ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }`.
42- `MonadView`: proyección slim de `MonadManifest` SIN `centroid` ni
43 `members` — la UI no los necesita y eran KB por Mónada que no
44 tenían por qué viajar cada poll.
45- `transport::default_socket_path()` con env override
46 `NOUSER_ENGINE_SOCKET`.
47- Const `FLOW_MONAD_LIST = "monad-list"`, `FLOW_TYPE_NAME = "json"`.
48
49Listener en `nouser_core::engine_socket`:
50- `spawn_listener(config, db)` arma std::os::unix::net::UnixListener
51 en thread blocking dedicado. Frecuencia esperada (UI cada 2s) no
52 amerita tokio.
53- `client::list_monads(socket, timeout)` — cliente blocking con
54 `QueryError` tipado (Connect / Io / Serde / Daemon / Timeout / Empty).
55- 3 tests integración: roundtrip vacío, Mónadas reales, request
56 inválido devuelve ErrorResponse.
57
58Refactor explorer:
59- Drop dep `brahman-admin`, add deps `brahman-sidecar`, `nouser-card`,
60 `nouser-core`.
61- State: `socket: Option<PathBuf>` cache + `snapshot: Option<ListMonadsResponse>`
62 + `socket_source: "discovery"|"cache"` (sólo informativo).
63- Tick: `tick(prior_socket)` separado del UI, devuelve un enum
64 `TickOutcome::{Ok, DiscoveryFailed, QueryFailed}`. Cualquier
65 fallo invalida la cache → re-discovery automática.
66- Header reformulado: `Engine 'nouser_engine' · N mónada(s) ·
67 socket: /... (cache|discovery) · watching: /tmp/x`.
68- Render pintado de un engine card + Mónadas, sin ya iterar
69 `BrokeredCard` del admin.
70
71Trade-offs aceptados:
72- Polling 2s (no streaming). El broker no empuja cambios de Data
73 cards hoy; agregar streaming requiere extender el protocolo
74 handshake. Para snapshot UI, polling 2s es suficiente.
75- Re-descubrimiento full en cada error de query (en lugar de retry
76 con backoff). Discovery es barato (~ms vs broker), no vale la
77 pena la complejidad.
78
79Tests: 10 (nouser-card, +3 query) + 27 (nouser-core, +3 engine_socket)
80+ 4 (sidecar) verdes. Explorer compila clean.
81
misc.md
Changelog — misc
Entradas que no encajan en otro proyecto (chore, ci, gitignore, etc.).
chore(.gitignore): excluir .claude/ (state local de Claude Code)
Iter 18. Side cleanup tras debugging: .claude/ aparecía en
git status cada sesión (contenía scheduled_tasks.lock y
settings.local.json, ambos local-only). Excluido para que no se
commitee accidentalmente y para que git status quede limpio.
Investigación previa que motivó el cleanup: persiguiendo un supuesto
deadlock en drift_check_surfaces_expected_per_record_diffs con
eprintlns/macro de log a archivo en drift.rs y run.rs. Conclusión:
no hay deadlock — pasa cleanly aislado, en suite nakui-core, y en
cargo test --workspace. El "hang" original venía de procesos cargo
y test-binaries huérfanos de sesiones anteriores compitiendo por el
build lock. Source restaurado, ningún cambio funcional. Memoria
project_drift_hang.md reescrita con el playbook correcto.
feat(explorer+daemon): discovery dinámico vía broker + query socket
La UI deja de hardcodear el socket admin: ahora descubre al daemon
nouser vía MatchEvent::Available del broker brahman y le consulta
sus Mónadas directo, sin pasar por brahman-admin. Cierra el "explorer
encuentra al daemon de forma totalmente dinámica" del meta-plan.
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 un Unix socket en ese path y monta un listener
blocking que sirve
nouser_card::query::QueryRequest::ListMonads,
responde ListMonadsResponse { engine, monads: Vec<MonadView> }.
- Explorer construye un consumer Card con
flow.input = monad-list:json
vía brahman_sidecar::build_consumer_card, llama
await_provider_blocking(card, 3s) y recibe el socket descubierto.
- Cachea ese socket; cada poll (2s) llama
nouser_core::engine_socket::client::list_monads(socket, 2s).
Fallo de query → invalida cache → próximo tick re-descubre.
Wire types nuevos en nouser_card::query:
QueryRequest::ListMonads (single variant por ahora).
ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }.
MonadView: proyección slim de MonadManifest SIN centroid ni
members — la UI no los necesita y eran KB por Mónada que no
tenían por qué viajar cada poll.
transport::default_socket_path() con env override
NOUSER_ENGINE_SOCKET.
- Const
FLOW_MONAD_LIST = "monad-list", FLOW_TYPE_NAME = "json".
Listener en nouser_core::engine_socket:
spawn_listener(config, db) arma std::os::unix::net::UnixListener
en thread blocking dedicado. Frecuencia esperada (UI cada 2s) no
amerita tokio.
client::list_monads(socket, timeout) — cliente blocking con
QueryError tipado (Connect / Io / Serde / Daemon / Timeout / Empty).
- 3 tests integración: roundtrip vacío, Mónadas reales, request
inválido devuelve ErrorResponse.
Refactor explorer:
- Drop dep
brahman-admin, add deps brahman-sidecar, nouser-card,
nouser-core.
- State:
socket: Option<PathBuf> cache + snapshot: Option<ListMonadsResponse>
socket_source: "discovery"|"cache" (sólo informativo).
- Tick:
tick(prior_socket) separado del UI, devuelve un enum
TickOutcome::{Ok, DiscoveryFailed, QueryFailed}. Cualquier
fallo invalida la cache → re-discovery automática.
- Header reformulado:
Engine 'nouser_engine' · N mónada(s) · socket: /... (cache|discovery) · watching: /tmp/x.
- Render pintado de un engine card + Mónadas, sin ya iterar
BrokeredCard del admin.
Trade-offs aceptados:
- Polling 2s (no streaming). El broker no empuja cambios de Data
cards hoy; agregar streaming requiere extender el protocolo
handshake. Para snapshot UI, polling 2s es suficiente.
- Re-descubrimiento full en cada error de query (en lugar de retry
con backoff). Discovery es barato (~ms vs broker), no vale la
pena la complejidad.
Tests: 10 (nouser-card, +3 query) + 27 (nouser-core, +3 engine_socket)
- 4 (sidecar) verdes. Explorer compila clean.