2ae888bc8f
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.
1233 lines
56 KiB
Markdown
1233 lines
56 KiB
Markdown
# Changelog
|
||
|
||
Registro cronológico de cambios sustantivos en el monorepo Brahman. Cada
|
||
entrada lista las acciones concretas tras un commit; para detalles de
|
||
ratio/diff ver `git show <sha>`.
|
||
|
||
## 2026-05-09
|
||
|
||
### 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.
|
||
|
||
### feat(nous-real): cache de embeddings + write-through al CAS de arje
|
||
Cierra el ciclo de la crítica del usuario: "Si un archivo no ha
|
||
cambiado su hash en el CAS, Nouser ni siquiera debería pedirle al
|
||
LLM que re-genere el embedding". El modelo real
|
||
(`fastembed-allMiniLML6V2-384d`, ~1-50ms por archivo) era invocado
|
||
ciegamente en cada re-cluster del watcher. Ahora se cachea por
|
||
`sha256(bytes-vistos) + model_id`.
|
||
|
||
Pipeline en `handle_file`:
|
||
1. Lee primeros 8 KiB (igual que antes).
|
||
2. `file_sha = ente_cas::sha256_of(buf)` — hash de los bytes que el
|
||
modelo *realmente* verá (no del archivo completo). Garantiza
|
||
que un archivo creciendo más allá de la ventana sin tocar la
|
||
cabeza siga sirviendo cache hits.
|
||
3. Cache lookup: HIT → respuesta en ~µs.
|
||
4. MISS → `ente_cas::store(&buf)` (write-through al CAS de arje,
|
||
no-fatal si falla) → `backend.embed_one(text)` → `cache.put(...)`.
|
||
|
||
Backend de cache: sled local en
|
||
`$XDG_CACHE_HOME/brahman/nouser-nous-real-embed-cache.sled`. Tree
|
||
versionado `embed_cache_v1`; el `MODEL_ID` viaja en la key, así que
|
||
cambiar de modelo invalida el cache implícitamente. Override por env
|
||
`NOUSER_NOUS_REAL_CACHE`.
|
||
|
||
Encoding compacto: cada `Vec<f32>` se serializa como bytes
|
||
little-endian (4B por f32, sin overhead). Para el modelo default
|
||
(384-d) son 1.5 KiB por entry. Decode tolera bytes corruptos
|
||
(longitud no-múltiplo de 4 → `None`, no panic).
|
||
|
||
Por qué sled y no `ente-cas` directo: el CAS de arje es flat
|
||
sha256-keyed; la cache necesita un mapeo `(file_sha, model_id) →
|
||
embedding`, no expresable como entry CAS. El write-through a CAS
|
||
queda como registro consultable + futura GC.
|
||
|
||
API:
|
||
- `EmbedCache::open()` → abre sled, idempotente.
|
||
- `EmbedCache::open_at(dir)` para tests.
|
||
- `EmbedCache::get(sha, model)` → `Option<Vec<f32>>`.
|
||
- `EmbedCache::put(sha, model, &[f32])` → no-fatal en error.
|
||
- `EmbedCache::len()` → contador para logs (best-effort).
|
||
|
||
Mock NO se modifica — su embedding pseudo-32d es metadata-hashing
|
||
puro, sin costo. Cachearlo sería overhead.
|
||
|
||
Tests: 5 unitarios (`roundtrip_returns_same_vector`, `miss_returns_none`,
|
||
`different_models_do_not_collide`, `different_content_different_keys`,
|
||
`corrupted_value_returns_none`). Verdes con `--features embeddings`;
|
||
stub mode (sin feature) sigue compilando sin tocar cache.
|
||
|
||
### chore(nakui): alinear `nakui-core` con `[workspace.package]` y deps compartidas
|
||
Cleanup de drift de convenciones: `nakui-core` era el único crate del
|
||
monorepo que mantenía `version = "0.1.0"` / `edition = "2021"` /
|
||
`thiserror = "1"` hardcoded, mientras el resto heredaba del workspace
|
||
y usaba `thiserror = "2"`. Eso significaba que un bump global de versión
|
||
o de edition se olvidaba sistemáticamente de nakui.
|
||
|
||
Cambios:
|
||
- `[package]`: `version`, `edition`, `rust-version`, `license`, `authors`,
|
||
`publish` → todos `*.workspace = true`. Agregado `description` (cumple
|
||
convención del resto de crates).
|
||
- Deps compartidas migradas a `{ workspace = true }`: serde, serde_json,
|
||
thiserror (v1→v2), tokio, ulid, sha2.
|
||
- `uuid` migrado a `{ workspace = true, features = ["serde"] }` — la
|
||
feature `serde` no está en el workspace dep porque nakui es el único
|
||
user; queda local opt-in en lugar de inflar el dep común.
|
||
- Deps específicas de nakui (sin compartición posible): rhai, petgraph,
|
||
surrealdb permanecen inline con versión local.
|
||
|
||
Verificación: `cargo build -p nakui-core` verde tras el bump de
|
||
`thiserror` v1→v2 — el `#[derive(Error)]` de los 14+ enums de error
|
||
en nakui no requirió ajustes (la API de derive es backwards-compatible
|
||
para los patrones simples). `cargo test -p nakui-core --lib`: 27/27
|
||
verdes, sin regresión.
|
||
|
||
### feat(card): `Card::new(label)` — alternativa segura a `Default::default()`
|
||
Cierra la trap documentada de `Card::default()` que devuelve `id =
|
||
Ulid::nil()`. Usar `Card::default()` "viva" colisionaba con cualquier
|
||
otra Card default-construida bajo el mismo id `00000000…`. La fix no
|
||
es romper `Default` (sigue siendo determinista, requerido por callers
|
||
que lo usan como template para deserialización), sino agregar un
|
||
constructor explícito:
|
||
|
||
let card = Card {
|
||
kind: CardKind::Data,
|
||
payload: Payload::Embedded(json),
|
||
..Card::new("mi-modulo.algo")
|
||
};
|
||
|
||
`Card::new(label)` asigna `id = Ulid::new()` (único) + `label`
|
||
provisto, dejando el resto en defaults seguros (Virtual / OneShot /
|
||
Ente). Pensado para usarse en struct-literals con override parcial,
|
||
igual sintaxis que el patrón viejo pero sin la trap.
|
||
|
||
Refactor de call sites:
|
||
- `brahman_sidecar::discovery::build_consumer_card` → `..Card::new(label)`
|
||
- `nouser daemon::build_engine_card` → `..Card::new("brahman.nouser_engine")`
|
||
|
||
`Default` se mantiene tal cual con docstring expandida que advierte
|
||
explícitamente sobre el uso "vivo" y apunta a `Card::new`. Tests
|
||
existentes y el patrón `nouser_card::MonadManifest::to_brahman_card`
|
||
(que asigna el id estable de la Mónada, no uno fresco) NO se
|
||
modifican — `Default` sigue siendo correcto cuando el caller
|
||
sobreescribe `id` explícitamente.
|
||
|
||
Tests: 3 unitarios nuevos en brahman-card (`new_assigns_real_ulid_and_label`,
|
||
`new_yields_distinct_ids_per_call`, `default_keeps_nil_id_for_struct_update_pattern`).
|
||
15 tests verdes (era 12).
|
||
|
||
### 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
|
||
quedarse con manifests congelados al arranque.
|
||
|
||
$ nouser daemon /tmp/x &
|
||
$ touch /tmp/x/src/a.rs /tmp/x/src/b.rs /tmp/x/src/c.rs
|
||
# daemon log (un solo batch, no 9 reacciones):
|
||
[watcher] ⚙ batch: 6 path(s) coalescidos → re-scan
|
||
[watcher] ✦ x/src nace (3 miembros, lens=Code)
|
||
[watcher] ⌃ delta: 1 nuevas, 0 refrescadas, 0 cerradas — 3 sesiones vivas
|
||
|
||
Mecánica del debounce (150ms):
|
||
- `spawn_fs_watcher` arma dos threads: **dispatcher** filtra eventos
|
||
notify Create/Modify/Remove a un canal de paths; **coordinator**
|
||
mantiene `HashMap<PathBuf, Instant>` y dispara batch sólo cuando
|
||
todos los paths llevan ≥150ms quietos.
|
||
- Un `:w` típico de vim (~5 eventos por archivo) colapsa a 1 batch.
|
||
|
||
Mecánica del re-publish:
|
||
- `SidecarPool` ahora trackea `HashMap<Ulid, AbortHandle>` indexado
|
||
por `Card.id`. Llamar `pool.spawn(card)` con un id ya presente
|
||
aborta la sesión previa y abre una nueva — `spawn` se vuelve
|
||
idempotente: re-publicar una Mónada cuya composición cambió
|
||
refresca su sesión en el broker sin dejar zombies.
|
||
- Nueva API `pool.drop_session(id)` para cerrar una sesión
|
||
explícitamente cuando una Mónada desaparece (directorio quedó
|
||
bajo `min_files` o se borró).
|
||
- `pool.live_sessions()` para introspección/logs.
|
||
- `process_change_batch` re-scanea + re-clusteriza con hidratación,
|
||
diffea contra prior_monads, y para cada Mónada decide:
|
||
- removida → `drop_session`
|
||
- nueva → `spawn` con ✦
|
||
- composición cambió (members o centroid distintos) → `spawn` con ↻
|
||
- idéntica → no-op
|
||
|
||
Trade-off aceptado: re-scan global por batch (no incremental). Es
|
||
O(N archivos) por evento y para árboles típicos (<10k) cae en
|
||
<100ms. Optimizar a re-cluster parcial cuando duela.
|
||
|
||
Tests: workspace completo verde.
|
||
|
||
### feat(nouser): notify watcher — el sistema reacciona en tiempo real
|
||
El daemon ahora monta un `notify::recommended_watcher` recursivo
|
||
sobre el directorio. Cada `Create`/`Modify` de archivo regular
|
||
dispara: embedding del archivo, filtro por `centroid_model`, ranking
|
||
contra centroides existentes, log con marker 🧲 / · según supere
|
||
el umbral de atracción.
|
||
|
||
$ nouser daemon /tmp/x &
|
||
# en otra terminal:
|
||
$ vim /tmp/x/src/nuevo.rs
|
||
# daemon log:
|
||
[watcher] 🧲 /tmp/x/src/nuevo.rs → x/src (0.7470)
|
||
|
||
$ echo "edit" >> /tmp/x/docs/n1.md
|
||
[watcher] 🧲 /tmp/x/docs/n1.md → x/docs (0.8169)
|
||
|
||
Mecánica:
|
||
- DB pasa a `Arc<Mutex<MonadDb>>` para sharing con el thread del
|
||
watcher.
|
||
- Watcher en thread dedicado (`nouser-watcher`); reacciona sólo a
|
||
Create/Modify, ignora Access/Metadata-only.
|
||
- `react_to_change(path, metadata, db)` computa embedding,
|
||
filtra por `centroid_model`, busca best attraction.
|
||
- No re-publica al broker ni muta DB — sólo observa y narra. La
|
||
invalidación selectiva (re-cluster + replace_monads + diff
|
||
publish) queda como work futuro.
|
||
|
||
Limitación conocida: `notify` emite múltiples eventos por una sola
|
||
edición (Create + Modify, etc.). Sin debounce, el watcher reporta
|
||
varias veces. Aceptable para demo; production conviene debounce
|
||
~100ms por path.
|
||
|
||
Tests: 7 (card) + 24 (core) verdes, 0 errores, 0 warnings.
|
||
|
||
### feat(nouser): hidratación del daemon vía sled + path_hint
|
||
El daemon ya no recomputa ciegamente al arrancar. Si la DB tiene
|
||
Mónadas previas con `centroid_model` válido, las publica instantáneo
|
||
y el re-scan reusa sus IDs vía `path_hint`.
|
||
|
||
Schema:
|
||
- `MonadManifest.path_hint: Option<String>` — identidad estable
|
||
derivada del origen (para `by_directory`, el parent dir
|
||
canónico). Permite reusar ULID across re-scans.
|
||
|
||
Algoritmo (cluster):
|
||
- Nueva fn `cluster::by_directory_hydrated(files, min_files,
|
||
prior: Option<&MonadDb>)`. Cuando hay `prior`, busca Mónada con
|
||
mismo `path_hint` Y mismo `centroid_model`; si la encuentra,
|
||
reusa `id`, `lineage` y `created_at_ms`.
|
||
- `by_directory` queda como wrapper sin hidratación (back-compat).
|
||
|
||
Daemon (cmd_daemon):
|
||
1. Open sled si NOUSER_DB_PATH existe.
|
||
2. Publica las Mónadas previas con `centroid_model` válido (las
|
||
inválidas se descartan con log explícito).
|
||
3. Re-scan + `by_directory_hydrated(prior=&db)`.
|
||
4. Sólo spawnea sidecars para Mónadas con id que NO estaba en la
|
||
hidratación inicial. Los path_hints existentes preservan identidad,
|
||
evitando duplicados en el broker.
|
||
5. Persiste el set actualizado.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core
|
||
# arranque 1: DB vacía
|
||
re-scan 102 archivos → 5 mónadas
|
||
1 ente + 5 mónadas vivas (5 nuevas vs hidratación)
|
||
|
||
$ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core
|
||
# arranque 2: DB poblada
|
||
hidratadas 5 mónadas previas en O(1)
|
||
re-scan 102 archivos → 5 mónadas
|
||
1 ente + 5 mónadas vivas (0 nuevas vs hidratación)
|
||
|
||
Costo del arranque 2: ~0.06s user CPU. Antes (sin hidratación) era
|
||
re-scan + cluster + spawn x N — segundos enteros para árboles grandes.
|
||
|
||
Tests: 7 (card) + 24 (core) verdes.
|
||
|
||
### feat(nouser): centroid_model — versionado de embeddings
|
||
Protege contra el bug silencioso de mezclar centroides de modelos
|
||
distintos (mock 32-d vs real 384-d), que daba scores sin sentido.
|
||
|
||
- `MonadManifest.centroid_model: Option<String>` taggea qué modelo
|
||
produjo el `centroid`. `None` = legacy pre-versioning.
|
||
- `nouser_core::embed::MODEL_ID = "nouser-pseudo-32d"`. El cluster lo
|
||
setea en cada Mónada que genera.
|
||
- `nouser-nous-mock` reusa la misma constante (`use
|
||
nouser_core::embed::MODEL_ID`); produce vectores idénticos al
|
||
cluster local, así que reportar el mismo ID es honesto.
|
||
- `nouser-nous-real` reporta `"real-fastembed-allMiniLML6V2-384d"`
|
||
(dim distinta, semántica distinta).
|
||
- `cmd_attract` ahora:
|
||
- Captura el `model_id` del embedding del target (local o remote).
|
||
- Filtra Mónadas cuyo `centroid_model` no matchee.
|
||
- Reporta `embed: <source> (<model>)` y `skipped: N mónadas con
|
||
centroid_model distinto` cuando descarta.
|
||
|
||
Resultado operativo: cambiar de mock a real (vía
|
||
`BRAHMAN_BROKER_CONTEXT=prod`) hace que `attract` filtre las Mónadas
|
||
viejas con cero score en lugar de fingir que las puede comparar.
|
||
|
||
## 2026-05-08
|
||
|
||
### chore: profile.dev slim — target/ ~50% más liviano
|
||
Cambios en `[profile.dev]` raíz para que builds futuras no desborden
|
||
disco. Decisiones:
|
||
- `debug = "line-tables-only"`: stack traces correctos, drop del resto
|
||
de symbols. Sin pérdida real para nuestro flujo.
|
||
- `split-debuginfo = "unpacked"`: relink más rápido, debuginfo en
|
||
archivos aparte.
|
||
- `codegen-units = 256`: paralelismo + builds incrementales chicas.
|
||
- Override `[profile.dev.package.X]` para los pesados (gpui, ort,
|
||
fastembed, tokenizers, image): `opt-level = 1`, `debug = false`.
|
||
No los debuggeamos línea por línea, no necesitan info pesada.
|
||
|
||
Resultado: binarios ~3× más livianos. ente-zero 125→47 MB; mock-nous
|
||
~50→22 MB.
|
||
|
||
### feat(nouser): dynamic binding — consumer descubre el provider vía broker
|
||
Cierra el bucle prometido por `priority_contexts`: el cliente ya no
|
||
hardcodea el socket del provider de embeddings. En su lugar:
|
||
|
||
1. Si `NOUSER_NOUS_SOCKET` está set, lo usa directo (atajo explícito).
|
||
2. Si no, abre `brahman_handshake::client::Client` al `brahman-init`,
|
||
anuncia un consumer Card mínimo con `flow.input = embed-result:json`,
|
||
espera 3s por el primer `MatchEvent::Available`, y usa el
|
||
`producer_service_socket` que viaja en el evento.
|
||
|
||
Esto activa el swap automático mock↔real:
|
||
- `BRAHMAN_BROKER_CONTEXT=test`: el bias `+1 en test` del mock lo hace
|
||
ganar; consumer recibe el socket del mock.
|
||
- `BRAHMAN_BROKER_CONTEXT=prod`: el bias del real lo hace ganar.
|
||
- Sin contexto: empate alfabético entre los presentes.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ ente-zero & nouser-nous-mock &
|
||
$ # Sin NOUSER_NOUS_SOCKET:
|
||
$ nouser attract --remote crates/core archivo.rs
|
||
embed: remote
|
||
🧲 0.9058 ente-brain/src ...
|
||
(mock log confirma "embed_file path=...")
|
||
|
||
Cambios:
|
||
- `nouser-core` Cargo.toml: deps directas brahman-handshake + tokio.
|
||
- `cmd_attract` resuelve el socket por discovery antes de llamar a
|
||
`embed_via(&path, file)` (mini-runtime tokio current_thread inline).
|
||
|
||
Bug que se descubrió en el camino: la "flakiness" reportada de
|
||
`cargo test --workspace` era disco lleno (24 GB en `target/`), no
|
||
condición de carrera. Con `cargo clean` + profile slim, todos los
|
||
tests pasan deterministas.
|
||
|
||
### feat(nouser): yahweh widget — `nouser-explorer` panel GPUI
|
||
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 900×640 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` se usa porque GPUI no provee
|
||
un runtime tokio.
|
||
- Nuevo helper 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 muestra ~6 cards en vivo, refrescando cada 2s.
|
||
|
||
cargo check --workspace: 0 errores, 0 warnings.
|
||
|
||
### feat(nouser): persistencia sled write-through del MonadDb
|
||
`MonadDb` ahora soporta backend dual:
|
||
|
||
- `MonadDb::new()` → memoria pura (default, back-compat).
|
||
- `MonadDb::open(path)` → sled-backed con cache en memoria. Carga
|
||
contenido existente al abrir; cada `insert_*` hace write-through
|
||
(cache + sled).
|
||
|
||
Diseño:
|
||
- 2 trees sled: `files` y `monads`.
|
||
- Wire format: serde_json (ergonomía + inspectability con sled-cli;
|
||
los manifests son chicos, JSON gana sobre postcard aquí).
|
||
- Reads SIEMPRE desde la cache — sled se consulta sólo al abrir.
|
||
- `replace_monads()` purga el tree de sled antes de escribir.
|
||
|
||
Bin nouser: nueva env var `NOUSER_DB_PATH`. Si está set, persiste
|
||
en esa ruta; si no, in-memory:
|
||
|
||
$ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core
|
||
scan: 102 archivos en crates/core, 5 mónadas
|
||
$ ls /tmp/monads.sled
|
||
blobs conf
|
||
$ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core
|
||
# segunda corrida re-escribe la DB con el nuevo scan
|
||
|
||
Tests nuevos en db.rs:
|
||
- `persistence_roundtrip` — escribe, cierra, reabre, datos están.
|
||
- `replace_monads_purges_persistent_tree` — replace limpia el tree.
|
||
|
||
24 tests en nouser-core (era 22, +2).
|
||
|
||
### feat(sidecar): Phase B-3 — SidecarPool consolida en un runtime
|
||
Antes: cada `spawn(card)` creaba un thread + tokio runtime propio.
|
||
Para módulos que publican muchas sesiones (nouser daemon con 50+
|
||
Mónadas) eso es 50 threads + 50 runtimes. Ahora: **un thread + un
|
||
runtime tokio current_thread** que hostea N tasks de sidecar.
|
||
|
||
API nueva (aditiva, no rompe `spawn`/`spawn_with_handle`):
|
||
|
||
let pool = SidecarPool::new()?;
|
||
pool.spawn(card1);
|
||
pool.spawn(card2);
|
||
pool.spawn_conscious(card_wit, wit);
|
||
pool.spawn_with_config(SidecarConfig::new(c).with_wit(w));
|
||
// pool drop = todas las sesiones cierran.
|
||
|
||
`run_client` se hace pública para que el pool pueda enqueuar tasks
|
||
externos al runtime con `handle.spawn(run_client(config))`.
|
||
|
||
`nouser daemon` migrado al pool. Verificación con `ps -L`:
|
||
|
||
$ ps -L -p $(pidof nouser)
|
||
LWP CMD
|
||
28817 nouser # main thread
|
||
28819 brahman-sidecar # pool thread (todas las sesiones)
|
||
|
||
Antes serían 6+ LWP (1 main + N sesiones); ahora 2 fijos sin importar
|
||
cuántas Mónadas se publiquen.
|
||
|
||
### feat: Crossreferencia — Card.references como grafo del fractal
|
||
Las Cards ahora declaran sus relaciones con otras Cards. El Engine
|
||
posee Mónadas; las Mónadas declaran que son poseídas por el Engine.
|
||
La UI puede cruzar el grafo sin discovery especial.
|
||
|
||
- `brahman-card`:
|
||
- `RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }`.
|
||
- `CardReference { kind, target_id, target_label }` — `target_label`
|
||
es cache del label en el momento de declarar (la UI puede pintar
|
||
sin resolver).
|
||
- `Card.references: Vec<CardReference>` y espejo en `WireCard`.
|
||
Conversiones `From` propagan.
|
||
- `brahman-broker::BrokeredCard` propaga `references`.
|
||
- `brahman-status` imprime cada referencia: `ref OwnedBy → label (id)`.
|
||
- **nouser daemon**: cada Mónada que publica añade
|
||
`RelationshipKind::OwnedBy` apuntando al engine. La declaración es
|
||
unilateral; el engine no necesita conocer las IDs de antemano.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ ente-zero & nouser daemon crates/core
|
||
$ brahman-status
|
||
Sessions (6):
|
||
[ente] ... brahman.nouser_engine
|
||
[data] ... brahman-handshake/src
|
||
ref OwnedBy → brahman.nouser_engine (01K...)
|
||
summary: 6 archivos...
|
||
[data] ... ente-brain/src
|
||
ref OwnedBy → brahman.nouser_engine (01K...)
|
||
...
|
||
|
||
### feat: Phase D-3 + D-4 — service_socket en Card, providers coexisten
|
||
Cierra el ciclo del swap automático de Nous (mock↔real):
|
||
|
||
- **Schema** (`brahman-card`): `Card.service_socket: Option<PathBuf>` y
|
||
espejo en `WireCard`. Conversiones `From` propagan. Es el path del
|
||
**data plane** (distinto del socket del Init); cualquier consumer
|
||
que matchee con esta Card puede conectar directo sin discovery
|
||
adicional.
|
||
- **Broker** (`brahman-broker`): `BrokeredCard` propaga
|
||
`service_socket` desde la Card. Sin participación en el matching —
|
||
sólo metadata para los observadores.
|
||
- **MatchEvent** (`brahman-handshake`): nuevo campo
|
||
`producer_service_socket: Option<PathBuf>`. Cuando el server emite
|
||
`Available`, busca la `BrokeredCard` del productor en el broker y
|
||
copia su `service_socket`. El consumer recibe la ruta completa para
|
||
conectar.
|
||
- **Transport** (`nouser-nous`): `provider_socket_path(provider: &str)`
|
||
devuelve `nouser-nous-{provider}.sock` por default — mock y real
|
||
coexisten en sockets distintos (Phase D-4). `default_socket_path()`
|
||
conserva el comportamiento single-provider.
|
||
- **Providers**: mock declara `service_socket =
|
||
/run/user/X/nouser-nous-mock.sock`; real declara
|
||
`nouser-nous-real.sock`. La Card se construye DESPUÉS del bind para
|
||
que el path declarado sea el real.
|
||
- **Status**: `brahman-status` imprime `socket:` por sesión cuando
|
||
está presente.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ ente-zero & nouser-nous-mock & nouser-nous-real &
|
||
$ ls /run/user/1001/nouser-nous-*.sock
|
||
nouser-nous-mock.sock
|
||
nouser-nous-real.sock
|
||
|
||
$ brahman-status
|
||
Sessions (2):
|
||
[ente] ... nouser.nous_real
|
||
socket: /run/user/1001/nouser-nous-real.sock
|
||
in embed-request: Primitive { name: "json" }
|
||
out embed-result: Primitive { name: "json" }
|
||
[ente] ... nouser.nous_mock
|
||
socket: /run/user/1001/nouser-nous-mock.sock
|
||
in embed-request, out embed-result
|
||
|
||
Pendientes para futuro (no críticos):
|
||
- nouser-core attract --remote todavía usa NOUSER_NOUS_SOCKET hardcoded
|
||
o `default_socket_path()`. El siguiente paso es subscribirse al
|
||
MatchEvent del broker y usar `producer_service_socket` directo —
|
||
con eso `BRAHMAN_BROKER_CONTEXT=test/prod` swapea provider sin
|
||
tocar al consumer.
|
||
|
||
### refactor(nouser): labels de Mónada con 2 componentes del path
|
||
Resuelve la fricción visual de monorepos donde múltiples Mónadas se
|
||
llamaban "src". Nueva función `label_from_path` toma los últimos hasta
|
||
2 componentes normales del path y los une con `/`.
|
||
|
||
$ nouser scan crates/core
|
||
[01K..] brahman-admin/src card=5
|
||
[01K..] brahman-handshake/src card=6
|
||
[01K..] ente-brain/src card=11
|
||
[01K..] ente-kernel/src card=4
|
||
...
|
||
|
||
Tests añadidos: `label_from_root_only_one_component`,
|
||
`label_from_deep_path_takes_last_two`. Tests existentes actualizados
|
||
con los nuevos labels.
|
||
|
||
### feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature flag
|
||
Cierra el ciclo del módulo Nous: existe un proveedor que produce
|
||
embeddings reales con un modelo LLM, mientras que `cargo build` sin
|
||
features sigue siendo liviano (no descarga ni compila ML deps).
|
||
|
||
Crate nuevo:
|
||
|
||
- `crates/modules/nouser/nous-real`: bin con dos modos según feature.
|
||
- **Sin feature (default)**: stub. Bin compila en ~10s, arranca,
|
||
sidecarea a brahman-init declarando la Card de real-nous, escucha
|
||
en el socket Nous, y rechaza toda request con `ErrorResponse {
|
||
error: "compilado sin la feature embeddings. Rebuild con
|
||
cargo build -p nouser-nous-real --features embeddings" }`.
|
||
`cargo build --workspace` sigue siendo limpio.
|
||
- **Con `--features embeddings`**: pulls `fastembed = "4"`. Ese crate
|
||
arrastra `ort 2.0.0-rc.9` (ONNX Runtime con binarios descargados
|
||
por Cargo) + `tokenizers 0.21` + ~30 deps más. Compila en ~50s.
|
||
Modelo default: `all-MiniLM-L6-v2` (384-d, descargado a
|
||
`~/.cache/fastembed` la primera vez).
|
||
- `EmbedText`: pasa el texto al modelo, devuelve vector 384-d.
|
||
- `EmbedFile`: lee primeros 8KiB con UTF-8 lossy, embed como texto.
|
||
Para binarios el resultado no es semánticamente útil — caller
|
||
decide.
|
||
- `Ping`: devuelve `model_id` y `embed_dim` reales.
|
||
|
||
- Card de real-nous:
|
||
- label `nouser.nous_real` (distinto del mock para coexistir).
|
||
- `priority_contexts.prod = { priority_offset: +1 }`. En contexto
|
||
prod gana sobre el mock; en `test` el mock gana por su propio
|
||
`+1`. Sin contexto activo, empate alfabético entre ambos.
|
||
|
||
Validación end-to-end con modelo real:
|
||
|
||
$ cargo build -p nouser-nous-real --features embeddings # ~50s
|
||
$ ente-zero & nouser-nous-real &
|
||
$ # probe vía python al socket Unix:
|
||
$ echo '{"kind":"embed_text","payload":{"text":"hello brahman"}}' \
|
||
| python3 -c "..." | head
|
||
model: real-fastembed-allMiniLML6V2-384d
|
||
elapsed_ms: 8
|
||
embed_dim: 384
|
||
first 5 values: [0.0034, -0.0036, 0.0078, -0.0218, -0.0162]
|
||
|
||
Tradeoff conocido: las dimensiones del mock (32-d) y real (384-d) son
|
||
incompatibles. Cambiar de proveedor invalida los centroides cacheados
|
||
de Mónadas. Documentar como "limpiar DB al cambiar proveedor".
|
||
|
||
Workspace state:
|
||
- cargo build --workspace sigue limpio sin features (no ML).
|
||
- cargo build -p nouser-nous-real --features embeddings funciona.
|
||
- 0 errores, 0 warnings en ambos modos.
|
||
|
||
Pendientes para D-3 / futuro:
|
||
- Discovery de socket: hoy el consumer hardcodea NOUSER_NOUS_SOCKET.
|
||
Para que el broker brahman elija real vs mock per-contexto, falta
|
||
inyectar el socket del provider electo en el MatchEvent o exponer
|
||
un broker query "dame el socket de la sesión X".
|
||
- Coexistencia: hoy los dos providers compiten por el mismo socket
|
||
path por default. Habría que parametrizarlos a sockets distintos
|
||
cuando coexistan.
|
||
|
||
### feat(nouser): Phase D — proveedor Nous mock + cliente remoto
|
||
Cierra el patrón "Nous como módulo aparte intercambiable": el contrato
|
||
del proveedor de embeddings vive en su crate, el mock determinístico
|
||
implementa ese contrato sirviéndolo por Unix socket, y `nouser-core`
|
||
sabe consumirlo remotamente. El switch entre mock y real (futuro) se
|
||
hará vía priority_contexts en el broker.
|
||
|
||
Crates nuevos:
|
||
|
||
- `crates/modules/nouser/nous`: contrato compartido. Tipos
|
||
`EmbedRequest`, `RequestKind { EmbedFile, EmbedText, Ping }`,
|
||
`EmbedFilePayload`, `EmbedTextPayload`, `EmbedResponse`,
|
||
`PingResponse`, `ErrorResponse`. Wire format: line-delimited JSON
|
||
por Unix socket, single-shot per conexión. Constants para los nombres
|
||
de flow (`embed-request`/`embed-result`) y el tipo (`json`). Helper
|
||
`transport::default_socket_path()` con env var
|
||
`NOUSER_NOUS_SOCKET`.
|
||
- `crates/modules/nouser/nous-mock`: bin `nouser-nous-mock`. Sidecarea
|
||
a brahman-init con Card kind=Ente declarando los flows
|
||
`embed-request:json`/`embed-result:json` y un
|
||
`priority_contexts.test = { priority_offset: +1 }` (gana sobre
|
||
cualquier real-nous en contexto test). Bind del socket Nous, accept
|
||
loop, despacha por `RequestKind`. EmbedFile usa
|
||
`nouser_core::embed::embed` (los pseudo-embeddings de Phase C).
|
||
Modelo: `mock-pseudo-32d`.
|
||
|
||
Cambios:
|
||
|
||
- `nouser-core`: dep nueva `nouser-nous`. Subcomando `attract` ahora
|
||
acepta `--remote` que abre un socket UnixStream blocking, envía un
|
||
`EmbedRequest` y lee la response. Imprime `embed: local|remote`
|
||
para que se vea cuál ruta corrió.
|
||
|
||
Validación end-to-end (un solo terminal, varios procesos):
|
||
|
||
$ ente-zero &
|
||
$ nouser-nous-mock &
|
||
$ NOUSER_MIN_FILES=5 nouser daemon crates/core &
|
||
$ brahman-status
|
||
|
||
Sessions (7):
|
||
[ente] nouser.nous_mock flows: embed-request, embed-result
|
||
[ente] brahman.nouser_engine
|
||
[data] src summary: 6 archivos en crates/core/brahman-handshake/src
|
||
[data] graph summary: 7 archivos en crates/core/ente-zero/src/graph
|
||
...
|
||
|
||
$ nouser attract --remote crates/core <archivo.rs>
|
||
embed: remote
|
||
🧲 0.9058 src ...
|
||
|
||
Mock log: "embed_file path=crates/modules/nouser/core/src/embed.rs"
|
||
|
||
Bug encontrado y corregido en el camino:
|
||
- `ContextBias` tenía `#[serde(skip_serializing_if = ...)]` en sus
|
||
campos. Postcard NO soporta skip-condicional (formato no
|
||
self-describing): el serializer omitía bytes que el deserializer
|
||
esperaba, rompiendo la wire de cualquier Card con
|
||
`priority_contexts` poblada.
|
||
- Fix: removidos los `skip_serializing_if` de `ContextBias`. JSON
|
||
pretty ahora emite `{"pin_to": null, "priority_offset": 0}` en lugar
|
||
de objeto vacío. Trade-off aceptado por compatibilidad de wire.
|
||
- Test nuevo en brahman-card: `wirecard_postcard_with_priority_contexts`
|
||
que ejercita el roundtrip completo postcard.
|
||
|
||
Tests acumulados: 75 (card 12 +1 nuevo, broker 15, handshake 9,
|
||
card-wit 4, admin 0, nouser-card 7, nouser-core 20, nouser-nous 2).
|
||
cargo check --workspace: 0 errores, 0 warnings.
|
||
|
||
Próximo natural: Phase D-2 — `real-nous` con un modelo ONNX/Llama de
|
||
text-embedding. La infraestructura ya está lista: declara la misma
|
||
Card con `priority_contexts.prod = { priority_offset: +1 }` y el
|
||
swap es transparente para el consumer.
|
||
|
||
### feat(nouser): Phase C — pseudo-embeddings + atracción por centroide
|
||
El "imán semántico" matemático del diseño Kairos, sin LLM. Cada
|
||
archivo se proyecta a un vector 32-d derivado de sus metadatos; cada
|
||
Mónada calcula su centroide; archivos nuevos se asignan por cosine
|
||
similarity contra los centroides existentes.
|
||
|
||
Cambios:
|
||
|
||
- nouser-core dep nueva: `blake3` (hash determinista de strings).
|
||
- `crates/modules/nouser/core/src/embed.rs`:
|
||
- `EMBED_DIM = 32`. Estructura del vector:
|
||
- dims 0..8: blake3(extension) → identidad de tipo
|
||
- dims 8..16: blake3(parent_dir) → identidad de contenedor
|
||
- dims 16..24: blake3(file_stem) → identidad léxica
|
||
- dims 24..28: tamaño (log + flags)
|
||
- dims 28..32: mtime (escala día + cíclicas)
|
||
- **Tip clave**: bytes del hash se centran a `[-1, 1]` (no `[0, 1]`).
|
||
Sin centrar, dos vectores hash random tendrían cosine ~0.75
|
||
espuria; centrados, expectativa ≈ 0 entre no-relacionados.
|
||
- APIs: `embed`, `cosine_similarity`, `centroid`, `cohesion`,
|
||
`attraction_score`, `best_attraction`. `DEFAULT_ATTRACTION_THRESHOLD = 0.7`.
|
||
- `cluster::by_directory` ahora computa el centroide de cada Mónada
|
||
(promedio de embeddings de los miembros, L2-normalizado) y lo guarda
|
||
en `MonadManifest.centroid`. El centroide viaja al brahman-status vía
|
||
`DataFacet.centroid` → ahora se ven los Vec<f32> reales por cada Mónada.
|
||
- bin nouser nuevo subcomando: `attract <dir> <file>`.
|
||
- Escanea el dir, embeda el archivo objetivo, ranking de afinidad
|
||
contra todas las Mónadas con centroide.
|
||
- Marca 🧲 si la mejor supera el umbral, `·` si es la mejor pero
|
||
debajo, espacio en blanco para el resto.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ nouser attract crates/core crates/modules/nouser/core/src/embed.rs
|
||
ranking de atracción (cosine similarity):
|
||
🧲 0.9058 [01K..] src (11 archivos en crates/core/ente-brain/src)
|
||
0.8984 [01K..] src (6 archivos en crates/core/brahman-handshake/src)
|
||
0.8918 [01K..] src (5 archivos en crates/core/ente-zero/src)
|
||
...
|
||
|
||
$ nouser attract crates/core crates/modules/nouser/core/Cargo.toml
|
||
ranking:
|
||
0.3427 [01K..] graph (7 archivos en crates/core/ente-zero/src/graph)
|
||
...
|
||
(mejor score 0.3427 < umbral 0.7000 — el archivo no se 'pega')
|
||
|
||
Tests: 20 en nouser-core (era 13, +7 de embed). Total acumulado: 73
|
||
(card 11, broker 15, handshake codec+tr 2 + integ 7, card-wit 4,
|
||
admin 0, nouser-card 7, nouser-core 20, ente-card 0).
|
||
cargo check --workspace: 0 errores, 0 warnings.
|
||
|
||
Próximo: **Phase D** — `nouser-nous`, módulo aparte para LLM real.
|
||
Mock-nous determinista (basado en estos pseudo-embeddings) en
|
||
`BRAHMAN_BROKER_CONTEXT=test`; real-nous en `prod`. El switch lo hace
|
||
el broker via priority_contexts sin tocar nada más.
|
||
|
||
### feat(nouser): Phase B-2 — daemon que publica Mónadas al Init
|
||
Cierra la unificación: el `nouser daemon` se sidecarea como Ente y
|
||
publica cada Mónada como su propia sesión Data. Un solo
|
||
`brahman-status` muestra procesos y datos en la misma lista, exactamente
|
||
como buscaba el diseño.
|
||
|
||
Cambios:
|
||
|
||
- `crates/modules/nouser/core/Cargo.toml`: deps nuevas `brahman-card`
|
||
y `brahman-sidecar`.
|
||
- `crates/modules/nouser/core/src/bin/nouser.rs`: subcomando
|
||
`daemon <dir>`.
|
||
- Spawna un sidecar para el "engine" (`brahman.nouser_engine`,
|
||
kind=Ente) — el ser que produce y administra Mónadas.
|
||
- Scan + cluster del dir.
|
||
- Para cada Mónada, llama `monad.to_brahman_card()` y spawnea un
|
||
sidecar (kind=Data). Cada Mónada es una sesión brahman propia
|
||
con su ULID estable.
|
||
- Park del thread principal: los sidecars siguen pingueando.
|
||
|
||
Validación end-to-end:
|
||
|
||
$ ente-zero &
|
||
$ NOUSER_MIN_FILES=5 nouser daemon crates/core &
|
||
$ brahman-status
|
||
|
||
Sessions (6):
|
||
[ente] ... brahman.nouser_engine lifecycle=Daemon
|
||
[data] ... src summary: 5 archivos en crates/core/brahman-admin/src
|
||
members: 5 (dispersion=0.00)
|
||
lens hint: code
|
||
[data] ... src summary: 11 archivos en crates/core/ente-brain/src
|
||
...
|
||
[data] ... graph summary: 7 archivos en crates/core/ente-zero/src/graph
|
||
|
||
El protocolo de presentación es uno solo: la Card. La función — anunciar
|
||
identidad, exponer metadata, ser descubierto — es idéntica para procesos
|
||
vivos y agrupaciones de datos. La UI lo ve como una lista uniforme.
|
||
|
||
Costo conocido: cada Mónada consume un thread + tokio runtime
|
||
current_thread (legacy del sidecar API). Para muchas Mónadas (>50)
|
||
conviene consolidar en un único runtime con N tasks. Defer a Phase B-3.
|
||
|
||
Pendientes propuestos:
|
||
- **B-3**: consolidar todos los sidecars en un único runtime tokio
|
||
para no spawnear N threads.
|
||
- **C**: pseudo-embeddings + atracción por centroide.
|
||
- **D**: módulo `nouser-nous` para LLM, swappable por priority_contexts.
|
||
- **Polish**: labels con 2-3 componentes del path.
|
||
- **Crossreferencia**: que un Ente pueda anunciar "estoy procesando la
|
||
Mónada X" y la Mónada anuncie "Ente Y me está procesando".
|
||
|
||
cargo check --workspace: 0 errores, 0 warnings.
|
||
|
||
### feat: Phase B-1 — unificación ontológica de Cards (Ente ↔ Data)
|
||
La Card es **el** protocolo de presentación del ecosistema, no sólo de
|
||
los procesos. Una Mónada Nouser y un Ente Brahman son ambos "entidades
|
||
que se presentan"; el consumidor (UI, broker, admin) discrimina por
|
||
`kind` cuando importa, pero todos hablan el mismo idioma.
|
||
|
||
Cambios:
|
||
|
||
- `brahman-card`:
|
||
- `CardKind { Ente (default), Data }`. Conserva back-compat:
|
||
Cards existentes son `Ente` por default.
|
||
- `DataFacet { summary, keywords, centroid, member_count, dispersion,
|
||
presentation_hint }`. Liviano para el wire — listas grandes
|
||
(members, embeddings completos) se consultan al daemon dueño bajo
|
||
demanda.
|
||
- `Card.kind` y `Card.data: Option<DataFacet>` agregados. WireCard
|
||
espeja, conversiones `From` propagan.
|
||
- Default impl actualizado.
|
||
|
||
- `brahman-broker::BrokeredCard`: propaga `kind` y `data` desde la Card
|
||
registrada. No afecta el matching (sigue siendo por TypeRef +
|
||
priority + pin_to); permite a observadores discriminar sin re-query.
|
||
|
||
- `nouser-card`: depende ahora de `brahman-card`. Nuevo método
|
||
`MonadManifest::to_brahman_card()` que proyecta:
|
||
- id, label, lineage → directos.
|
||
- payload Virtual, supervision Delegate, lifecycle Daemon (placeholder
|
||
semántico — la Mónada no se ejecuta).
|
||
- kind = Data.
|
||
- data = Some(DataFacet) con summary, keywords, centroide,
|
||
member_count, entropy → dispersion, y un `presentation_hint` derivado
|
||
del `Lens` (`Code` → `"code"`, `Gallery` → `"gallery"`, etc.).
|
||
- Test nuevo: `projects_to_brahman_card`.
|
||
|
||
- `brahman-status`: cada sesión muestra ahora `[ente]` o `[data]` como
|
||
prefijo. Para sesiones `data`, render adicional con summary, members
|
||
+ dispersion, keywords y lens hint.
|
||
|
||
Resultado: la UI (yahweh, brahman-status, futuro explorer) ve una sola
|
||
lista uniforme. No tiene que saber si está mirando un proceso o un
|
||
cúmulo de datos — sólo lee el Card y se adapta por `kind`.
|
||
|
||
Tests acumulados: 59 (card 11, broker 15, handshake codec+transport 2 +
|
||
integ 7, card-wit 4, admin 0, nouser-card 7, nouser-core 13).
|
||
cargo check --workspace: 0 errores, 0 warnings.
|
||
|
||
Próximo: **Phase B-2** — bin `nouser daemon <dir>` que sidecarea cada
|
||
Mónada como una sesión brahman, publicándola al broker. Brahman-status
|
||
las verá junto a los entes.
|
||
|
||
### feat(nouser): Phase A — mecanismo determinista de Mónadas
|
||
Primer trozo del módulo Nouser (Kairos): explorador de Mónadas como
|
||
"imanes semánticos" sobre el filesystem. Phase A cubre el 90% de los
|
||
casos sin tocar IA — sólo metadatos y heurísticas.
|
||
|
||
Crates nuevos:
|
||
|
||
- `crates/modules/nouser/card`: `MonadManifest` (la Tarjeta de
|
||
Presentación de una Mónada — espejo conceptual de `brahman::Card`
|
||
pero para datos, no para procesos runtime). Campos: id (Ulid),
|
||
label, summary, centroid (vacío en Phase A), keywords, cardinality,
|
||
entropy [0,1], dominant_lens, pins, members, timestamps,
|
||
extensions (forward-compat). 6 tests de validación + JSON roundtrip.
|
||
- `crates/modules/nouser/core`: pipeline determinista.
|
||
- `scanner`: walkdir → `Vec<FileEntry>` con metadatos (path, size,
|
||
mtime, extension). Skipea hidden por default. Configurable max
|
||
depth y follow_links.
|
||
- `cluster::by_directory`: agrupa por parent dir, mínimo 3 archivos
|
||
para promover a Mónada (configurable). Calcula keywords (top-N
|
||
extensiones por frecuencia + alfabético), elige `Lens` dominante
|
||
(Code/Gallery/Markdown/Database/Grid) según extensión más
|
||
frecuente, computa entropía de Shannon normalizada [0,1].
|
||
- `db`: `MonadDb` en memoria con índices BTreeMap files/monads y
|
||
`resolve_members(monad_id)` que filtra IDs huérfanos. Phase B
|
||
traerá persistencia.
|
||
- bin `nouser`: subcomandos `scan <dir>`, `show <dir> <prefix>`,
|
||
`json <dir>`. Env var `NOUSER_MIN_FILES` para tunear el threshold.
|
||
- 13 tests (4 scanner + 6 cluster + 3 db).
|
||
|
||
Demo end-to-end:
|
||
|
||
$ nouser scan crates
|
||
scan: 255 archivos en crates, 19 mónadas (min_files=3)
|
||
[01KR4C13] src card=12 ent=0.00 lens=Code
|
||
keywords: rs
|
||
[01KR4C13] tests card=14 ent=0.00 lens=Code
|
||
keywords: rs
|
||
[01KR4C13] fixtures card=5 ent=0.00 lens=Grid
|
||
keywords: rhai
|
||
...
|
||
|
||
$ nouser show crates 01KR4C
|
||
Monad 01KR4C1370DVF6NMTW6SECNXAF
|
||
label: src
|
||
summary: 4 archivos en crates/modules/nouser/core/src (ext: rs)
|
||
cardinality: 4
|
||
entropy: 0.0000
|
||
lens: Code
|
||
members (4):
|
||
4132 bytes crates/modules/nouser/core/src/db.rs
|
||
...
|
||
|
||
Pendientes para próximas fases (anotados, no urgentes):
|
||
- **Phase B**: bin `nouser daemon` que sidecarea a brahman-init
|
||
declarando flows (`scan-request:json` → `monad-update:json`).
|
||
- **Phase C**: pseudo-embeddings deterministas (hash de path/ext/size
|
||
a 32-d) + atracción por centroide via cosine similarity. Implementa
|
||
el "imán" sin LLM.
|
||
- **Phase D**: módulo `nouser-nous` aparte para el LLM real
|
||
(Llama/ONNX). En `priority_contexts.test` el Init pinea a
|
||
`mock-nous` (embeddings determinísticos); en `prod` a `real-nous`.
|
||
- **Polish**: labels de Mónada incluir 2-3 componentes del path para
|
||
desambiguar `src/` repetidos en monorepo.
|
||
|
||
Workspace: 0 errores, 0 warnings. Tests acumulados: 58
|
||
(card 11, broker 15, handshake codec+transport 2 + integ 7,
|
||
card-wit 4, admin 0, nouser-card 6, nouser-core 13).
|
||
|
||
### feat(broker): priority contexts — biases per-contexto operativo
|
||
- `brahman-card::ContextBias { pin_to: Option<String>, priority_offset: i8 }`
|
||
declara un override per-contexto.
|
||
- `Card.priority_contexts: BTreeMap<String, ContextBias>` y mismo en
|
||
`WireCard` (cruza el wire). Las conversiones `From` lo propagan.
|
||
- `BrokerConfig.current_context: Option<String>`. Cuando el broker corre
|
||
bajo un contexto y una Card declara biases para ese nombre, se aplican:
|
||
- Como **consumidor**: `pin_to` sobreescribe el `Flow.pin_to` estático.
|
||
- Como **productor**: `priority_offset` se suma a la priority base
|
||
(clamp en `[Low=0, Critical=3]`) para el ranking.
|
||
- `BrokeredCard` propaga `priority_contexts`. `find_producer_for` usa
|
||
`effective_priority(card)` y `effective_pin(card, input)` antes de
|
||
los tiebreaks.
|
||
- `brahman-admin::AdminConfig.current_context` + `StatusSnapshot.current_context`
|
||
espejan el contexto activo. `brahman-status` lo imprime como
|
||
`Context: <nombre>` justo debajo de `Init: ...`.
|
||
- `ente-zero` lee `BRAHMAN_BROKER_CONTEXT` env var y la propaga al
|
||
broker y al admin. Sin var, biases per-contexto inactivos.
|
||
- 4 tests nuevos en brahman-broker:
|
||
`context_priority_offset_lifts_producer_above_alphabetic_winner`,
|
||
`context_pin_to_overrides_static_pin`, `unknown_context_no_op`,
|
||
`priority_offset_clamps_to_critical`.
|
||
- Validación end-to-end: `BRAHMAN_BROKER_CONTEXT=test ente-zero` →
|
||
`brahman-status` muestra `Context: test`.
|
||
|
||
### feat(card): WireCard + extensions — forward-compat sin romper postcard
|
||
- `Card.extensions: BTreeMap<String, serde_json::Value>` restaurado con
|
||
`#[serde(flatten, default, skip_serializing_if = is_empty)]`. Los
|
||
campos JSON/TOML desconocidos sobreviven el roundtrip de archivos.
|
||
- Nuevo `WireCard`: proyección postcard-friendly (sin `extensions`,
|
||
`genesis: Vec<WireCard>` recursivo). Conversiones `From<Card>` y
|
||
`From<WireCard>` con descarte/recreación de extensions.
|
||
- `brahman-handshake::Hello.card` pasa de `Card` a `WireCard`. Client
|
||
hace `card.into()` antes de enviar; Server hace `hello.card.into()`
|
||
para volver a Card antes de validar/registrar.
|
||
- 3 tests nuevos en brahman-card:
|
||
`extensions_preserved_in_json_roundtrip`,
|
||
`wire_card_roundtrip_strips_extensions`,
|
||
`wire_card_postcard_friendly` (postcard encode/decode efectivo).
|
||
- brahman-card gana `postcard` como dev-dep para el último test.
|
||
- Contrato documentado: extensions = anotaciones locales que NO cruzan
|
||
al Init; sólo viven en archivos.
|
||
|
||
### `9420eae` chore: limpia warnings dead-code en arje (commit del usuario)
|
||
- `ente-zero/src/events.rs`: `#![allow(dead_code)]` a nivel módulo —
|
||
es vocabulario de eventos con variantes/campos reservados para flujos
|
||
no cableados aún (CapabilityRequested, ShutdownReason::Signal,
|
||
CapabilityGrant::{Granted, Denied, QuotaExceeded}, ExitStatus
|
||
fields).
|
||
- `ente-zero/src/graph/mod.rs`: comentado el re-export ahora innecesario
|
||
de `SHUTDOWN_GRACE`. `DEFAULT_GRANT_TTL` con `#[allow(dead_code)]`
|
||
+ nota "reservado para capability granting".
|
||
- `ente-zero/src/graph/capabilities.rs`: `renew_grant` con
|
||
`#[allow(dead_code)]` (capability renewal pendiente).
|
||
- `ente-kernel/src/surface.rs`: drop de `use anyhow::Context` (no se
|
||
usaba).
|
||
- `ente-hostnamed-compat/src/main.rs`: drop de `Connection` (no se
|
||
usaba).
|
||
- `ente-polkit-compat/src/main.rs`: `PolicyDecision.source` con
|
||
`#[allow(dead_code)]` (sólo aparece en `Debug` para logging).
|
||
- `cargo check --workspace`: 17 warnings → 0.
|
||
|
||
### feat(sidecar): WIT al sidecar — módulos conscientes vivos
|
||
- `brahman-card::WitInterface` deriva `Serialize`, `Deserialize`,
|
||
`PartialEq`, `Eq` para cruzar el wire postcard.
|
||
- `brahman-handshake::Hello` lleva `wit: Option<WitInterface>`. Server
|
||
usa `ResolvedCard::from_conscious` cuando viene presente, `from_agnostic`
|
||
cuando no.
|
||
- `brahman-handshake::Client::connect` queda como wrapper agnóstico de
|
||
`connect_with(path, card, wit: Option<WitInterface>)`.
|
||
- `brahman-broker::Broker::register` ahora toma `Option<WitInterface>`
|
||
como tercer arg. `BrokeredCard` guarda el wit. 25 sitios de tests
|
||
actualizados con `, None`.
|
||
- `brahman-sidecar::SidecarConfig` con campo `wit`. Helpers nuevos:
|
||
`SidecarConfig::new(card).with_wit(wit)` y `spawn_conscious(card, wit)`.
|
||
El log `attached` reporta `conscious=true|false`.
|
||
- `brahman-status` muestra marker 🧠 + sección `wit:` (package/world,
|
||
imports, exports) por sesión consciente.
|
||
- Example nuevo `crates/shared/brahman-sidecar/examples/presence-conscious.rs`:
|
||
toma label + path .wit (default `shared_wit/protocol.wit`), parsea
|
||
con brahman-card-wit, spawna sidecar consciente.
|
||
- Validado end-to-end:
|
||
```
|
||
$ presence-conscious demo.conscious shared_wit/protocol.wit &
|
||
$ brahman-status
|
||
Sessions (1):
|
||
01K... demo.conscious 🧠 lifecycle=Daemon
|
||
wit: brahman:protocol@0.1.0 / module
|
||
imports: types, handshake, lifecycle
|
||
exports: run
|
||
```
|
||
|
||
### feat(core): brahman-card-wit — extractor opcional de contratos WIT
|
||
- Crate nuevo `crates/core/brahman-card-wit` con `wit-parser = "0.230"`.
|
||
- API: `parse_wit(source)` y `parse_wit_file(path)` devuelven
|
||
`Vec<WitInterface>` (uno por `world` declarado).
|
||
- Interfaces importadas/exportadas (no sólo funciones) se resuelven
|
||
por nombre via `resolve.interfaces[id].name`.
|
||
- Example `crates/core/brahman-card-wit/examples/brahman-wit-info.rs`
|
||
CLI: `brahman-wit-info shared_wit/protocol.wit` → lista paquete,
|
||
worlds, imports y exports.
|
||
- 4 tests: inline, archivo real (`shared_wit/protocol.wit`), parse
|
||
error, world vacío.
|
||
- Validado contra `protocol.wit`: detecta worlds `module` y
|
||
`admin-host` con sus imports/exports correctos.
|
||
|
||
### `7b589b8` chore: agrega CHANGELOG.md retroactivo
|
||
- `CHANGELOG.md` en la raíz con los 11 commits previos documentados
|
||
acción por acción. A partir de este punto, cada cambio sustantivo
|
||
actualiza también este archivo en el mismo commit.
|
||
|
||
### `8a83a26` feat(handshake): notificación push de matches
|
||
- Frame `MatchEvent { kind: Available | Lost, ... }` añadido al protocolo.
|
||
- `Session::run_post_handshake` usa `tokio::select!` para multiplexar
|
||
reads del cliente y un canal `mpsc` push del server.
|
||
- Server: `SessionTxTable` (Arc<Mutex<HashMap<SessionId, Sender<Frame>>>>)
|
||
y `LastMatches` para diff por sesión. `broadcast_match_diffs` corre
|
||
tras cada `register` y `unregister`, emite sólo los cambios.
|
||
- Capacity del canal push: 32 (ephemeral, `try_send` non-blocking).
|
||
- Client: `VecDeque<MatchEvent>` interno, `take_event()` (non-blocking)
|
||
y `await_event(timeout)`. `ping()` ahora drena MatchEvents intermedios
|
||
hasta encontrar el Pong.
|
||
- Example `crates/core/brahman-handshake/examples/subscriber.rs`.
|
||
- Test `match_event_pushed_on_producer_arrival` (handshake integ 6→7).
|
||
|
||
### `70a7a0d` feat: segundo módulo (nakui) + admin API + brahman-status
|
||
- Crate nuevo `crates/shared/brahman-sidecar` (DRY del thread + tokio +
|
||
ping loop). API: `spawn(card)` / `spawn_with_handle(config)`.
|
||
- `nakui` cmd_run llama `brahman_sidecar::spawn` antes de `run_server`.
|
||
Card: lifecycle Daemon, supervision Restart, flow `command` (json) /
|
||
`report` (json).
|
||
- Crate nuevo `crates/core/brahman-admin` con `StatusSnapshot` JSON
|
||
line-delim, `AdminServer` y `client::query`.
|
||
- ente-zero levanta también el AdminServer en `primordial_loop`.
|
||
- Example `crates/shared/brahman-sidecar/examples/presence.rs`
|
||
(módulo dummy long-lived parametrizable por label).
|
||
- Example `crates/core/brahman-admin/examples/brahman-status.rs`
|
||
(CLI que pretty-printa el snapshot).
|
||
- `brahman-broker`: `BrokeredCard` ahora incluye `lifecycle`. `Endpoint`
|
||
y `Match` derivan `Serialize`/`Deserialize`. Nuevo `Broker::cards()`
|
||
iterador.
|
||
- `brahman-card`: `pub use ::ulid` para que módulos no dependan de ulid.
|
||
- yahweh-shell migrado al sidecar compartido (96→53 LOC).
|
||
|
||
### `595f68e` feat(yahweh-shell): primer módulo brahman vivo
|
||
- yahweh-shell spawnea sidecar antes de `Application::new()`.
|
||
- Card declarada: label `brahman.ui_engine`, lifecycle Widget,
|
||
supervision Delegate, payload Virtual, flow input `render-data`
|
||
(json) / output `user-intent` (json).
|
||
- Sidecar en thread aparte con tokio current_thread runtime,
|
||
desacoplado del runtime GPUI.
|
||
|
||
### `df9d10c` feat(ente-zero): enchufa el handshake server al Init real
|
||
- ente-zero levanta `brahman_handshake::server::Server::bind` en
|
||
`primordial_loop` después del ente-bus, con degradación grácil
|
||
si bind falla (mismo patrón que uevents).
|
||
- Nuevo módulo `brahman-handshake/src/transport.rs`: helper
|
||
`default_socket_path()` con resolución `BRAHMAN_INIT_SOCKET` →
|
||
`XDG_RUNTIME_DIR` → `TMPDIR`.
|
||
- Example `crates/core/brahman-handshake/examples/probe.rs`.
|
||
- Validación end-to-end manual: probe contra ente-zero vivo
|
||
imprime `HelloAck: session=... init_attached=true`.
|
||
|
||
### `07d77a3` feat(handshake): integra el broker con el ciclo de sesiones
|
||
- `ServerConfig` acepta `Option<Arc<Mutex<Broker>>>`.
|
||
- `register_session` indexa la Card en el broker y la `SessionRegistry`
|
||
antes de emitir HelloAck.
|
||
- `Session::handle` refactor a `do_handshake → run_post_handshake →
|
||
cleanup` con cleanup unificado (broker + sessions).
|
||
- Tests integ nuevos: `broker_registers_and_unregisters_with_session`
|
||
y `broker_matches_two_live_modules`.
|
||
- Fix colateral: `brahman-card::TypeRef` pasa de internally-tagged
|
||
(`#[serde(tag = "kind")]`) a externally-tagged. Postcard no soporta
|
||
internally-tagged en formatos no self-describing. JSON cambia de
|
||
`{"kind":"primitive","name":"x"}` a `{"primitive":{"name":"x"}}`.
|
||
|
||
### `5091106` feat(core): brahman-broker — matching híbrido
|
||
- Crate nuevo `crates/core/brahman-broker`.
|
||
- 3 estrategias de matching: `Exact`, `Structural`, `ExactThenStructural`
|
||
(default). Devuelven `Match::via` con la estrategia que ganó.
|
||
- Override `pin_to`: el consumer pide un productor por label; si la
|
||
pista no resuelve, cae en type-search.
|
||
- Tiebreak por `Card.priority` desc, luego `label` asc (estable y
|
||
determinista).
|
||
- API: `register`, `unregister`, `find_producer_for`, `all_matches`,
|
||
`cards`, `sessions`, `len`, `is_empty`.
|
||
- 11 tests (matching, pin_to, priority, no-self-loops, all-matches).
|
||
|
||
### `814390f` feat(core): brahman-handshake — protocolo runtime
|
||
- Crate nuevo `crates/core/brahman-handshake` con server y client
|
||
Rust↔Rust sobre Unix socket.
|
||
- Frames length-prefixed (4 bytes LE) + cuerpo postcard.
|
||
- Mensajes: `Hello`, `HelloAck`, `Ping`, `Pong`, `Farewell`, `Error`.
|
||
- `MAX_FRAME_BYTES = 4 MiB` para evitar reservas absurdas.
|
||
- Tradeoff: drop `extensions`/`extra` de Card por incompat
|
||
postcard ↔ `serde_json::Value`. Forward-compat queda en
|
||
`schema_version` + `protocol_version` negotiation.
|
||
- 4 tests integ + 1 unit en codec.
|
||
|
||
### `ed0e973` refactor(arje): migra ente-card a re-export de brahman-card
|
||
- `ente-card/src/lib.rs` reescrito como crate-shim de re-export
|
||
(327 LOC → 25 LOC).
|
||
- `EntityCard` ≡ `brahman_card::Card` por type alias.
|
||
- `ente-card/Cargo.toml`: deps reducidas a `brahman-card`.
|
||
- `Card` impl `Default` (Ulid::nil(), label vacío) para que
|
||
`..Default::default()` funcione en struct-literals.
|
||
- 4 sitios en `ente-zero/src/seed.rs` actualizados con
|
||
`..Default::default()` para los campos aditivos.
|
||
- Los 21 consumidores arje compilan sin tocar fuente.
|
||
|
||
### `0feba74` feat(core): brahman-card — Tarjeta canónica híbrida
|
||
- Crate nuevo `crates/core/brahman-card`.
|
||
- Hereda de arje: `id: Ulid`, `lineage`, `Capability` tipado,
|
||
`Payload::{Wasm, Native, Virtual, Legacy}`, `SomaSpec`
|
||
(namespaces, cgroups, rlimits, cpu_affinity), `Supervision`
|
||
(Restart con backoff, OneShot, Delegate), `genesis` recursivo.
|
||
- Aditivo brahman: `Permissions` enumerados (`NetworkingPolicy`,
|
||
`FsPolicy`, `IpcPolicy`), `Lifecycle` ortogonal a Supervision,
|
||
`Priority` de scheduling, `Flows` con `TypeRef` discriminado
|
||
(Primitive | Wit), `pin_to` opcional.
|
||
- `TrustLevel` derivado de `Permissions` (no declarado).
|
||
- `ResolvedCard { card, wit: Option<WitInterface>, trust }`.
|
||
- Soporta JSON (canónico) + TOML (auto-detectado por extensión).
|
||
- 8 tests incluido `arje_seed_format_compatible` que valida que
|
||
el JSON de arje sigue parseando con defaults para los aditivos.
|
||
|
||
### `4d50bfc` chore: absorbe nakui (ERP matemático) en modules/nakui
|
||
- `~/nakui` → `crates/modules/nakui/{core,modules}`.
|
||
- `core/`: el crate `nakui-core` con 4 bins (nakui, demo,
|
||
inventory_demo, sales_demo) y tests.
|
||
- `modules/{inventory,sales,treasury}/`: data declarativa
|
||
(`nsmc.json`, `schema.k`, `morphisms/`) que el crate consume.
|
||
No son crates Cargo.
|
||
- Deps directas (no `workspace = true`): thiserror v1, surrealdb,
|
||
rhai, petgraph. No conflicto con el resto del workspace.
|
||
|
||
### `53dbdf0` chore: monorepo inicial con arje + minga + yahweh absorbidos
|
||
- 45 crates absorbidos en 4 ejes:
|
||
- `crates/core/`: 24 crates de arje (Init systemd-compatible:
|
||
`ente-card`, `ente-zero`, `ente-kernel`, `ente-bus`, `ente-cas`,
|
||
`ente-soma`, `ente-wasm`, `ente-snapshot`, `ente-brain`,
|
||
`ente-echo`, `ente-policy-provider`, + 12 `*-compat`).
|
||
- `crates/modules/semantic_dht/`: 5 crates de minga (`minga-core`
|
||
con AST/CAS/MST, `minga-p2p` con libp2p Kad, `minga-store`,
|
||
`minga-vfs`, `minga-cli`).
|
||
- `crates/modules/ui_engine/`: 11 crates de yahweh (libs/{core,
|
||
theme, bus, providers}, widgets/{tree, splitter, tabs, tiled,
|
||
container_core, text_input}).
|
||
- `crates/apps/`: 5 crates de yahweh (file_explorer,
|
||
database_explorer, text_viewer, image_viewer, yahweh-shell).
|
||
- `shared_wit/protocol.wit` con handshake/lifecycle inicial.
|
||
- `Cargo.toml` unificado: thiserror bumped a 2 (transparente para
|
||
arje), tokio "full", paths intra-workspace de yahweh redirigidos.
|
||
- `cargo check --workspace`: 0 errores (sólo dead-code warnings
|
||
preexistentes en ente-zero).
|