diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e26cf6..156af8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5058 +1,13 @@ # 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 `. - -## 2026-05-10 - -### feat(brahman-demo): bootstrap script reproducible — broker + producer + consumer + 4 explorers -Iter 22. Cierra el set de iteraciones de hoy: cualquier persona (o -future-me retomando el repo) puede levantar el escenario completo -con un comando. - -Crate nuevo `crates/apps/brahman-demo/` con 3 binarios: -- **`brahman-demo-broker`**: standalone `Server::bind` con un Broker - configurado, escucha en el socket default. Reemplaza a - `ente-zero` para fines de demo (ente-zero pesa kernel surface + - child subreaper + bus + brain + audit; el demo no lo necesita). -- **`brahman-demo-producer`**: registra una Card con `flow.output[demo-stream:json]` - y queda pingueando. -- **`brahman-demo-consumer`**: registra una Card con `flow.input[demo-feed:json]` - (mismo type → matchea con el producer) y queda escuchando - `MatchEvent`s. - -Variables de entorno respetadas en los 3: `BRAHMAN_INIT_SOCKET`, -`BRAHMAN_BROKER_CONTEXT` (sólo broker), `BRAHMAN_DEMO_LABEL/FLOW/TYPE`, -`RUST_LOG`. - -Script nuevo `scripts/bootstrap-demo.sh`: -- Modos: `all` (default — broker + producer + consumer + 4 explorers), - `broker` (sin GUIs, sólo backend), `only` (sólo broker, sin - producer/consumer ni GUIs). -- Cleanup-safe: trap `EXIT INT TERM` mata todos los procesos - spawneados (con SIGTERM grace + SIGKILL fallback) y borra el socket. -- Espera activa hasta 5s a que el socket aparezca antes de spawnear - los siguientes (evita ENOENT en el handshake). -- Logs separados por proceso bajo `$BRAHMAN_DEMO_LOG_DIR` (default - `/tmp/brahman-demo`). Re-invocaciones limpian los logs viejos. -- Re-build automático opcional (comentado por default — asume - `cargo build --workspace` ya hecho). - -Smoke verificado end-to-end (sin DISPLAY, sólo backend): -- Broker arranca, bind del socket OK. -- Consumer conecta, asigna session. -- Producer conecta, asigna session. -- Consumer recibe `MatchEvent { Available, demo-feed ← demo-stream, - via: Exact, pinned: false }` automáticamente — el broker computó - el match y lo pusheó por el push channel. - -Stack tests: brahman-demo (0 unit), workspace verde. - -### feat(brahman-handshake): ListMatches endpoint + timeline en broker-explorer -Iter 21. Cierra el loop de observabilidad iniciado en iter 20: ahora -se ven no sólo las sesiones registradas sino también qué matches -consumer↔producer está computando el broker en cada momento, y la -historia de cómo cambian. - -`brahman-handshake/messages.rs`: -- **`Frame::ListMatches(ListMatches{session})`**: pedido (mismo - patrón de validación session-id). -- **`Frame::MatchList(MatchList{matches: Vec})`**: - respuesta. Cada `Match` ya es serializable y lleva `consumer`, - `consumer_label`, `producer`, `producer_label`, `ty`, `via`, `pinned`. - -`brahman-handshake/server.rs`: -- `run_post_handshake` ahora pasa también `broker_for_match: Option<&SharedBroker>` - al `handle_inbound_frame`. -- Si el server tiene broker configurado, `ListMatches` responde con - `broker.all_matches()`. Si no (server sin broker), responde - `MatchList { matches: vec![] }` — refleja "no hay matching activo", - no es un error. - -`brahman-handshake/client.rs`: `Client::list_matches()` análogo a -`list_sessions()`, drena `MatchEvent`s intermedios al buffer. - -`brahman-sidecar/discovery.rs`: `list_matches` y `list_matches_blocking` -con la misma forma de Card observer minimalista. - -`brahman-broker-explorer`: -- Cada poll-tick ahora pide TANTO `list_sessions` COMO `list_matches`. -- `Explorer.last_match_keys: HashSet` mantiene el estado - del último snapshot. La key es `(consumer.session, consumer.flow, - producer.session, producer.flow)`. -- `Explorer.timeline: VecDeque` con cap `TIMELINE_CAP=50`. -- Función pura `diff_matches(last_keys, list) -> (entries, new_keys)`: - emite `Available` para keys nuevas y `Lost` para keys desaparecidas. - Primer tick (last_keys vacío) marca todo como Available — cubre - el boot sin que la UI quede vacía. -- Render: `stat_card` "Timeline de matches" con count + 20 entries - formateadas como `HH:MM:SS {+/-} consumer.flow ← producer.flow [via]`. - Más reciente arriba. - -Tests broker-explorer: 5 totales. -- `diff_matches_first_snapshot_marks_everything_available` -- `diff_matches_emits_lost_when_match_disappears` -- `diff_matches_no_change_emits_nothing` -- `pending_is_default_state_at_boot` (existente) -- `poll_and_probe_constants_are_sane` (existente) - -Decisión: timeline polled (cada `POLL_INTERVAL=5s`), no push. -Razón: los `MatchEvent` push del broker son consumer-céntricos -(cada session sólo ve sus propios matches). Para "system-wide -timeline" haría falta una API broker-level "subscribe a todos" — -mucho más scope. Polling cada 5s es suficiente para observabilidad. - -### feat(brahman-handshake): ListSessions endpoint + cliente + UI broker-explorer -Iter 20. Nuevo flujo end-to-end para observabilidad: cualquier -módulo conectado puede preguntar al broker la lista de sesiones -activas y mostrar labels + flows in/out por cada una. - -`brahman-handshake/messages.rs`: -- **`Frame::ListSessions(ListSessions { session })`**: request del - cliente (server valida que `session` coincida con la sesión vigente, - mismo patrón que Ping/Farewell). -- **`Frame::SessionList(SessionList { entries })`**: respuesta. - Cada `SessionEntry` lleva: `session`, `label`, `schema_version`, - `outputs` (nombres de flow.output), `inputs` (nombres de - flow.input), `conscious` (`true` si la Card vino con WIT). - -`brahman-handshake/server.rs`: -- `run_post_handshake` ahora pasa `SessionRegistry` a - `handle_inbound_frame` (necesario para consultar el snapshot de - sesiones en respuesta a `ListSessions`). -- Helper `build_session_list(sessions)` que toma el snapshot bajo - el lock, lo proyecta a `SessionList`, y suelta el lock antes de - escribir el frame al wire. -- Validación `session_id` mismatched → `HandshakeError::Unauthorized`. - -`brahman-handshake/client.rs`: -- `Client::list_sessions()` async: envía el request, drena - `MatchEvent`s intermedios al `pending_events` buffer (mismo patrón - que `ping`), retorna el `SessionList`. - -`brahman-sidecar/discovery.rs`: -- `pub async fn list_sessions(observer_label)` y - `pub fn list_sessions_blocking(observer_label)`: arman una Card - observer mínima (sin flow.input/output), conectan, piden la lista, - Farewell. Para CLIs y módulos std-thread. - -`brahman-broker-explorer`: -- Cada poll-tick (cuando el broker está UP*) ahora también pide - `list_sessions_blocking` y guarda el snapshot en `Explorer.sessions`. -- Render extiende el body con un `stat_card` "Sesiones activas" que - muestra el count + lista ordenada por `session` (Ulid temporal), - cada item: `label · in:[flows] out:[flows] (wit?)`. - -Tests: -- `list_sessions_returns_currently_registered`: levanta server con - broker, conecta 3 clientes (alpha, beta, observer), observer pide - `list_sessions`, verifica los 3 labels presentes y que la entry - del observer reporte `conscious=false` y el `schema_version` - esperado. -- Stack: handshake suite (24 tests), sidecar (3+8 unit + integ), - broker-explorer (4 tests). Todo verde. - -### feat(yahweh-launcher): F3 — extracción del shell standard de explorers -Iter 19. Patrón con 4 consumers idénticos (nakui-explorer, -nouser-explorer, minga-explorer, brahman-broker-explorer) declaraban -~20 líneas de boot: -`Application::new + Theme::install_default + cx.open_window -+ WindowOptions{ window_bounds, titlebar } + cx.activate(true)`. -Todo idéntico salvo título, tamaño, y root entity factory. - -Crate nuevo `crates/modules/ui_engine/libs/launcher/` -(`yahweh-launcher`): -- **Deps**: `gpui`, `yahweh-theme`. Sin más. -- **`pub fn launch_app(title, size, root_factory)`**: 1-line boot. -- **`pub fn launch_app_with(config, root_factory)`**: variante con - `AppLaunchConfig` armado afuera, para casos donde título/tamaño se - computan condicionalmente (env var, etc). -- **`AppLaunchConfig::new(title, size)`**: builder normalizador. -- 2 tests `#[test]` cubren la normalización de config (no se testea - `launch_app` per se porque bloquea el thread main hasta que la - ventana se cierra). - -Migración 4 consumers: -- `main()` pasa de 20 líneas a 1: `launch_app("Title", (W, H), Explorer::new)`. -- Imports de gpui se podan: ya no necesitan `App, Application, - Bounds, WindowBounds, WindowOptions` ni `SharedString` ni `prelude::*`. -- Cada consumer agrega dep `yahweh-launcher` (path local). - -Naming: el slot natural era `yahweh-shell`, pero ya existe un crate -`yahweh-shell` en `crates/apps/` (bootstrap heavyweight con file -explorer + DB explorer + viewers). El helper es liviano y específico -al patrón de launch, así que `yahweh-launcher` evita confusión. - -Total ahorro: ~75 líneas hardcoded de boilerplate UI a 4 líneas en -total. Cambios de boot (window opts, theme, etc) ahora viven en un -solo lugar. - -Tests stack: 2 nuevos en launcher; suites de los 4 consumers -intactas. Todo verde. - -### 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. - -### fix(nakui-core): schema_bundle_hash debe reflejar el contenido real del schema -Iter 17. Regresión surfaceada por el workspace test -`verify_log_rejects_seed_after_schema_kcl_changes` (rebautizado a -`verify_log_rejects_seed_after_schema_changes`). - -**Bug**: `compute_schema_bundle_hash` operaba sobre los bytes del -bundle compilado, que `build_schema_bundle` arma como -`(import "/abs/path/schema.ncl") & ...`. Los bytes del bundle nunca -cambian cuando se edita el archivo apuntado — sólo cambian si se -agregan/quitan schemas o se mueve el módulo de path. Resultado: el -hash quedaba pegado y los seeds firmados bajo schema vN se aceptaban -silenciosamente como válidos bajo schema vN+1, aunque las invariantes -hubieran cambiado. - -**Fix**: nueva fn `read_schema_files_concat(module_dir, schemas)` -que lee los bytes de cada schema declarado y los concatena con -framing `\0NCL:\0` (separador no ambiguo + nombre relativo, no -absoluto, para que el hash sea estable entre máquinas). Esos bytes -alimentan `compute_schema_bundle_hash` y `compute_morphism_schema_hash` -en lugar de los bytes del bundle. El bundle compilado sigue siendo -imports-style (necesario para que Nickel resuelva paths relativos); -sólo la fuente del hash cambió. - -**Impacto en logs existentes**: como cualquier cambio al insumo del -hash, los seeds y morphisms versados bajo el bundle hash anterior -fallarán `SchemaMismatch` al verificarse contra un binario nuevo. No -hay migración — esto es exactamente el comportamiento que el hash -busca: re-seed. - -Tests: 10/10 en `schema_versioning` (era 9/10 con 1 FAILED). - -### feat(yahweh-widget-app-header): promover el header standard de explorers -Iter 16. Patrón con 4 consumers idénticos: `nakui-explorer`, -`nouser-explorer`, `minga-explorer`, `brahman-broker-explorer` -todos declaraban un header `flex_row + flex_grow(label) + -theme_switcher + bg(panel) + border-bottom + text_size(14) + -padding(16/12)`. Ahora es 1 línea. - -Crate nuevo `crates/modules/ui_engine/widgets/app-header/` -(`yahweh-widget-app-header`): -- **Deps**: `gpui`, `yahweh-theme`, `yahweh-widget-theme-switcher`. - El switcher se incluye automáticamente. -- **`pub fn app_header(cx: &mut App, label: impl Into) - -> impl IntoElement`**: caso simple con texto plano. -- **`pub fn app_header_with(cx, label_child: impl IntoElement)`**: - variante para cuando el lado izquierdo no es texto plano (icon - + text, múltiples spans, etc.). -- 3 tests `#[gpui::test]`: smoke con string label, con custom - child IntoElement, type-check de label con literal/owned/format!. - -Migración de los 4 consumers: -- Cada uno reemplaza un bloque `let header = div().flex().flex_row()... - .child(theme_switcher(cx))` (~13 líneas) por - `let header = app_header(cx, header_text)` (~1 línea). -- Cada uno borra dep `yahweh-widget-theme-switcher` (ya no la - necesita directo — el `app_header` la incluye internamente). -- Cada uno reemplaza `use yahweh_widget_theme_switcher::theme_switcher` - por `use yahweh_widget_app_header::app_header`. - -Total ahorro: ~50 líneas de código UI hardcoded en consumers. -Cambios visuales en el header (padding, border, text_size) ahora -viven en un solo lugar. - -Tests stack: 3 nuevos en app-header; suites de los 4 consumers -intactas. Todo verde. - -Decisión: el sidebar header del `MetaApp` (que también incluye -theme_switcher) NO se migra — es un header de sidebar, no de app -top, y tiene styling distinto (px(12/10/13), sin bg/border-bottom -porque ya está dentro del panel). Diferente patrón → diferente -widget si emerge segundo consumer. - -### feat(yahweh-widget-stat-card): promover el patrón stat card como widget -Iter 15. El patrón "tarjeta de dashboard con border-l accent + -label + valor grande + descripción + listing opcional" tenía 2 -consumers (`minga-explorer` y `brahman-broker-explorer`); ahora vale -extraer al stack yahweh para reusabilidad y mantenimiento single-place. - -Crate nuevo `crates/modules/ui_engine/widgets/stat-card/` -(`yahweh-widget-stat-card`): -- **Deps**: `gpui` + `yahweh-widget-card` (compone `card_themed`). - Sin theme directo — el caller pasa `text` y `text_dim` ya - resueltos del theme. -- **`pub fn stat_card(cx, label, value, description, accent, - text, text_dim, recent_items)`**: - - `cx: &App` (acepta `&Context` por deref auto-coerce). - - `value: impl Into` — sirve para counts (`"3"`), - status text (`"UP"`), o cualquier label corto. - - `recent_items: &[String]` — si no vacío, agrega sub-header - `"recent (N):"` + una linea por item. -- 3 tests `#[gpui::test]` con TestAppContext: smoke con/sin - recent_items, type-check de `value` con literal/format/owned. -- Dev-deps: gpui con `test-support` + yahweh-theme para construir - el cx con un theme global. - -Cambios consumer: -- **`minga-explorer`**: sustituye su `fn stat_card` local - (~60 líneas) por `use yahweh_widget_stat_card::stat_card`. - Borra dep `yahweh-widget-card` (ya no se usa directo). Adapta - los 3 callsites para pasar `value.to_string()` (el widget - acepta `Into`). -- **`brahman-broker-explorer`**: refactoriza su `fn state_card` - para que sea un wrap de `stat_card` con la traducción - `ProbeState → (accent, value, description)`. La función queda - como helper local porque la mapping del enum es app-specific, - pero el rendering pasa por el widget compartido. Borra dep - `yahweh-widget-card`. - -Tests stack: nuevos 3 del widget. Suites de los 2 consumers -intactas (4 minga-explorer, 2 broker-explorer). Stack total ~120 -verdes (varía por compilation cache). - -Beneficio operativo: -- Cualquier app nueva que necesite cards de dashboard usa - `stat_card(...)` directo; no re-implementa el pattern. -- Cambios visuales (text sizes, padding, sub-header format) - ahora viven en un solo lugar. -- `value: impl Into` es más expressive que el - `usize` rígido del original local. - -Pequeña simplificación documentada: el sub-header del listing -pasa de `"recent (N de TOTAL):"` a `"recent (N):"`. El "TOTAL" -ya no se calcula porque el widget no lo conoce — el caller que -quiera mostrarlo lo formatea en el label/value (ej. label `"Nodos -AST (5 de 247)"`). Acceptable trade-off por la reusabilidad -genérica. - -### feat(brahman-broker-explorer): nueva app probe del broker brahman -Iter 14. Cierra otro frente: visibilidad del broker brahman (el -broker handshake que matchea Cards consumer/producer). Hasta ahora -no había forma de "ver" si el broker estaba up sin invocar otro -binario CLI. Ahora hay una app GUI que probe cada 5s y reporta 3 -estados claros. - -Crate nuevo `crates/apps/brahman-broker-explorer/`: -- **Deps**: `brahman-handshake`, `brahman-sidecar` + el stack - yahweh themed (theme + 3 widgets). Consume el mismo - `await_provider_blocking` que usa `nouser-explorer`. -- **`ProbeState` enum** con 4 variants: - - `Pending` (estado inicial al boot, antes del primer probe). - - `Down { reason }` — connect failed, broker no escucha. - - `UpNoProvider { flow }` — broker reachable + sin productor - para el flow probado dentro del timeout. - - `UpWithProvider { flow, producer_socket }` — broker reachable - + matcheó algo, devuelve el socket del provider. -- **Polling loop** en `cx.spawn` cada 5s; el probe (que es - bloqueante porque internamente usa tokio runtime) se ejecuta en - `cx.background_executor().spawn(...)` para no congelar el main - thread del UI. -- **Configuración via env**: - - `BRAHMAN_INIT_SOCKET` — path del broker (default resuelto por - `brahman_handshake::transport`). - - `BRAHMAN_BROKER_PROBE_FLOW` — flow del Card observer - (default `broker-health`). - - `BRAHMAN_BROKER_PROBE_TYPE` — type name (default `ping`). -- **UI**: header con probe info + theme switcher; banner permanente - (Error/Warning/Success/none según estado) debajo del header; - stat card con accent color por estado y descripción. -- 2 tests sanity (default state es Pending; constants coherentes: - PROBE_TIMEOUT < POLL_INTERVAL >= 2s). - -Smoke run del binario verificado: bootstrap completo OK, panic -esperado en open_window por falta de display. - -Beneficio operativo: -- Si tenés un broker corriendo en `~/.local/share/brahman/init.sock`, - el explorer lo detecta + reporta estado verde con su socket. -- Si no hay broker, banner rojo + msg claro indicando el path - probado. -- Si hay broker pero ningún Card produce el flow probado, banner - amber — útil para distinguir "broker down" de "broker up, - no productor del tipo X". -- Apuntando el flow/type via env, podés monitor productores - específicos: ej. `BRAHMAN_BROKER_PROBE_FLOW=monad-list - BRAHMAN_BROKER_PROBE_TYPE=json` para ver si nouser está sirviendo. - -Apps GUI integradas al stack themed: **5** (nakui-ui, nakui-explorer, -nouser-explorer, minga-explorer, brahman-broker-explorer). - -Limitaciones documentadas: -- El observer registra una Card temporal en cada probe (cada 5s). - Eso ensucia un poco las estadísticas del broker (Cards - registradas/desregistradas). No impacta funcionalidad pero - inflama el log si el broker tiene observability habilitada. -- No muestra la **lista global** de Cards registradas en el broker - — el protocolo handshake actual no expone esa API. Para eso - habría que agregar un endpoint `ListSessions` al broker server. -- No mantiene un buffer de MatchEvents. Cada probe es independiente. - Para timeline de matches, hace falta mantener el Client vivo - entre probes — scope futuro. - -### feat(yahweh-theme): persistencia de la preferencia de theme entre runs -Iter 13. El theme switcher ya cambiaba el chrome en runtime, pero -al cerrar y reabrir la app el theme volvía a Nebula default. Ahora -el name del theme se persiste en `$XDG_CONFIG_HOME/yahweh/theme` -(default `~/.config/yahweh/theme`) y se restaura al boot. - -Cambios en `yahweh-theme`: -- **`pub fn config_path() -> Option`**: resuelve el path - XDG. Devuelve `None` si ni `XDG_CONFIG_HOME` ni `HOME` están - set (sandbox/CI). -- **`pub fn load_persisted() -> Option`**: lee el archivo, - trim, busca el theme por name vía `Theme::by_name`. `None` si - el file no existe, lectura falla, o el name no matchea ningún - preset (e.g. preset renombrado entre versiones). -- **`pub fn persist(theme: &Theme) -> io::Result<()>`**: escribe - el name al config file. Crea el dir parent si no existe. -- **`pub fn load_from_path` y `pub fn persist_to_path`**: variantes - con path explícito — útiles para tests con tempfile y para apps - que quieren un path custom (multi-user, staging, etc.). -- **`Theme::install_default(cx)` cambia**: antes hardcoded - `nebula()`. Ahora intenta `load_persisted()`, fallback a Nebula. -- **`Theme::set(cx, theme)` cambia**: antes sólo `cx.set_global`. - Ahora también `persist(&theme)` antes (best-effort: ignora io - errors). El `theme_switcher` widget ya consume `Theme::set`, así - que sin cambios en su código el switching ahora persiste. - -5 tests nuevos (`persistence_tests`): -- `persist_then_load_round_trip` — escribir + leer Aurora. -- `load_from_missing_file_returns_none` — no rebota. -- `load_from_unknown_name_returns_none` — name desconocido → - `None` (degrada a default cuando se usa). -- `persist_creates_parent_dir_if_missing` — crea - `~/.config/yahweh/` si no existe. -- `config_path_uses_xdg_config_home_when_set` — respeta el env. - -Tests stack: ~5 nuevos en yahweh-theme. Todos los downstream -(nakui-ui, *-explorer) compilan sin tocar nada — la API pública -de `Theme::install_default` y `Theme::set` no cambió shape. -Smoke run del binario verificado: bootstrap OK, panic esperado -sin display. - -Beneficio operativo: -- Usuario abre `nakui-ui`, cicla a Aurora con el switcher, cierra - app. Próxima apertura: Aurora cargado del disco. Todas las - apps yahweh-themed (4 del repo) comparten la misma preferencia. -- Failure mode benigno: sin home dir o sin permisos de write, - el theme cambia in-memory pero no se persiste — el switcher - sigue usable, sólo no sobrevive al close. -- Path canónico documentado: usuarios que quieran preset el - theme antes de abrir la app pueden hacer - `echo Aurora > ~/.config/yahweh/theme`. - -### feat(minga-explorer): listings de items recientes en cada stat card -Iter 12. Hasta ahora minga-explorer mostraba sólo counts (3 -números). Ahora cada stat card muestra también un sample de los -items dentro: hashes truncados de los 5 primeros nodos AST -(con su `kind`), atestaciones (`content_hash ← did_short`) y -claves MST. Mucho más útil para debugging que el "tengo N items". - -Cambios en `minga-explorer`: -- **`RepoSnapshot` extendido** con 3 nuevos `Vec<...>`: - - `recent_nodes: Vec<(String, String)>` — `(hash_short, kind)`. - - `recent_attestations: Vec<(String, String)>` — - `(content_hash_short, did_short)`. - - `recent_mst_keys: Vec` — `hash_short`. - - Cap de 5 items por sección via `RECENT_LIMIT` const. -- **`load_snapshot` itera los stores** y toma los primeros 5 - items via `iter().filter_map(Result::ok).take(RECENT_LIMIT)`. - Errores per-item se silencian (`filter_map`) — el dashboard - muestra lo que pueda; un par de items corruptos no debería - tirar el panel. -- **`short_hash(&str)` helper local**: trunca un hex a sus - primeros 12 chars (48 bits, distintivo dentro de un repo - single-machine). -- **`stat_card` extendido**: nuevo arg `recent_items: &[String]`. - Si no está vacío, agrega un sub-header `"recent (N de TOTAL):"` - + una linea por item. Cada line es texto pequeño (`px(11)`) - con el color principal del theme — visualmente queda como - monospace listing aunque no use mono font (no hay todavía - en el theme). - -Tests: 2 → **4** (+2 sanity de los nuevos defaults + del -`short_hash`). - -Beneficio operativo: -- Después de `minga ingest archivo.rs`, el explorer muestra - inmediatamente los hashes de los nodos AST creados, qué `kind` - tienen, y las atestaciones firmadas — sin necesitar `minga - status` o queries SQL. -- "5 de 247" da contexto del crecimiento sin overwhelm de - listing completo. - -Limitación documentada: los "recent" no son cronológicos — sled -ordena lexicográfico por hash. Para timeline real, agregar -timestamp al schema (cambio breaking del store, scope futuro). - -### feat(minga-explorer): nueva app dashboard del repo Minga sobre stack yahweh -Iter 11. Cierra el último frente identificado: integración del -módulo Minga (VCS semántico P2P) al ecosistema GUI. Antes Minga -sólo tenía CLI (`minga init/status/ingest/listen/sync/watch`). -Ahora hay un **dashboard GPUI** que muestra los counts del repo -en vivo, sobre el mismo stack themed que las otras dos apps -explorer. - -Crate nuevo `crates/apps/minga-explorer/`: -- **Deps**: `minga-store` (para `PersistentRepo::open`) + - `yahweh-theme` + `yahweh-widget-{banner,card,theme-switcher}`. - Sin `minga-cli` (no necesita prompts de passphrase) ni - `minga-core` (counts no requieren parsear AST). -- **Lectura sin passphrase**: el `PersistentRepo` se abre directo - desde `/repo` sled. Los counts (`nodes.len()`, - `attestations.len()`, `mst.len()`) son lectura pública. Para el - DID se sigue necesitando `minga status` (CLI con passphrase). -- **Refresh por polling cada 2s**: mismo pattern que - `nakui-explorer`/`nouser-explorer`. -- **3 stat cards** una por dimensión: - - Nodos AST (cyan) — fragments parseados del código. - - Atestaciones (verde) — firmas Ed25519 sobre los nodos. - - Claves MST (purple) — entradas del Merkle Search Tree. -- **Helper `stat_card(cx, label, value, description, accent, ...)`**: - fabrica una card con border-l colored + label tenue + número - grande (`px(28)`) + descripción. Reutilizable. -- **Header**: título dinámico (`Repo: · reload ms`) - + theme switcher derecha. -- **Error banner**: themed Banner::Error si el repo no abre. -- 2 tests: `load_snapshot_errors_on_missing_dir` (msg claro - cuando el dir no existe) + sanity del `RepoSnapshot::default`. - -Workspace: nueva entry en `members[]`. - -Smoke run del binario verificado: bootstrap completo OK, panic -esperado en open_window por falta de display. - -Beneficio operativo: -- Un usuario corre `minga init` + `minga ingest archivo.rs` desde - CLI, después abre `minga-explorer` y ve los counts crecer en - vivo cuando ingiere más archivos. -- Comparte theme switcher con `nakui-explorer` y - `nouser-explorer` — cualquier preset elegido se aplica - visualmente igual cross-app. -- `minga` deja de ser sólo CLI; gana presencia GUI sin tocar - el resto del módulo. - -Apps GUI integradas al stack themed: **4** (nakui-ui, nakui-explorer, -nouser-explorer, minga-explorer). - -### feat(nouser-explorer): integración al stack yahweh themed -Iter 10. `nouser-explorer` (la app paralela a `nakui-explorer` -para ver Mónadas via daemon nouser) tenía colors hardcoded -idénticos al patrón previo. Aplico el mismo refactor que se hizo -para `nakui-explorer` en iter 4: instala el theme global, migra -chrome a slots, usa los widgets `banner_themed` / `card_themed` / -`theme_switcher`. - -Cambios en `nouser-explorer`: -- **Nuevas deps**: `yahweh-theme`, `yahweh-widget-banner`, - `yahweh-widget-card`, `yahweh-widget-theme-switcher`. -- **`main()`**: `Theme::install_default(cx)` antes de - `cx.open_window`. -- **`render`**: 4 vars `let X = rgb(...)` (chrome) → theme slots - (`bg_app`/`fg_text`/`fg_muted`/`bg_panel`/`border`). -- **Header**: gana flex_row + theme switcher en la derecha (mismo - pattern que nakui-explorer). -- **`error_banner`**: pasa de div hardcoded a `banner_themed(cx, - Banner::Error, ...)` con override de padding (16/8) por - convención del header. -- **2 cards de Engine y Monad**: pasan de `div().flex().flex_col() - .p().mb().bg(card_bg).rounded().border_l_4().border_color()...` - a `card_themed(cx).border_l_4().border_color(accent)...`. -- **Acentos semánticos**: `accent_engine` (cyan, las "máquinas") - y `accent_data` (purple, las Mónadas) quedan locales — son - señales del dominio nouser, no del chrome. - -Tests: workspace stack intacto. nouser-explorer no tiene tests -propios (siempre fue una vista live del daemon, sin lógica -testable separada). - -Beneficio operativo: las dos apps explorer del repo -(`nakui-explorer` para event log + `nouser-explorer` para Mónadas) -ahora comparten la misma paleta themed + el mismo control de -switcher. Si un usuario las corre lado a lado, la consistencia -visual emerge sola. - -### feat(yahweh): caret blinking + slots ornament en theme + MetaApp full themed -Iters 8-9 combinadas. Tres mejoras pequeñas que cierran la -integración del theme: - -**1. Caret blinking en text-input** (`yahweh-widget-text-input`): -- Nuevo field `caret_visible: bool` que toggea cada 500ms. -- Nuevo field `_blink_task: Task<()>` mantiene el loop de blink - vivo y lo cancela al drop del widget. -- En `new()`, `cx.spawn(...)` arranca el loop: `timer.timer(500ms)` - + `this.update(...)` que toggea + `cx.notify()`. Si el update - falla (entity drop), break. -- En `render()`, caret `|` se dibuja sólo si - `is_focused && self.caret_visible`. Familiar feel del SO. - -**2. Slots ornament en yahweh-theme** (5 nuevos): -- `bg_input() -> Hsla` — bg sutil para fields editables. -- `bg_button() -> Hsla` + `bg_button_hover() -> Hsla` — controls - clickable secundarios. -- `accent_destructive() -> Hsla` — rojo para acciones peligrosas. -- `bg_destructive_hover() -> Hsla` — bg de hover sobre destructive. -- Implementados como **methods** del `Theme` (no fields del - struct), derivados via `ornament_slots(self.is_dark)`. Esto - evita modificar los 6 presets — el slot vive donde uno lo - invoca. - -**3. MetaApp ornament cleanup** (`yahweh-widget-meta-form`): -- 11 colores hardcoded `gpui::rgb(0x...)` migrados a slots del - theme: - - Sidebar menu items (selected/hover) → `bg_row_active` / - `bg_row_hover`. - - List row separator + button bgs → `bg_row_active` / - `bg_button()` / `bg_button_hover()`. - - Icon ✕ delete + hover → `accent_destructive()` / - `bg_destructive_hover()`. - - EntityRef selector hover/selected → `bg_row_active` / - `bg_row_hover`. - - EntityRef selector border → `theme.border` (slot existente). - - Form fallback input bg + submit button → `bg_input()` / - `bg_button()` / `bg_button_hover()`. - - Confirm modal hint subtitle + hovers de Cancel/Confirm → - `theme.fg_muted` / `bg_button_hover()` / `bg_destructive_hover()`. -- Pattern: `let X = theme.slot()` antes de las closures + `move |d| - d.bg(X)` en hover/when para que el cierre tome ownership. - -Antes de este commit MetaApp tenía la **paleta principal** themed -(iter 5) pero el ornament secundario (hovers, separators, botones -inline) seguía hardcoded. Ahora el theme switcher cambia -**absolutamente todo** el chrome del MetaApp en runtime. - -Tests: 117 verdes (sin cambios numéricos, pero downstream sigue -compilando). Smoke run de nakui-ui: bootstrap completo OK. - -Limitación restante: `nouser-explorer` todavía no migra al stack -yahweh themed — patrón idéntico a `nakui-explorer` aplicado pero -más nuevo. Próxima iter. - -### feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus -Iter 7 (mini-iter — el text-input ya estaba themed, faltaba sólo -el polish de focus visibility). Antes el border era siempre -`accent_strong` y el caret `|` siempre estaba presente — imposible -distinguir cuál input está activo en un form con varios fields. - -Cambios en `yahweh-widget-text-input`: -- **Border focus-aware**: cuando el input está focused, border = - `theme.accent_strong` (color vivo). Cuando no, border = - `theme.border` (color tenue del chrome). Se obtiene via - `self.focus_handle.is_focused(window)`. -- **Caret `|` sólo on focus**: cuando el input no tiene focus, se - muestra el texto plano sin caret. Reduce el "ruido visual" en - forms con muchos fields. -- `render` ahora usa el `Window` arg (antes `_w`) para chequear - focus. - -Sin cambios en API pública — todo es interno al `render`. El -binario no requiere migración. - -Tests: sin cambios (los tests del crate son struct constructors, -no rendering). Tests downstream del widget (`yahweh-widget-meta-form`, -`nakui-ui`) siguen verdes — el cambio es backward compatible. - -Beneficio operativo: -- Forms con 5+ fields ahora son navegables: el usuario ve cuál - input recibe sus teclas via el border highlighted. -- Cambio de theme afecta también a inputs (ya estaban themed; ahora - además respetan el `accent_strong` específico del preset - cuando focused, vs el `border` cuando no). - -Limitación pendiente: el caret `|` literal no parpadea (no hay -animation timer). Cuando emerja la necesidad, agregar via -`cx.spawn` con un loop de toggle. Por ahora el caret estático on -focus es suficiente signal. - -### feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime -Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed + -widgets que la consumen, faltaba el control UI para rotar entre -presets en vivo. Ahora hay un botón yahweh que muestra el theme -actual y al click avanza al siguiente. `nakui-ui` y `nakui-explorer` -lo incrustan en sus headers — un click cambia toda la paleta. - -Crate nuevo: `crates/modules/ui_engine/widgets/theme-switcher/` -(`yahweh-widget-theme-switcher`): -- **Deps**: `gpui` + `yahweh-theme`. Sin nada más. -- **`pub fn theme_switcher(cx: &mut App) -> impl IntoElement`**: - botón clickable con `id`, padding consistente (`px(8/4)`), - bg = `theme.bg_panel_alt`, hover = `bg_row_hover`. Muestra - `"Tema: ▸"` y al click hace - `Theme::set(cx, Theme::next_after(current.name))`. -- 2 tests `#[gpui::test]`: - - `switcher_constructs_with_theme_installed` — smoke: el - constructor lee el global y devuelve un IntoElement sin panic. - - `theme_set_changes_global` — verifica que `Theme::set` reemplaza - el global y que el siguiente `Theme::global` devuelve el nuevo. -- Dev-dep `gpui` con `test-support` para habilitar TestAppContext. - -Migración de consumers: -- **`nakui-explorer`**: nueva dep `yahweh-widget-theme-switcher`. - El header pasa de `div().px().py()...child(text)` a - `div().flex_row().child(div().flex_grow().child(text)).child(theme_switcher(cx))`. - El switcher queda alineado a la derecha vía `flex_grow` del label. -- **`yahweh-widget-meta-form`**: nueva dep. El sidebar header - ("Nakui" + 12px padding) gana el switcher con el mismo patrón - flex_row + flex_grow. - -Tests stack: 115 → **117** (+2 del switcher). Cada crate compila -individualmente. - -Beneficio operativo: -- Click en el switcher cambia toda la paleta en vivo: bg del app, - panels, banners (los que usan `_themed`), confirm modal, todo. -- 6 presets disponibles via `Theme::all()` (Nebula, Aurora, - Sunset, Flat Dark, Solarized Light, High Contrast). El switcher - cicla circularmente. -- Apps adoptantes del `Theme` heredan el switch sin esfuerzo. - -Decisión técnica: el handler usa `Theme::set(cx, ...)` que -invalida el global. GPUI marca todos los views como dirty y -re-renderea — los widgets que leen `Theme::global` en su `render` -ven el nuevo automáticamente. No requiere `cx.observe_global` -explícito en cada widget consumidor. - -Limitación: TextInput entities ya creadas no se actualizan visualmente -si el theme cambia los colors del input bg/border (esos colors -están hardcoded en `yahweh-widget-text-input`). Migrar text_input -al theme es una iter futura — bajo scope porque actualmente vive -suficientemente bien con sus defaults dark. - -### feat(yahweh-widget-meta-form): paleta del chrome migrada a `Theme::global(cx)` -Iter 5 de integración. El `MetaApp::render` tenía 7 vars locales -con colors hardcoded (`bg/panel/border/text/text_dim/accent/ -accent_active`) que se pasaban a las funciones internas -(`render_sidebar`/`render_main`/`render_list`/`render_form`/ -`render_entity_ref_selector`). Ahora salen del `Theme::global(cx)` -que el binario shell instala al boot. El `confirm_delete_banner` -también usa `themed_colors(Banner::Warning)` / `themed_colors(Banner::Error)` -para sus colors base. - -Cambios en `MetaApp::render`: -- 7 `let X = gpui::rgb(0x...)` → derive del theme: - - `bg` ← `theme.bg_app` (Background, soporta gradientes). - - `panel` ← `theme.bg_panel`. - - `border` ← `theme.border` (Hsla). - - `text` ← `theme.fg_text`. - - `text_dim` ← `theme.fg_muted`. - - `accent` ← `theme.accent`. - - `accent_active` ← `theme.accent_strong`. -- `toast_div` y `error_banner`: `banner(...)` → `banner_themed(cx, ...)`. - -Cambios de firma (internas, no API público): -- `render_sidebar` / `render_main` / `render_list` / - `render_entity_ref_selector` / `render_form` cambian Rgba → - Hsla en sus parámetros de color (Background donde aplica para - `panel`). Los métodos `bg/text_color/border_color` de gpui::Div - aceptan ambos via `Into`, así que el uso interno no cambia. - -Cambios en `render_confirm_delete_banner`: -- 6 colors hardcoded amber/red/gray → `themed_colors(Warning)` para - banner base, `themed_colors(Error)` para botón Confirm, - `theme.bg_panel_alt + fg_text` para botón Cancel. -- Cambiar de Theme ahora cambia toda la paleta del modal. - -Lo que **NO** migra esta iter (queda como ornament hardcoded; iter -futura si emerge la necesidad): -- Row hovers misceláneos en `render_list` (px 0x232a36 / 0x1f2630 - para selected/hover de filas). -- Borders sutiles entre filas (px 0x232a36). -- Bg de inputs custom (px 0x171a20). -- Bg de botones en `render_entity_ref_selector` (px 0x2c3540). -- Color rojo del icon `✕` de delete (px 0xd07070) y su hover - (px 0x4a2020). - -Estos son detalles ornamentales que un theme switcher real -querría integrar; los aislo para una pasada futura cuando esté -claro qué slots semánticos del theme conviene agregar (ej. -`bg_row_selected` distinto de `bg_row_hover`, `accent_destructive`, -etc.). - -`nakui-ui` shell ya instalaba `Theme::install_default(cx)` desde la -iter pasada — sigue siendo el contract entre el shell y el widget. -Smoke test del binario verificado: bootstrap completo OK, panic -esperado en open_window sin display. - -Tests stack: 115 verdes (sin cambio — los tests del widget no -acceden al render). - -Beneficio operativo: -- El theme switcher (cuando llegue) cambia toda la paleta principal - de `MetaApp` con 1 sola llamada `Theme::set(cx, ...)`. -- `MetaApp` y `nakui-explorer` comparten el mismo theme global en - un mismo proceso (si llegan a vivir juntos). -- Los `confirm_delete_banner` y los toasts del MetaApp respetan - is_dark: el contrast ajusta automatic. - -### feat(yahweh): theme integration en `banner` + `card` + `nakui-explorer` consume themed -Iter 4 de la integración. Los widgets `banner` y `card` ahora -ofrecen variants `_themed(cx, ...)` que leen `Theme::global(cx)`. -Las versiones sin theme se preservan para apps sin theme global. -`nakui-explorer` migra a versiones themed + `Theme::install_default` -al boot — el chrome hardcoded del explorer (5 variables `let bg = -rgb(...)`) sale del theme. - -Cambios en `yahweh-widget-card`: -- **Nueva dep**: `yahweh-theme`. -- **`pub fn card_themed(cx: &App) -> Div`**: devuelve [`card`] - pre-aplicado con `bg(theme.bg_panel)`. El caller sigue componiendo - con borders, accents, children. - -Cambios en `yahweh-widget-banner`: -- **Nueva dep**: `yahweh-theme`. -- **`pub fn banner_themed(cx: &App, kind, message) -> Div`**: - deriva `(bg, fg)` según `kind` + `theme.is_dark`: - - `Info`: `theme.bg_panel_alt` + `theme.accent`. - - `Success` / `Warning` / `Error`: hue fijo (verde/amber/rojo) - + lightness flippeada según `is_dark` (dark = bg low, fg high; - light = invertido). -- **`pub fn themed_colors(kind, theme) -> (Background, Hsla)`**: - helper público para callers que quieren computar el par sin - construir el div. -- 3 tests nuevos del derivation: dark/light lightness contrast, - kinds distinguidos por hue. - -Migración de `nakui-explorer`: -- Nueva dep `yahweh-theme`. -- `main()` llama `Theme::install_default(cx)` antes de open_window - (el theme default es Nebula). -- `render`: - - 5 `let bg/text/text_dim/card_bg/border = rgb(...)` colors - locales → `theme.bg_app/fg_text/fg_muted/bg_panel/border`. - - `card().bg(card_bg)` → `card_themed(cx)` (borra los locales). - - `banner(Banner::Error, ...)` → `banner_themed(cx, Banner::Error, ...)`. - - Los accents `accent_seed` / `accent_morphism` se preservan - locales: son **señales semánticas del log** (azul=seed, - verde=morphism), no chrome del app. - -Distribución de tests: 112 → **115** (+3 del banner derivation). -Workspace stack pasó por la migración sin errores. - -Beneficio operativo: -- Cambiar de Theme (Nebula → Aurora → Solarized Light, etc.) ahora - refleja en `nakui-explorer` automáticamente. Antes había que - buscar y reemplazar los hex codes uno a uno. -- Apps que adopten el patrón `_themed` heredan el switcher de - theme cuando emerja. - -Decisiones: -- **Hue fijo por kind**: Success siempre verde, Error siempre rojo, - etc. La lightness se ajusta al theme; el hue se mantiene como - invariante semántico cross-theme. -- **API dual**: `banner` (defaults) + `banner_themed` (theme). - Apps sin theme global pueden seguir con la versión simple. -- **Acentos semánticos del explorer (seed/morphism) NO migran**: - pertenecen al dominio del log, no al chrome. - -Próximas integraciones pendientes: -- `MetaApp` (en `yahweh-widget-meta-form`) tiene su propia paleta - hardcoded de 6 colors que podría migrarse al theme. Scope mayor - que esta iter; queda como candidato. -- Theme switcher widget (botón/menú en chrome para ciclar themes). - Cuando emerja la necesidad real. - -### feat(yahweh-widget-card): container card-shape compartido para timeline entries -Iteración 3 de la integración nakui ↔ yahweh. El "card visual" -pattern (padding consistente + rounded + flex_col + gap) que vivía -duplicado en cada timeline entry de `nakui-explorer` ahora es un -widget yahweh reusable. Sin acoplamiento a colores: el caller -decide bg/border/accent. - -Crate nuevo: `crates/modules/ui_engine/widgets/card/` -(`yahweh-widget-card`): -- **Dep**: solo `gpui`. App-agnostic. -- **`pub fn card() -> Div`**: container con `flex_col` + `px(12)` - + `py(8)` + `mb(4)` + `rounded(4)` + `gap(2)`. Sin colores - aplicados. -- El return es `Div` GPUI — el caller compone con `.bg(...)`, - `.border_l_4()`, `.border_color(...)`, `.child(...)`, hover, - on_click, etc., según necesite. -- 1 test smoke (constructor no panicea). - -Migración de `nakui-explorer`: -- Nueva dep `yahweh-widget-card`. -- Los 2 patterns de timeline entry (Seed y Morphism) pasan de: - ```rust - div().flex().flex_col().px(12).py(8).mb(4).bg(card_bg) - .rounded(4).border_l_4().border_color(accent).gap(2)... - ``` - a: - ```rust - card().bg(card_bg).border_l_4().border_color(accent)... - ``` -- Reducción ~7 calls → ~3 por entry; legibilidad mejor (la - intención "card with accent" emerge del nombre `card()`). - -Tests stack: 111 → **112 verdes** (+1 del crate card). Cada crate -afectado compila y testea individualmente. - -Beneficio operativo: -- Si `MetaApp` o cualquier futura app necesita un container - card-shape (ej. info card, expanded list row), `card()` está - ya disponible. -- Cambiar el padding/rounded/gap canónico = un cambio en un solo - lugar. -- El widget no impone colores → no fuerza una paleta y permite - themes diversos por app/contexto. - -### feat(yahweh-widget-banner): widget compartido para toasts/errores cross-app -Patrón visual común a `yahweh-widget-meta-form` (toast success + -error_banner) y `nakui-explorer` (error_banner): un `div` con bg -+ text colored según severidad. Antes vivía duplicado con colores -hardcoded en cada consumer; ahora hay un widget yahweh con presets -consistentes. - -Crate nuevo: `crates/modules/ui_engine/widgets/banner/` -(`yahweh-widget-banner`): -- **Dep**: solo `gpui` (sin nakui, sin runtime). Reusable por - cualquier app GPUI que necesite tiras de status. -- **`pub enum Banner`** con 4 variants: - - `Info` (azul tenue, mensajes neutros). - - `Success` (verde, confirmaciones). - - `Warning` (amber, llamadas de atención). - - `Error` (rojo, errores fatales). -- **Métodos `Banner::bg()` y `Banner::fg()`**: paleta hardcoded por - variant (sin tema dinámico todavía — cuando emerja, se - inyecta vía `yahweh-theme`). -- **`pub fn banner(kind, message) -> Div`**: constructor que - devuelve el div ya con padding/text_size defaults; el caller - puede agregar children, override pads/sizes, attach handlers. -- 2 tests sanity: ningún kind comparte bg, ningún kind comparte fg. - -Migración de consumers: -- **`yahweh-widget-meta-form`**: nueva dep `yahweh-widget-banner`. - El `toast_div` (Success) y `error_banner` (Error) en - `MetaApp::render` pasan de 2x6 líneas hardcoded a una llamada - a `banner(...)` cada uno (~12 líneas → 2). -- **`nakui-explorer`**: nueva dep. El error banner local pasa a - `banner(Banner::Error, e).px(16).py(8).text_size(12)` — - preserva el padding/size custom del header del explorer via - override builder. - -Tests stack: 109 → **111 verdes** (+2 del crate banner). - -Beneficio operativo: -- Si emerge un tercer consumer, importa la dep + 1 llamada. -- Cambiar la paleta de un kind = un cambio en un solo lugar - (ej. ajustar tono del Error o el contraste del Warning). -- Composición preservada: el `banner()` devuelve un `Div` directo, - el caller modifica con builder calls (`.child()`, `.px()`, - `.on_click()`, etc.) sin rewrap. - -Próximo candidato natural: el `confirm_delete_banner` de MetaApp -es Banner::Warning + 2 botones embedded. Cuando emerja un segundo -consumer de modal-style banners, extraer un widget compositivo -arriba del `Banner` base. - -### feat(yahweh): `MockBackend` público + tests E2E del widget con `gpui::TestAppContext` -Cierra el ciclo de testabilidad del widget metainterfaz. Hasta -ahora los tests del trait `MetaBackend` vivían como impl privada -en `backend.rs`; el widget no tenía forma de testear handlers -reales sin levantar `NakuiBackend` (que depende de event log + -Rhai + nakui-core). Ahora el mock es público y los tests del widget -lo consumen con `TestAppContext`. - -Cambios en `yahweh-meta-runtime`: -- **Nuevo módulo `pub mod testing`** con - `pub struct MockBackend`. Exporta: - - `MockBackend::new()` — vacío. - - `MockBackend::with_records(iter)` — pre-poblado con - `(entity, uuid, value)` tuples. - - `MockBackend::with_morphism(name, |inputs, params| -> Result)` — - builder para registrar handlers callable de morphism (sin - handler, `morphism()` rebota con error claro). - - Métodos de inspección `total_records()` / `records_for(entity)` - (último devuelve `Vec<(Uuid, &Value)>` sin clones). - - `impl MetaBackend` completo: seed/load/list/update/delete con - semantica documentada. -- **Tests del trait en `backend.rs` simplificados**: el `MemBackend` - duplicado se borra; los tests pasan a usar `MockBackend::new()` - importado de `crate::testing`. 8 tests del backend.rs intactos + - 9 tests propios del mock en `testing.rs`. -- Bajo `pub mod testing` (no `#[cfg(test)]`) deliberadamente: los - crates downstream pueden importarlo en sus dev/integ tests - vía `yahweh_meta_runtime::testing::MockBackend`. - -Cambios en `yahweh-widget-meta-form`: -- **Dev-dep nueva**: `gpui = { workspace = true, features = ["test-support"] }`. - Habilita `TestAppContext` para tests sin abrir window real. -- **`MetaApp::apply_action` ahora `pub`** (era privado). Necesario - para que los tests E2E lo invoquen desde fuera. La function ya - era el entry point de los click handlers internos; exponerla no - cambia el contract. -- **Nuevo archivo `tests/widget_with_mock_backend.rs`** con 4 tests - `#[gpui::test]`: - - `meta_app_constructs_with_mock_backend_and_initial_state`: - instancia `MetaApp` con records pre-poblados + - toast inicial; valida que la window construye sin panic. - - `open_view_action_does_not_panic`: invoca - `apply_action(OpenView)` real a través de - `window.update(cx, |meta, _, cx| ...)` → state machine corre - sin crash. - - `backend_state_visible_from_widget_perspective`: demuestra el - patrón "backend pre-poblado para fixtures" (typical para - screenshots / demos). - - `morphism_handler_can_be_registered_and_called_via_widget`: - `MockBackend::with_morphism` registra un counter callback; - `apply_action(Morphism)` lo dispara via `commit_morphism` - sin tocar nakui-core / Rhai. - -Helpers de tests: -- `customers_module()`: fixture local de un `Module` con entity - Customer + view list + view form. Reusable cross-test. - -Distribución de tests: -- `yahweh-meta-runtime`: 47 → **56** (+9 del nuevo testing - module). -- `yahweh-widget-meta-form`: 3 → **7** (+4 E2E reales). -- Total stack: **109 tests verdes** (56 runtime + 31 cards + 12 - nakui-ui + 3 explorer + 7 widget). - -Beneficio operativo: -- El widget tiene cobertura runtime real, no sólo type-check. -- Cualquier app que tome `B: MetaBackend` puede testarse con - `MockBackend` en sus dev-deps sin re-implementar el mock. -- Fixtures pre-pobladas habilitan demos/screenshots/CI con state - conocido. - -Limitaciones: -- `render()` no se invoca en los tests (requiere window context - más rico). Los tests verifican state machine, no pixels. Pixel - comparison (snapshot tests) es scope futuro si emerge la - necesidad. -- `apply_action(Morphism)` con un module que no declara - `nakui_module_dir` rebota antes de llamar al mock handler. El - 4to test acepta ambos outcomes (counter 0 o 1) — si en el futuro - agregamos un módulo de fixture con nakui_module_dir poblado, el - test puede aserta exactamente. - -### feat(yahweh-meta-runtime): promover `short_hash` y `preview_value` desde nakui-explorer -Continúa la integración de las apps nakui al stack yahweh. Los -helpers visuales que `nakui-explorer` tenía locales y son reusables -suben a `yahweh-meta-runtime/format` para que cualquier app pueda -consumirlos sin duplicar. - -Cambios en `yahweh-meta-runtime`: -- **`pub fn short_hash(h: &[u8; 32]) -> String`**: hex de los - primeros 4 bytes (8 chars). Útil para mostrar bundle/schema - hashes en UI sin quemar pantalla. -- **`pub fn preview_value(v: &Value, max: usize) -> String`**: - JSON one-liner truncado con `...` al final si excede `max` - chars. Edge case: `max < 3` devuelve los primeros `max` chars - sin sufijo. -- Re-exports en lib. -- 5 tests nuevos: 4 tests + 1 sanity para el caso `max < ellipsis`. - -Migración de `nakui-explorer`: -- Nueva dep `yahweh-meta-runtime` en Cargo.toml. -- Borrado helpers locales `short_uuid`, `short_hash`, - `preview_value` (~30 líneas). -- `use yahweh_meta_runtime::{preview_value, short_hash, short_uuid}`. -- Borrados 4 tests duplicados (los runtime los testea). - -Tests: -- `yahweh-meta-runtime`: 42 → **47** (+5 helpers nuevos). -- `nakui-explorer`: 7 → **3** (–4 duplicados; quedan los 3 - específicos: load_log, breakdown, missing_file). -- Resto del workspace intacto. - -Beneficio operativo: 3 helpers visuales centralizados. Cualquier -app nueva que muestre UUIDs/hashes/JSON-previews los importa sin -re-implementar la heurística de truncamiento. - -Pendiente arquitectural: el render del card timeline en -`nakui-explorer` (border-l-4 colored + flex_col + texto en niveles) -es un pattern reusable que también aparece en `yahweh-widget-meta-form` -(render_list filas). Cuando aparezca un tercer consumer de ese -pattern se extrae a un widget yahweh. - -### feat(brahman-cards): templates Nickel canónicos para cada body kind -Materializa el patrón "import + override" del brazo: hasta ahora -`BRAHMAN_CARDS_TEMPLATES_DIR` existía como mecanismo pero el repo -no shippeaba ningún template. Ahora hay 3 templates basic (uno -por body kind del CardBody) bajo -`crates/core/brahman-cards/templates/`: - -- **`ente_basic.ncl`** — Card runtime mínima: `payload="Virtual"`, - `supervision="OneShot"`, `schema_version=1`. Override típico: - `id` + `label`. -- **`monad_basic.ncl`** — agrupación semántica de archivos - (Mónada Nouser): metadata vacía, `dominant_lens="grid"` (lowercase - por convención serde rename_all). Override típico: `id`, `label`, - `members`, `cardinality`. -- **`ui_module_basic.ncl`** — descriptor UI con `entities=[]`, - `menu=[]`, `views={}`. Override típico: `id`, `label` y los - 3 payloads. - -Cada field override-able marcada `| default` (sin eso Nickel -rebota merge de strings/numbers no-iguales). - -API nueva en `lib.rs`: -- **`pub fn canonical_templates_dir() -> PathBuf`**: devuelve el - directorio de templates del crate (resuelto via - `CARGO_MANIFEST_DIR`). Útil para apuntar el env - `BRAHMAN_CARDS_TEMPLATES_DIR` en runtime/tests sin hardcoding - del path. -- Doc explica que para distribución del binary standalone (cuando - emerja), incluir templates como recursos via `include_dir!` o - instalar el directorio junto al ejecutable. - -5 tests E2E (`tests/templates.rs`) que cubren: -- `ente_basic` import + override `id`+`label` → Card body Ente - con `payload=Virtual` (default preserved). -- `monad_basic` import + override `id`+`label`+`cardinality` → - Card body Monad con members=[] y summary="" (defaults). -- `ui_module_basic` import + override de `id`+`label`+menu+views - → Card body UiModule con entities=[] (default). -- Sanity: import sin override → defaults `"TEMPLATE_ID"` / - `"TEMPLATE_LABEL"` pasan al wrapper sin error. -- Sanity: el path de `canonical_templates_dir()` apunta a un - directorio existente con los 3 archivos esperados. - -Helper de test `with_canonical_templates(F)` setea/restaura el -env localmente; cada test single-thread-safe. - -Tests suite brahman-cards: 26 → **31** verdes (+5). El resto del -workspace intacto. - -Beneficio operativo: -- Un usuario que quiera declarar un Card nuevo puede empezar con - un override de 2 líneas (`id` + `label`) en lugar de copiar el - shape full desde cero. -- Templates auto-documentan la convención `| default` para que - copiar uno y agregar fields propios "just works" en merge. -- El brazo sigue siendo agnostic — los templates son sólo - archivos `.ncl` resueltos via el import resolver Nickel; nada - hardcoded en código Rust. - -Limitaciones: -- No hay templates "ricos" tipo `crud_basic.ncl` que parametricen - por entity name. Nickel no expone funciones-templates de la - forma típica de templating engines; lo más cercano sería un - template con un field `entity_name | String` y references - internas via `me.entity_name`. Cuando aparezca el caso de uso - real (e.g., un módulo donde el patrón list+form es repetitivo), - se diseña el template paramétrico. -- `canonical_templates_dir()` resuelve via `CARGO_MANIFEST_DIR` — - funciona en `cargo` (test/run/build) pero no para un binary - instalado fuera del workspace. Para release distribution la API - necesitará un fallback (resources embedded o convención de - install path). - -### refactor(nakui-core): KCL → Nickel — `kcl_wrapper` reemplazado por evaluación in-process -Cierra el ciclo: el motor de validación de entities deja de -shellear el binario externo `kcl` y pasa a evaluar **Nickel -contracts** in-process via la dep `nickel-lang` (la misma que ya -usa `brahman-cards` para sus templates). Los 3 schemas de los -módulos sales/inventory/treasury migran de `.k` a `.ncl`. -Además se borran los 2 archivos `.k` doc-only del repo -(`ente-card/schema/card.k`, `ente-brain/schema/rule.k` — ambos -estaban marcados "REFERENCE ONLY. NOT LOADED"). - -Cambios en **nakui-core**: -- **Nueva dep**: `nickel-lang = "2.0.0"` (interfaz estable). -- **Borrado** `kcl_wrapper.rs` (43 líneas) — shellear el binario - desaparece. -- **Nuevo** `nickel_validator.rs`: - - `pub fn vet(schema_path, state, schema_name) -> Result<(), NickelError>` - evalúa `let bundle = (import "") in - (std.deserialize 'Json m%%""%%) | bundle.`. - - El state JSON va dentro de un raw string Nickel - (`m%%"..."%%`) y se deserialize via `std.deserialize 'Json`. - No embebemos el state como record literal Nickel directo - porque la sintaxis JSON usa `:` (Nickel records usan `=`). - - 5 tests propios cubriendo happy path + 4 fallure modes - (field missing, predicate fails, cross-field invariant - fails, optional field present/absent). -- **`executor.rs`**: - - `kcl_wrapper::vet` → `nickel_validator::vet`. - - `KclError` → `NickelError`. - - `ExecError::KclPre/KclPost/KclPostCreate` → `SchemaPre/Post/PostCreate` - (más neutro, ya no menciona KCL). - - `kcl_check` (privado) → `validate_entity`. - - `build_schema_bundle` ahora emite un archivo Nickel con - `(import "X") & (import "Y") & ...` en lugar de concatenar - bytes (cada `.ncl` es una expresión record completa, no - juntable como texto plano). -- **`manifest.rs`**: - - `effective_schemas` default `"schema.k"` → `"schema.ncl"`. - - `extract_schema_names` reescrito: ahora detecta keys - CapitalCase con 2 spaces de indent (convención de los - `schema.ncl`), no más patrón `schema X:` de KCL. - - Tests del extractor actualizados (1 test reemplazado por 2: - `_handles_nickel_record_top_level` + `_skips_let_bindings_and_lowercase`). - -Cambios en **schemas de módulos**: -- **`sales/schema.ncl`**: contracts Nickel para `Venta`. Usa - `std.contract.Sequence [record_contract, from_predicate]` - para combinar shape + invariante cross-field - (`total == cantidad * precio_unitario`). El patrón directo - `record | from_predicate` rebota con "missing definition" porque - el predicate evalúa el contract antes de que el value lo - populate; documentado en el comment. -- **`inventory/schema.ncl`**: `Stock`, `MovimientoStock`, - `TransferenciaStock` (esta última con cross-field - `source != dest` via Sequence). -- **`treasury/schema.ncl`**: `Caja`, `Movimiento`, - `Transferencia` (con cross-field via Sequence). -- Helpers locales en cada archivo: `positive_int`, - `non_negative_int`, `currency_iso`, etc. via - `std.contract.from_predicate`. -- Los 3 `schema.k` viejos **borrados**. -- `sales/nsmc.json` actualizado: paths `schema.k` → - `schema.ncl`. - -Cambios en **tests**: -- `sales.rs`, `inventory.rs`: `KclPost` → `SchemaPost`. -- `kernel_guards.rs`: `KclPostCreate` → `SchemaPostCreate`, - path del schema directo `treasury/schema.k` → - `treasury/schema.ncl`. -- `graph.rs`, `manifest_validation.rs`: tests que escriben - `schema.k` inline cambian a `schema.ncl` con sintaxis Nickel. -- `schema_versioning.rs`: refs `schema.k` → `schema.ncl`. - -Cambios documentales: -- **Borrado** `crates/core/ente-card/schema/card.k` (1 archivo, - REFERENCE ONLY documentado en su header). -- **Borrado** `crates/core/ente-brain/schema/rule.k` (REFERENCE - ONLY documentado en su header). - -Tests: -- **nakui-core**: 84 tests verdes (41 unit + 43 integration en - graph/event_log/manifest_validation/schema_versioning/ - inventory/sales/kernel_guards). Suite full pasa. -- **nakui-ui**, **brahman-cards**, **yahweh-***: sin cambios, - todos verdes. -- Total cubriendo el área: 174 tests. - -Beneficios: -- **Sin dep externa**: el binario `kcl` ya no es requisito de - runtime ni de tests. Build limpio en CI sin instalar KCL. -- **Errores en línea**: Nickel reporta contract violations con - caret pointing al field exacto del schema y el value que - falló. KCL daba mensajes textuales menos navegables. -- **Mismo motor que el brazo de cards**: una sola dependencia - Nickel para todo el repo (validación + templates de cards). -- **Sin tempfile JSON intermedio**: el state se evalúa - directamente en memoria; no hay `std::fs::write` por cada - validate. - -Limitaciones / decisiones: -- El comentario "REFERENCE ONLY" de los `.k` borrados ya estaba - marcado en sus headers; eran sólo notas de diseño para humanos. - La autoridad real (Rust validate methods) sigue intacta. -- La sintaxis Nickel `record_contract | from_predicate` no - funciona — hay que envolver en `std.contract.Sequence [record, - from_predicate]`. Documentado en cada schema y en el doc del - validator. - -**Pendientes restantes**: ninguno del refactor original. Los -yahweh + KCL + card.k cierran. Próximos pendientes salen de -nuevo trabajo (no del plan que arrastrábamos). - -### refactor(yahweh): Fase 2c — extracción del widget al crate `yahweh-widget-meta-form` -Cierra el refactor de UI: el widget render (forms, lists, modal de -delete, EntityRef selector, sidebar, key handlers) deja de vivir en -el binario nakui-ui y pasa a un crate yahweh nuevo, genérico sobre -`MetaBackend`. nakui-ui queda como un shell de bootstrap de 424 -líneas. - -Crate nuevo: `crates/modules/ui_engine/widgets/meta-form/` -(`yahweh-widget-meta-form`): -- **Deps**: gpui, yahweh-meta-schema, yahweh-meta-runtime, yahweh-theme, - yahweh-widget-text-input, serde_json, uuid. **Cero deps a nakui** o - brahman-cards — reusable por cualquier app. -- **`MetaApp`** público: estructura genérica con - `modules`, `backend: B`, `active`, `form_inputs`, `editing`, - `pending_delete`, `toast`, `load_error`. El bound `B: MetaBackend` - se propaga a todos los `impl MetaApp` y al `impl Render for - MetaApp`. -- **`MetaApp::new(modules, backend, initial_toast, initial_error, cx)`**: - constructor sin lógica de bootstrap. El caller pre-construye - modules + backend + cualquier mensaje inicial. La active view - default es la primera entry del menú del primer módulo. -- **Methods preservados** del original (rename simbólico): select_view, - open_edit, commit_seed, commit_morphism, commit_delete, apply_action, - list_rows, render_*, tick interno via WriteOutcome.post_status. -- **Helpers locales del widget**: `lookup_field` (path walker JSON - por la lista renderer), `append_compact_msg` (concatenador del - toast), `format_seed_toast` (decide "creado/actualizado/sin cambios" - según `WriteOutcome`). -- **3 tests funcionales puros**: `lookup_field`, `append_compact_msg`, - `format_seed_toast`. Tests con GPUI cx no son posibles sin un - TestAppContext setup; quedan implícitos vía type-check del trait - bound. - -Cambios en `nakui-ui` (shell): -- **main.rs**: 1959 → **424** líneas (78% reducción). Ahora sólo: - 1. Carga modules via `brahman_cards::load_cards_from_dir` + - `load_ui_modules` (filtra UiModule body, valida, dedup). - 2. Carga executors para módulos con `nakui_module_dir`. - 3. `NakuiBackend::open(...)` para inicializar el backend. - 4. `cx.open_window(...)` con `MetaApp::::new(...)` - como root view. -- **`use yahweh_widget_meta_form::MetaApp`** + dep nueva en - Cargo.toml. Los imports de yahweh-meta-runtime/schema desaparecen - de main (los consume el widget internamente). -- **Tests del shell**: 4 tests E2E que tocan nakui-core directamente - (event_log_replay, morphism_pipeline_real_sales_vender, - load_ui_modules x3). Los tests del NakuiBackend impl quedan en - `backend.rs` (8 tests). Los tests del widget viven en su propio - crate. -- **`backend.rs`**: sin cambios (NakuiBackend ya estaba aislado en - Fase 2b). - -Distribución final del refactor yahweh: -- `yahweh-meta-schema`: 8 tests (data puro). -- `yahweh-meta-runtime`: 42 tests (helpers + trait MetaBackend). -- `yahweh-widget-meta-form`: 3 tests (widget genérico). -- `brahman-cards`: 26 tests (loader unificado). -- `nakui-ui`: 12 tests (4 shell + 8 backend impl). -- **Total: 91 tests** cubriendo el área. - -Cada crate compila individualmente. El widget consume el trait sin -saber qué backend hay debajo; `nakui-ui` provee el trait wireado a -nakui-core; cualquier futuro shell (mock para tests, otro stack de -storage) puede reusar el widget sin cambio. - -Lo que NO hace Fase 2c: -- No mueve `format_seed_toast`/`append_compact_msg`/`lookup_field` - a `yahweh-meta-runtime`. Son lo bastante widget-flavored - (`SharedString` de gpui, decisiones de UX del toast, etc.) que - preferí dejarlos al lado del render. -- No introduce un `MetaApp::with_status` builder pattern. La - signature de `new` con 5 args es manejable; si crece, se refactor - después. -- No expone configuración del widget (theme override, layout - custom, etc.). Cuando emerja una segunda app que use el widget - con preferencias distintas, se agregan opts. - -**Pendientes**: -1. **KCL → Nickel**: kcl_wrapper en nakui-core reemplazado por - evaluación de Nickel contracts. Migrar los 3 schemas .k de - sales/inventory/treasury a .ncl. -2. **`card.k` eliminado** (REFERENCE ONLY documentado en su header). - -### refactor(yahweh): Fase 2b — `MetaBackend` trait + `NakuiBackend` + MetaUi consume el backend -Materialización del trait que diseñamos en charla. Tres pasos -combinados en un solo commit: - -**Step A** — trait + WriteOutcome en `yahweh-meta-runtime`: -- Nuevo módulo `backend.rs` con: - - `pub trait MetaBackend: 'static` con 6 métodos: - `list_records`, `load_record`, `seed`, `update`, `delete`, - `morphism`. Convención de ids como `Uuid` canónico (los - backends que internamente usan otros tipos mapean), `set+clear` - pre-computados por el caller (no double-roundtrip al store), - threshold `'static` sin Send/Sync (suficiente para handlers - GPUI single-threaded). - - `pub struct WriteOutcome { id, changed, post_status }` con - constructor `no_change(id)`. La UI usa `changed = 0` para - "sin cambios", `post_status` para concatenar mensajes - auto-emitidos por el backend (compact, etc.). -- 9 tests con un `MemBackend` mínimo (HashMap por - `(entity, uuid)`): seed/load round-trip, list/filter/order, - update set/clear/no-op, delete/missing, object-safety check. - -**Step B** — `NakuiBackend` en `nakui-ui/src/backend.rs`: -- Estructura que ownea `Arc>`, - `Option>>`, `BTreeMap>`, - `snap_path`, `snapshot_threshold`, `writes_since_compact`. -- `NakuiBackend::open(log_path, threshold, executors) -> (Self, OpenStatus)`: - abre log, carga snapshot, replay, auto-compact si threshold - cruzado; devuelve `OpenStatus { init_toast, load_error }` para - que el caller agregue al banner. -- `tick_compact()` privado que cada write public method invoca - tras éxito; devuelve `Option` que se mete en - `WriteOutcome.post_status`. -- `impl MetaBackend for NakuiBackend`: - - `seed`: WAL order (log first, store after), `tick_compact`, - devuelve `WriteOutcome { id: Some(uuid), changed: 1, post_status }`. - - `update`: si `set+clear` vacíos devuelve `WriteOutcome::no_change`; - si no construye `FieldOp::Set`+`FieldOp::Clear`, log Morphism - `ui.edit_record` con `params.fields/cleared`, store.apply, tick. - - `delete`: `FieldOp::Delete`, log Morphism `ui.delete_record`, - store.apply, tick. - - `morphism`: locks log + store, `execute_and_log_with_recovery`, - tick. `WriteOutcome { id: None, changed: ops.len(), post_status }`. -- Funciones `snapshot_path_for` y `maybe_compact_log` movidas acá - desde main.rs (ahora son detalle del backend). -- 7 tests del impl: round-trip via trait, set+clear, no-op edit - no escribe, delete/load, list_records, morphism sin executor da - error claro, threshold dispara snapshot. - -**Step C** — `MetaUi` consume el backend: -- Reemplaza fields `store` / `event_log` / `executors` / - `snap_path` / `snapshot_threshold` / `writes_since_compact` - por un único `backend: NakuiBackend`. -- `MetaUi::new` colapsa el wiring de persistencia en - `NakuiBackend::open(...)` — pasó de ~150 líneas a ~10 líneas. -- `commit_seed` ya no construye `LogEntry`/`FieldOp` directos: - - SEED → `self.backend.seed(entity, obj)`. - - EDIT → `self.backend.load_record + compute_field_delta + - compute_clear_fields → self.backend.update(set, clear)`. - - Devuelve `WriteOutcome` (reemplaza el viejo enum `CommitOutcome`). -- `commit_morphism` parsea inputs/params del form y delega a - `self.backend.morphism(...)`. -- `commit_delete` es one-liner: `self.backend.delete(entity, id)`. -- `tick_runtime_compact` eliminado (ahora interno al backend; el - msg viaja en `WriteOutcome.post_status`). -- `list_rows` queda como proxy `self.backend.list_records(entity)`. -- `validate_entity_refs` callsite usa cierre sobre - `backend.load_record` (en vez de `&Store`). -- Nuevo helper `format_seed_toast(entity, was_editing, &outcome)` - reemplaza el match sobre `CommitOutcome`. -- Imports limpiados: no más `nakui_core::delta::FieldOp`/`FieldPath`, - no más `nakui_core::event_log::*` en main.rs (sólo en tests E2E). - No más `Arc/Mutex` (vive en backend). - -Distribución de tests post-refactor: -- `yahweh-meta-runtime`: 33 → **42** (+9 trait tests con MemBackend). -- `nakui-ui`: 14 → **21** (+7 tests del NakuiBackend impl). -- `yahweh-meta-schema`: 8 (sin cambio). -- `brahman-cards`: 26 (sin cambio). -- Total: **97**. - -Build: cada crate compila individualmente. - -Nota sobre Fase 2b/c estado: -- ✅ Backend trait + impl + MetaUi usa backend. -- ⏭ Falta extraer los **widgets render** (form/list/modal/EntityRef - selector) de nakui-ui a un crate yahweh nuevo - (sugerencia: `yahweh-widget-meta-form`). Esa extracción ahora es - trivial: el render code ya consume sólo `&self.modules` + - `self.backend` (vía trait). Lo dejo para próximo commit. - -**Pendientes**: -1. **Fase 2c**: extraer widget render al crate yahweh - (`yahweh-widget-meta-form` o similar) — `MetaApp` - genérico, `nakui-ui` queda como ~50 líneas de shell con - `MetaApp::::new(...)`. -2. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de - Nickel contracts. -3. **`card.k` eliminado** (REFERENCE ONLY). - -### refactor(yahweh): Fase 2 — extraer helpers puros a `yahweh-meta-runtime` -Sigue de la Fase 1 (lift del schema a yahweh). Ahora extraemos los -**helpers puros** que cualquier widget renderer o backend ejecutor -necesita sobre el schema: parse, delta, validation, format. Sin -GPUI, sin acoplamiento a un backend específico. - -Crate nuevo: `crates/modules/ui_engine/libs/meta-runtime/` -(`yahweh-meta-runtime`): -- **Deps**: `serde_json`, `thiserror`, `uuid`, `yahweh-meta-schema`. - NO GPUI, NO nakui. -- **Módulos**: - - `parse.rs` — `parse_field_value(kind, raw)`, - `infer_param_value(raw)`, `resolve_param_value(name, raw, spec)`. - - `delta.rs` — `compute_field_delta(current, proposed)`, - `compute_clear_fields(current, to_clear)`. - - `refs.rs` — `validate_entity_refs(load: F, refs)` donde `F` - es un cierre `Fn(&str, Uuid) -> Option`. Decoupling vía - closure en lugar de trait — evita atar el crate a cualquier - backend específico (no hay `Store` trait acá), y los callers - pasan `|e, id| store.load(e, id)` trivialmente. - - `format.rs` — `human_label_for_record(value, id)`, - `render_value(opt_value)`, `value_to_input_text(value)`, - `short_uuid(id)`. -- **33 tests propios** en el crate nuevo (cubren todos los helpers - movidos + edge cases). - -Cambios en `nakui-ui`: -- **Nueva dep** `yahweh-meta-runtime` en `Cargo.toml`. -- **Imports**: agrega `use yahweh_meta_runtime::{...}` con todos los - helpers extraídos. Borrado el código local equivalente - (~200 líneas). -- **`validate_entity_refs` callsite**: pasa de - `validate_entity_refs(&*store, &refs)` a - `validate_entity_refs(|e, id| store.load(e, id), &refs)` — el - closure es ergonómico sobre cualquier `Store`. -- **Tests duplicados borrados** (~34 tests que ahora viven en - `yahweh-meta-runtime`): - - `parse_field_*` (text/number/boolean variants) - - `infer_param_value_*` - - `delta_*` (5 tests) - - `clear_fields_*` (3 tests) - - `validate_entity_refs_*` (5 tests) - - `resolve_param_*` (6 tests) - - `parse_field_entity_ref_*` (4 tests) - - `human_label_*` (3 tests), `render_value_*`, - `value_to_input_text_inverse_of_parse` -- **Tests que se quedan en nakui-ui** (runtime-específicos): - - `lookup_field_simple_and_nested` — helper local del list renderer. - - `append_compact_msg_handles_both_branches`, - `runtime_compact_cycle_resets_counter_after_threshold`, - `snapshot_path_for_replaces_extension`, - `maybe_compact_log_*` (3) — wiring de persistencia a EventLog. - - `load_ui_modules_via_brahman_cards_*` (3) — integración con el - brazo de cards. - - `value_to_input_then_parse_round_trip` — round-trip del par - `value_to_input_text + parse_field_value` (toca ambos lados). - - `event_log_replay_restores_memory_store`, - `morphism_pipeline_executes_real_sales_vender`, - `event_log_replay_handles_full_crud_cycle` — E2E nakui-core. - -Distribución de tests: -- `nakui-ui`: 48 → 14 (los 34 movidos viven en runtime). -- `yahweh-meta-runtime`: 33 (nuevos). -- `yahweh-meta-schema`: 8 (sin cambio). -- `brahman-cards`: 26 (sin cambio). -- Total cubriendo el área: 81. - -Build: cada crate afectado compila y testea limpio individualmente. -Workspace build full no se completó esta corrida por OOM al -compilar `surrealdb-core` (problema ambiental no relacionado al -refactor). - -Lo que NO hace Fase 2: -- No mueve los widgets render (`render_form`/`render_list`/ - `render_entity_ref_selector`/`render_confirm_delete_banner`) a - yahweh — eso es Fase 2b/3, requiere diseñar el `MetaBackend` - trait porque las render functions tocan el state de `MetaUi` - (form_inputs, pending_delete, executors). - -**Pendientes** (orden): -1. **Fase 2b**: extraer widget render a un crate yahweh nuevo - (sugerencia: `yahweh-widget-meta-form`). Requiere diseñar - `MetaBackend` trait. -2. **Fase 3**: thin shell — `nakui-ui` queda reducido a una impl - de backend wireada a `nakui-core`. -3. **KCL → Nickel** + **card.k eliminado**. - -### refactor(yahweh): Fase 1 — `nakui-ui-schema` → `yahweh-meta-schema` -Primer paso del refactor yahweh. El schema de UI declarativa -(entities, menús, listas, formularios, acciones) vivía bajo -`crates/modules/nakui/ui-schema/` y se llamaba `nakui-ui-schema` — -un nombre que sugería acoplamiento con Nakui que en realidad no -existe (el crate sólo depende de `serde`/`serde_json`/`thiserror`). -Lo movemos a yahweh para que sea consumible por cualquier app de UI -metadata-driven sin hacer pasar la dep "rara" por nakui. - -Cambios mecánicos: -- **`git mv`**: `crates/modules/nakui/ui-schema/` → - `crates/modules/ui_engine/libs/meta-schema/`. -- **Cargo.toml del crate movido**: - - `name = "nakui-ui-schema"` → `name = "yahweh-meta-schema"`. - - Description actualizada: "Yahweh — meta-schema: descriptores - declarativos de UI ... independiente del backend". -- **Workspace `Cargo.toml`**: la entry del members[] pasa de - `crates/modules/nakui/ui-schema` a - `crates/modules/ui_engine/libs/meta-schema` (en su sección - yahweh, no en la sección nakui). -- **`brahman-cards`**: - - Cargo.toml: dep path/name a `yahweh-meta-schema`. - - lib.rs: `pub use nakui_ui_schema::Module` → - `pub use yahweh_meta_schema::Module`. - - readers.rs: comment + doc-link al nuevo nombre. -- **`nakui-ui`**: - - Cargo.toml: dep path/name a `yahweh-meta-schema`. - - main.rs: `use nakui_ui_schema::{...}` → - `use yahweh_meta_schema::{...}`. -- **Self-test del crate movido** - (`tests/example_modules.rs`): `nakui_ui_schema` → `yahweh_meta_schema`, - y se rebasa el path del repo root (5 niveles arriba ahora, era 4). - -Cambios documentales: -- **Doc de crate** (`lib.rs`): "Schema declarativo de la metainterfaz - Nakui" → "Schema declarativo de la metainterfaz (yahweh - meta-schema)" + "backend-agnostic" en la filosofía. La sección - Persistencia universal pasa de "el runtime conecta cada vista al - `nakui_core::store::Store`" a un wording neutro: "el runtime que - consume este schema conecta vistas a su backend". -- **Doc del field `Module.nakui_module_dir`**: ahora marcado como - "path opaco al backend, lo interpreta el runtime concreto". Se - describe la convención actual de Nakui (nsmc.json + KCL + Rhai) - como ejemplo, no como contrato del schema. El nombre del campo - se mantiene por compat con módulos ya escritos; agregado - `#[serde(alias = "backend_module_dir")]` para que un futuro - rename no rompa los actuales. - -Tests: -- yahweh-meta-schema (crate movido): 13 tests propios siguen - verdes tras el path rebase. -- brahman-cards: 26/26 verdes (17 integration + 9 nickel). -- nakui-ui: 48/48 verdes. -- Workspace build verde. - -Lo que NO hace Fase 1: -- No mueve los widgets de UI (form/list/modal/EntityRef selector) - a yahweh — eso es Fase 2. -- No introduce un trait `MetaBackend` para desacoplar la lógica - de runtime de Nakui — eso es Fase 3. -- No renombra el field `nakui_module_dir`. Se hará cuando aparezca - un segundo backend que también lo necesite. - -**Pendientes** (orden): -1. **Fase 2**: extraer widgets render (form/list/modal/EntityRef - selector + helpers parse_field_value/render_value/etc.) a un - nuevo crate `yahweh-widget-meta-form` (o nombre similar). -2. **Fase 3**: trait `MetaBackend` + thin shell — `nakui-ui` queda - reducido a una impl de backend wireada a `nakui-core`. -3. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de - Nickel contracts. -4. **card.k eliminado** (REFERENCE ONLY). - -### feat(nakui-ui): migrar consumer al brazo unificado `brahman_cards::load_cards_from_dir` -Primera consumer migration del brazo. `nakui-ui` ya no llama a -`nakui_ui_schema::load_modules_from_dir` directamente — pasa por -`brahman_cards::load_cards_from_dir` y extrae el variant `UiModule` -del `CardBody` de cada Card. Beneficios concretos: - -- **Soporta `.ncl` además de `.json`**: el usuario puede dropear un - `card.ncl` (con templates Nickel + merge) en cualquier subdir y - el runtime lo levanta automáticamente. El layout legacy - `examples/nakui-modules//module.json` sigue funcionando vía - los filenames default `[card.ncl, card.json, module.ncl, module.json]`. -- **Cards de otros body kinds (Ente/Monad) se skipean limpio**: - si el dir contiene Cards no-UiModule, se reportan en un toast - informativo en lugar de fallar la carga. - -Cambios en `brahman-cards`: -- **Nuevo `load_cards_from_dir(dir)`** + variante con readers/filenames - custom. Walkea subdirs (orden lexicográfico), busca el primero de - `DEFAULT_CARD_FILENAMES`, dispatcha al reader. Subdirs sin ningún - filename matching se skipean silenciosamente (permite assets/fixtures - sueltos al lado de los cards). Errores per-file se propagan loud - (sin ocultar corrupción). -- **`pub const DEFAULT_CARD_FILENAMES`**: lista canónica probada en - orden. `card.ncl` tiene prioridad sobre `card.json` y sobre los - legacy `module.*`. -- **4 tests nuevos del helper**: walk + skip de subdirs sin - card, prioridad ncl > json, propagación loud de errores per-file, - custom filenames. - -Cambios en `nakui-ui`: -- **Nueva dep** `brahman-cards` en `Cargo.toml`. -- **Nuevo helper `load_ui_modules(dir) -> (Vec, Vec)`** - que envuelve `brahman_cards::load_cards_from_dir`, filtra a - UiModule body, valida cada Module con su `validate()`, ordena - por id, y detecta duplicados. El callsite en `MetaUi::new` pasa - a usarlo y al ver Cards skipped emite un toast informativo. -- **3 tests nuevos**: - - `load_ui_modules_via_brahman_cards_returns_ui_modules_and_skips_others` - — verifica que un dir con UiModule + Ente carga el primero y - reporta el segundo en `skipped`. - - `load_ui_modules_via_brahman_cards_rejects_invalid_module` — - `Module::validate()` se sigue aplicando (menu apuntando a view - inexistente rebota). - - `load_ui_modules_detects_duplicate_id` — dos UiModule con - mismo id rebotan con mensaje claro. - -Tests totales: -- `brahman-cards`: 22 → 26 (+4 helper directorio). -- `nakui-ui`: 45 → 48 (+3 e2e migración). -- Workspace build verde. - -Lo que NO cambió: -- `nakui_ui_schema::load_modules_from_dir` se mantiene intacto (sus - propios tests lo siguen usando, y otros consumers futuros podrían - preferir su error-typing más específico). La migración es opt-in: - `nakui-ui` usa el brazo, ui-schema sigue siendo una API válida. -- Layout actual de `examples/nakui-modules//module.json` no - requiere cambio. Un usuario puede convertir cualquier módulo a - `card.ncl` sin tocar el dir layout. - -**Pendientes para próximos commits** (orden): -1. **Yahweh refactor**: lift del MetaUi runtime a - `crates/modules/ui_engine/` para reuso. El brazo + canónico ya - estables, ahora puede extraerse el meta-form widget genérico. -2. **KCL → Nickel**: kcl_wrapper reemplazado por Nickel contracts; - los 3 schemas .k de nakui modules pasan a .ncl. -3. **card.k eliminado** (es REFERENCE ONLY documentado). - -### feat(brahman-cards): Nickel reader + templates con merge nativo (V2) -Sigue al V1 (readers JSON). Ahora el brazo acepta inputs `.ncl`: -los evalúa via `nickel-lang` 2.0, exporta a JSON, y dispatcha por -los mismos readers JSON estándar. Un `.ncl` puede producir -cualquier `CardBody` siempre que su shape sea reconocida. Los -templates funcionan con los `import` + `&` merge nativos de -Nickel — el brazo no inventa una mecánica paralela. - -Cambios: -- **Dep `nickel-lang = "2.0.0"`** (interfaz estable, no - `nickel-lang-core` que es internal/inestable). Compila clean - pero suma ~1 min al build cold del crate. -- **Nuevo módulo `nickel_eval.rs`** con `eval_nickel_file(path) -> - Result`. Errores tipados: - `Io`, `Eval`, `Export`, `JsonReparse` — el mensaje de Nickel se - formatea como texto plano (sin ANSI) para que sea legible en - logs y toasts. -- **`load_card_with` añade `"ncl"`**: lee archivo → eval Nickel → - exporta a JSON → parsea de vuelta a Value → dispatch a los - readers JSON. Pipeline simétrico a `"json"`. -- **`CardLoadError::Nickel(NickelEvalError)`**: el error de - Nickel se propaga limpio al error público del brazo. -- **Resolución de imports**: - - El parent dir del input se agrega como import path → `import - "./template.ncl"` resuelve sin config. - - El env `BRAHMAN_CARDS_TEMPLATES_DIR` (constante exportada - `BRAHMAN_CARDS_TEMPLATES_ENV`) agrega un registry global → - `import "ui_module_minimal.ncl"` desde cualquier ubicación. - - No hay magic resolución por kind. El autor del Card decide - qué template importa. - -**Convención obligatoria de templates** (documentada en -`nickel_eval.rs`): las fields que el usuario va a sobrescribir -deben marcarse `| default` (o `| optional`). Sin ese marker -Nickel rechaza el merge de strings/numbers no-iguales con la -misma prioridad. Patrón canónico: - -```nickel -# template ui_module_basic.ncl -{ - id | String | default = "TEMPLATE_ID", - label | String | default = "TEMPLATE_LABEL", - ... -} - -# uso concreto -let base = import "ui_module_basic.ncl" in -base & { id = "my_id", label = "Mi Label" } -``` - -9 tests nuevos en `tests/nickel.rs`: -- `eval_nickel_file_returns_value_for_valid_input` — happy path. -- `eval_nickel_file_surfaces_evaluation_error` — variant `Eval` - con path + message. -- `load_card_dispatches_ncl_to_ui_module_variant` — pipeline - e2e a UiModule. -- `load_card_dispatches_ncl_to_ente_variant` — pipeline e2e a - Ente. -- `template_merge_overrides_id_and_label_only` — el caso del - user: template + override de id+label, resto del template - intacto. -- `template_resolves_via_env_registry` — uso del env como - registry global. -- `load_card_wraps_nickel_error_in_card_load_error` — wrap - limpio del error. -- `nickel_contract_violation_caught_at_eval_time` — value-add - concreto: `id | String = 42` falla en eval, no en deserialize - ni aguas abajo. -- `ncl_evaluating_to_unknown_shape_returns_no_matching_reader` - — sanity de coherencia con dispatcher JSON. - -22 tests en total en `brahman-cards` (13 JSON V1 + 9 Nickel V2). -Workspace build verde tras la dep nueva. - -**Lo que NO hace V2** (sigue pendiente): -- No migra consumers — `nakui-ui` sigue cargando con - `nakui_ui_schema::load_modules_from_dir`. La migración a - `brahman_cards::load_card` queda para después. -- No define un set canonical de templates en el repo (algo - como `templates/ente_basic.ncl`, `templates/ui_module_minimal.ncl`). - Eso emerge cuando aparezcan los primeros casos de uso reales - donde dos cards comparten estructura. -- No hace cross-validation entre template + override (ej: - detectar que un override saca un campo required del template). - Nickel ya lo hace via contracts si el template tiene un schema. -- No expone una API streaming (load N cards en paralelo). El - use case actual es one-shot al boot. - -**Pendientes para próximos commits** (orden): -1. Migrar consumers (`nakui-ui` consume `brahman_cards::load_card`). -2. Yahweh refactor: lift del MetaUi runtime a `crates/modules/ui_engine/`. -3. KCL → Nickel: kcl_wrapper reemplazado por evaluación de Nickel - contracts; los 3 schemas .k de nakui modules pasan a .ncl. -4. card.k eliminado (es REFERENCE ONLY documentado). - -### feat(brahman-cards): brazo unificado V1 — readers JSON + estructura canónica -**Pivote arquitectónico** decidido en charla: Brahman maneja varios -formatos legítimos de "Card" (cada formato vive en su crate origen y -conserva su shape público), y un **único brazo** los lee, completa -desde templates si vienen simplificados, y los proyecta a UNA sola -estructura interna canónica que consumen UI runtime / storage / DHT / -wire. Agregar un formato nuevo = agregar un reader, sin tocar -consumers. - -**V1 en este commit**: estructura canónica + readers para los 3 -formatos JSON existentes en el monorepo. Sin Nickel todavía (aislado -para próximo commit). - -Crate nuevo `crates/core/brahman-cards/`: -- **`Card { id, schema_version, lineage, label, extensions, body }`**: - wrapper común con identidad legible + extensiones forward-compat. - `id` como String (no `Ulid`) porque cada body variant usa un tipo - de id distinto (Ulid para Ente/Monad, slug human-friendly para - UiModule). PartialEq omitido del derive porque `MonadManifest` y - `nakui_ui_schema::Module` no lo implementan en sus crates origen. -- **`CardBody`** enum etiquetado `kind`: - - `Ente(brahman_card::Card)` — entidad runtime con - payload/soma/supervision. - - `Monad(nouser_card::MonadManifest)` — agrupación semántica de - archivos. - - `UiModule(nakui_ui_schema::Module)` — descriptor de UI con - entities/views/menu. - - Convención: agregar variant nuevo + reader; los consumers que - sólo manejen algunos hacen `match { Ente(..) => ..., _ => skip }`. -- **`trait CardReader`**: `name()` + `can_read(&Value) -> bool` + - `read(Value) -> Result`. El dispatcher prueba en orden y - delega al primero que matchee. -- **3 readers concretos** (en `readers.rs`): - - `EnteJsonReader` — heurística: `payload` Y `supervision` - presentes simultáneamente. - - `MonadJsonReader` — heurística: `members` Y `cardinality`. - - `UiModuleJsonReader` — heurística: `entities` Y `views` Y - `menu`. El más específico, va primero en `default_readers()`. -- **Entry points**: - - `load_card(path)` — abre archivo, dispatcha por extensión, dentro - de JSON prueba los readers default. - - `load_card_with(path, readers)` — variante con set custom para - apps que quieren restringir formatos. -- **Errores tipados** vía `CardLoadError`: `Io`, `JsonParse`, - `NoMatchingReader`, `ReaderFailed { reader, message }`, - `UnsupportedExtension { ext, supported }`. - -13 tests integration: -- 3 detection tests (cada reader matchea sólo su shape, rechaza los - otros 2 + non-object). -- 3 dispatch+projection tests (cada formato JSON cargado produce el - variant esperado con campos del wrapper bien derivados). -- 2 negative cases (NoMatchingReader, non-object input). -- 1 sanity de orden (UiModule gana cuando el shape acepta múltiples - readers — defiende el contrato de orden documentado). -- 1 e2e desde disco con `load_card_with`. -- 1 unsupported extension. -- 1 custom reader set (restringir a sólo Ente). -- 1 documented invariant (extensions vacío en V1; si cambia, este - test se rompe como signal). - -13/13 verdes. Workspace build verde tras agregar el crate al -`members[]` del workspace Cargo.toml. - -**Lo que NO hace V1** (explícito): -- No carga Nickel — próximo commit. La dep `nickel-lang-core` queda - aislada para no inflar este commit. -- No define templates — los templates Nickel se diseñan junto al - reader Nickel (necesitan `merge` nativo de Nickel para fusionar - override + base). -- No migra consumers. `nakui-ui` sigue cargando `module.json` con - `nakui_ui_schema::load_modules_from_dir` directo. La migración a - `brahman_cards::load_card` viene cuando V1 + Nickel + templates - estén estables. -- No mueve los `extensions` del input a `Card.extensions` — los crates - origen ya tienen sus propios `extensions` internos (`#[serde(flatten)]`). - Documentado como decisión consciente. - -**Pendientes para próximos commits** (orden): -1. Reader Nickel + template merge. -2. Migrar consumers (`nakui-ui` consume `brahman_cards::load_card`). -3. Yahweh refactor: lift del MetaUi runtime a `crates/modules/ui_engine/` - (esperando hasta que el brazo + canónico estén estables). -4. KCL → Nickel: kcl_wrapper reemplazado por evaluación de Nickel - contracts; los 3 schemas .k de nakui modules pasan a .ncl. -5. card.k eliminado (es REFERENCE ONLY documentado). - -### feat(nakui-ui): validación cross-field del EntityRef (existence en store) -Cierra otro pendiente. Hasta ahora `parse_field_value(EntityRef, raw)` -sólo validaba **forma** (UUID parseable + trim de whitespace) — un -UUID válido pero inexistente en el store pasaba silenciosamente al -log/store, dejando dangling references. Ahora validamos también -**existencia** contra la entity declarada en `FieldSpec.ref_entity`. - -Cambios: -- **Nuevo helper `validate_entity_refs(store, refs)`**: - - `refs: &[(label, target_entity, uuid)]`. - - Loop fail-fast: primer record ausente → error con label - legible + UUID corto + target entity en el msg - `"campo 'Stock': record abc12345 de 'Stock' no existe en el store"`. - - Pure (toma `&S: Store`), totalmente testable sin GPUI. -- **Wireup en `commit_seed`**: - - Durante el parse loop, cuando un field es EntityRef + tiene - `ref_entity` declarado + value parseado a UUID, lo encolamos - en `entity_refs: Vec<(String, String, Uuid)>`. - - Después del parse loop (antes del seed/edit branch), si - `entity_refs` no está vacío, una sola toma del store lock - para validar todos via el helper. - - Falla early: ningún log entry, ningún apply. -- **Cobertura**: - - SEED path: alta nueva con EntityRef → validamos antes de - `Seed { data }`. - - EDIT path: edit con EntityRef → validamos antes de calcular - delta. Una optional empty (que iría a clear) no cuenta como - EntityRef (raw vacío skipea el push). - - Morphism inputs: NO se duplica acá. `Executor::compute` ya - valida cada input via `store.load(...).ok_or(EntityMissing)` - antes de correr el script Rhai. Documentado en el doc del - helper. - -5 tests nuevos: -- `validate_entity_refs_passes_when_all_records_exist` — happy path. -- `validate_entity_refs_fails_on_first_missing` — fail-fast con - msg que incluye entity + UUID corto. -- `validate_entity_refs_uses_label_not_entity_in_msg` — el label - legible (ej: "Stock origen") aparece en el error, no la entity - desnuda. -- `validate_entity_refs_empty_list_is_ok` — lista vacía es Ok. -- `validate_entity_refs_distinguishes_target_from_other_entities` — - un UUID que existe bajo Customer pero NO Stock falla la - validación contra Stock. - -45 tests verdes en nakui-ui (+5). Workspace build verde. - -Comportamiento esperado: -- **Selector clickable es happy path**: el dropdown sólo lista - records existentes, así que clickearlo nunca debería disparar - el error. Sólo dispara con paste manual de UUID que no existe - o records borrados después de la selección (timing race). -- **Optional empty no se valida**: si el field es EntityRef - optional y el form lo deja vacío, lo manejamos como "no value" - (skipea el push a `entity_refs`); la lógica de Clear se - encarga del resto. -- **Lock contention**: una sola toma del store lock por - `commit_seed`, no una por field. La validación es O(refs) reads. - -Pendientes restantes: -- **Validación KCL del record post-edit** antes de emitir Set/Clear - (hoy `ui.edit_record` no pasa por `Executor::compute`). -- **EntityRef cross-module** (referenciar records de OTRO módulo - por nombre, no sólo por entity local). - -### feat(nakui-core,nakui-ui): FieldOp::Clear — borrar values vía form vacío -Cierra el último pendiente de UX del round. El edit no podía -"borrar" un value vaciando el input — empty optional fields -hacían `continue` en `commit_seed`, así que el current value -quedaba intacto. Para honrar el intent del usuario ("este field -ya no aplica") necesitábamos un FieldOp explícito que remueva -la key del map. - -Cambios en **nakui-core** (la variante es semántica del kernel, -no específica de la UI): - -- **`delta::FieldOp::Clear { path }`** — nueva variante. - Distinta de `Set { value: Null }`: Clear borra la clave; Set - Null deja la clave con valor literal `null`. Importa para - downstream que diferencia "ausente" vs "presente como null" - (ej: serde con `skip_serializing_if = "Option::is_none"`). -- **`capability_token`** — Clear devuelve `entity.field`, - mismo shape que Set. Una capability `writes: ["Customer.notes"]` - autoriza tanto Set como Clear sobre ese field. -- **`simulate_on`** — Clear remueve la key del Object si el - state es Some(Object). Skip silente si el state es None - (deleted) o no-objeto. -- **`MemoryStore::apply_dry_run`** — Set y Clear comparten - pre-condición (record padre existe + es objeto). Pattern - combinado con `|`. -- **`MemoryStore::apply`** — Clear hace `map.remove(field)`. - Field ausente = no-op silencioso (post-state idéntico). -- **`SurrealStore::apply_dry_run`** — Set/Clear combinados. -- **`SurrealStore::apply`** — Clear emite - `UPDATE type::thing UNSET `. El field name viene de - un FieldSpec validado upstream; SurrealQL no soporta binding - de identifiers, así que va inline (con la advertencia - documentada en el comment). -- **`Executor` capability check** — Set/Clear comparten match - (mismo token shape, misma resolución a binding role). -- **Conservation rules** (en `check_conservation`) NO consideran - Clear — sólo Set. Documentado: morphism authors que querían - clear de un field con conservation tienen que ser cuidadosos; - KCL post-checks pueden capturar violations. - -Cambios en **nakui-ui**: - -- **`commit_seed` loop** acumula `to_clear: Vec` con - los nombres de fields optional empty (en lugar de hacer - `continue` silencioso). -- **EDIT branch**: - - Computa `set_delta` (igual que antes) + `clear_fields` via - nuevo helper `compute_clear_fields(current, to_clear)`. - - Helper filtra a sólo los fields que actualmente tienen - valor non-null — Clear de un field ausente o ya null no - se emite (sería no-op semántico). Preserva el orden del - input para estabilidad del log entry. - - Construye `ops` combinando Set + Clear. - - NoChange ahora requiere AMBOS vacíos (set_delta y - clear_fields). - - `params` del log entry incluye `cleared: ["field1", ...]` - sólo si non-empty (preserva la shape `fields:` para - edits sin clears). - - `CommitOutcome::Updated.changed = sets + clears` para - que el toast `"actualizado X (N campo(s))"` siga siendo - preciso. - -Tests nuevos: -- **delta.rs**: `simulate_clear_removes_field`, - `simulate_clear_then_set_same_field_keeps_set`, - `clear_capability_token_matches_set_shape`. -- **store.rs**: `apply_clear_removes_field_key`, - `apply_clear_on_absent_field_is_noop`, - `dry_run_rejects_clear_on_missing_record`, - `dry_run_rejects_clear_on_non_object`. -- **nakui-ui main.rs**: `clear_fields_skips_absent_and_null`, - `clear_fields_preserves_input_order`, - `clear_fields_empty_when_current_is_null`. - -34 tests verdes en nakui-core (+7), 40 en nakui-ui (+3). -Workspace build verde. E2E del morphism real -`morphism_pipeline_executes_real_sales_vender` intacto — `vender` -no usa Clear. - -Implicaciones: -- **El log puede crecer con entries `ui.edit_record` que sólo - tienen `cleared: [...]`** sin `fields`. Esperado y esperable. -- **Replay**: las entries con Clear se aplican en orden via - `store.apply(&ops)`. La semantic es deterministic. -- **Si un módulo tiene KCL invariants sobre la presencia de un - field**, el usuario podría romper el record vaciando ese - field via UI. Hoy esto NO se chequea — `ui.edit_record` es - un morphism manual que no pasa por `Executor::compute`. Si - esto es un problema, el camino futuro es validar contra el - KCL del entity al submit (otro pendiente). - -Pendientes restantes: -- **Validación cross-field** (ej: UUID del EntityRef existe en - la entity referida). -- **Validación KCL del record post-edit** antes de emitir Set/Clear. - -### feat(nakui-ui): snapshot/compaction durante runtime cada N writes -Cierra el último pending del round de persistencia. Antes el compact -sólo corría al startup — para una sesión larga con muchas escrituras, -el log crecía sin tope hasta el próximo restart, y el siguiente boot -pagaba el costo lineal del replay. - -Cambios: -- **Nuevos fields en `MetaUi`**: - - `snap_path: PathBuf` — cacheado del init para que el tick no - tenga que recomputarlo. - - `snapshot_threshold: usize` — leído del env en `new()` y - cacheado. `0` desactiva runtime compact (mismo env y - semantic que el threshold de startup). - - `writes_since_compact: u64` — contador que incrementa por cada - write efectivo y se resetea cuando el threshold dispara - `maybe_compact_log`. -- **Nuevo método `tick_runtime_compact()`**: - - Early return si `threshold == 0`. - - Increment + check vs threshold. - - Si cruza: lock log + store, llama `maybe_compact_log`. - - **Si compactó OK**: counter = 0, devuelve msg. - - **Si `maybe_compact_log` returned None** (counter dijo "go" - pero entries < 2): counter = 0 (no re-entrar cada write). - - **Si error**: counter NO se resetea (próximo write reintenta), - devuelve el error. -- **Nuevo helper `append_compact_msg(base, opt)`**: concatena el - msg del compact al toast del op original con `";"` separator. -- **Wireup en 3 callsites de write efectivo**: - - `apply_action::SeedEntity`: tick si outcome != NoChange. - - `apply_action::Morphism`: tick siempre que Ok. - - Click handler `[Confirmar]` del delete modal: tick si commit_delete Ok. -- **NoChange no cuenta**: un edit que no cambia nada no escribe al - log, así que tampoco debería avanzar el counter — preserva la - semantic "1 write = 1 log entry = 1 tick". - -2 tests nuevos: -- `append_compact_msg_handles_both_branches` — base solo vs base - + compact, formato del separator. -- `runtime_compact_cycle_resets_counter_after_threshold` — E2E - estilo simulación: 7 writes con threshold=3 → 2 compacts (en - write 3 y 6), counter residual = 1, log final con 2 entries - (1 anchor + 1 write residual). Reproduce el algoritmo del tick - sin GPUI cx; si la lógica del método cambia, se rompe como signal. - -37 tests verdes (+2). Workspace build verde. - -Trade-offs: -- **Counter en memoria, no persistido**: si la app crashea entre - compacts, al próximo boot el counter parte de 0. El startup - compact (basado en entry_count del log file) compensa esto: - si quedó mucho post-último-compact, se compacta al boot. -- **Lock orden**: tick toma log lock primero, store lock después. - Misma orden que `commit_seed` y `commit_morphism`, no debería - haber deadlock. -- **Costo del tick**: 1 increment + 1 compare por write. Cuando - cruza threshold, 1 read del log (entries) + 1 snapshot write + - 1 compact. Para threshold=50 es ~1 fsync cada 50 writes — - amortiza bien. - -Pendientes restantes: -- **`FieldOp::Clear`** — para soportar borrar un value vía form vacío. -- **Validación cross-field** (UUID del EntityRef existe en la - entity referida). - -### feat(nakui-ui): atajo Esc para cancelar el modal de delete -Cierra otro pendiente de UX. El banner de confirmación de delete -ya tenía botones [Cancelar] / [Confirmar], pero la acción más -natural para cancelar un dialog es Esc — y no la teníamos wireada. - -Cambios: -- **`capture_key_down` en el root div** del `MetaUi::render`. Capture - phase (no bubble) para interceptar el Esc *antes* que cualquier - TextInput descendiente lo consuma. Sin pending el handler es - no-op y el evento sigue su flujo normal. -- **Match `event.keystroke.key == "escape"`** + `pending_delete.take()` - → toast `"delete cancelado (Entity) [esc]"` (sufijo `[esc]` para - diferenciar visualmente del botón). Si no hay pending, return - temprano sin tocar nada. -- **Hint visual en el banner**: subtítulo en amber tenue debajo del - título: `"Esc para cancelar · click [Confirmar] para borrar"`. - Que el usuario descubra el atajo sin RTFM. - -35 tests verdes — el handler de Esc es 8 líneas no-testeables sin -GPUI cx (la lógica de pending_delete + toast vive dentro del -listener); el wireup compila por type-check. - -Pendientes restantes: -- **`FieldOp::Clear`** — para soportar borrar un value vía form vacío. -- **Snapshot durante runtime** (cada N writes, no sólo al startup). -- **Validación cross-field** (UUID del EntityRef existe en la - entity referida). - -### feat(nakui-ui): EntityRef validation en parse_field_value (UUID al submit) -Cierra otro pendiente: `parse_field_value(FieldKind::EntityRef, raw)` -devolvía `Ok(json!(raw))` blindly — el value entraba al log/store -incluso si era basura. La validación de UUID sólo ocurría cuando el -field se usaba como **input** del morphism (línea ~540 de -`commit_morphism`); como **seed field** o como **param**, garbage -pasaba silenciosamente. - -Cambios: -- **`parse_field_value(EntityRef, raw)`** ahora hace - `Uuid::parse_str(raw.trim())` y devuelve error claro si falla: - `"'' no es UUID válido (usá el selector de records)"`. En - caso de éxito, devuelve el UUID **trimmed** como string — - protege contra paste manual con whitespace. -- **Doble cobertura**: este path cubre seed fields (commit_seed via - obj.insert) y morphism params (resolve_param_value lo invoca por - cada FieldSpec con kind=EntityRef). El path de morphism inputs - ya validaba antes con `Uuid::parse_str` directo — sigue intacto, - no hay double-validation. -- **Selector clickable es happy path**: el dropdown setea valores - bien-formados, así que el usuario nunca debería ver el error en - uso normal. Sólo dispara con paste manual o si el usuario escribe - garbage en el input — defensivo. - -5 tests nuevos (reemplazan al obsoleto `parse_field_entity_ref_returns_string`): -- `parse_field_entity_ref_accepts_valid_uuid` — happy path. -- `parse_field_entity_ref_trims_whitespace` — `" uuid\n"` → `"uuid"`. -- `parse_field_entity_ref_rejects_non_uuid` — `"abc-123"` → error - con el value y la palabra "UUID" en el mensaje. -- `parse_field_entity_ref_rejects_empty_string` — `""` → rebota. -- `resolve_param_strict_entity_ref_propagates_error` — sanity de - que el wireup en resolve_param_value hereda el strict checking, - con label del FieldSpec en el mensaje. - -35 tests verdes (+4 net). El E2E del morphism real -`morphism_pipeline_executes_real_sales_vender` sigue verde — sus -inputs van por el path dedicado, no por parse_field_value. - -Pendientes restantes: -- **Atajo Esc para Cancelar** del modal de delete. -- **`FieldOp::Clear`** — para soportar borrar un value vía form. -- **Snapshot durante runtime** (cada N writes, no sólo al startup). -- **Validación cross-field** (ej: el UUID del EntityRef existe en - la entity referida) — hoy sólo validamos forma; un UUID válido - pero inexistente sí pasa. - -### feat(nakui-ui): snapshot/compaction automático del event log al startup -Cierra el último gran pendiente del round: el replay full cada -startup escala lineal en el log. Con 60+ entries el costo de boot -se nota; con 10k entries es prohibitivo. Wireamos el snapshot -machinery que ya estaba en `nakui-core` (`Snapshot`, -`replay_with_snapshot_into`, `EventLog::compact_through`) al -runtime de la UI. - -Cambios: -- **Path del snapshot**: sibling del log, extensión `.snap.json`. - `nakui-ui-state.jsonl` ↔ `nakui-ui-state.snap.json`. -- **Nuevo helper `snapshot_path_for(log_path)`** — derivación - pura, testeable. -- **Nuevo helper `maybe_compact_log(log, snap_path, store, threshold)`**: - - Si `entry_count >= threshold` y `>= 2`, captura - `Snapshot::from_memory_store(store, next_seq - 1)`, lo escribe - atómicamente, y compacta el log dejando la última entry como - anchor. - - Anchor invariant: `EventLog::open` deriva `next_seq` del primer - entry del archivo. Si compactáramos *todo* el log file, al - reabrir el cursor volvería a 0 y el próximo append crashearía - con `NonMonotonic`. Por eso compactamos sólo hasta - `next_seq - 2` — la entry del `snap.seq` queda como anchor del - cursor; `replay_with_snapshot_into` la skipea porque snap ya - cubre hasta ese seq inclusive. - - Threshold via env `NAKUI_SNAPSHOT_THRESHOLD`, default 50. - `0` desactiva por completo. - - Devuelve `Result, String>`: `Ok(Some)` si compactó, - `Ok(None)` si no había payoff, `Err` si snap o compact fallaron. -- **`MetaUi::new` reescrito**: - - Carga snapshot al inicio (Some/None según exista). - - `replay_with_snapshot_into(&log, snapshot.as_ref(), &mut store)` - en lugar de `replay_into`. - - Después del replay corre `maybe_compact_log` con el threshold. - - Toast inicial menciona snapshot loaded si aplica - ("snapshot @ seq K") y la compactación si ocurrió. - - Errores de snapshot load **no son fatales**: cae a full replay - con un msg en el banner. - - Errores de auto-compact **no son fatales**: el log + snap - quedan como estaban, msg al banner. - -5 tests nuevos: -- `snapshot_path_for_replaces_extension` — `.jsonl` → `.snap.json`, - edge case sin extensión. -- `maybe_compact_log_below_threshold_noops` — 5 entries vs threshold - 50: no toca nada, no escribe snap. -- `maybe_compact_log_threshold_zero_noops` — threshold 0 = disabled. -- `maybe_compact_log_then_reopen_preserves_records` — E2E: - - Escribe 60 seeds (log + store en sync). - - Compacta (60 >= 50): snap escrito, log queda con 1 anchor entry, - msg reporta "59 entries dropped (1 anchor kept)". - - Reopen: `next_seq=60` se preserva via anchor, `entries.len()=1`. - - Replay con snap loadado en store fresco: los 60 records están. - - Segunda corrida del compact con threshold=1: no-op (idempotente). - -31 tests verdes (+5). Workspace build verde tras la nueva firma. - -Trade-offs y notas: -- **Fail-soft**: cualquier error de snap/compact no rompe el boot; - la UI sigue funcionando con full replay y el toast lo reporta. - Sólo `EventLog::open` failing es no-recoverable (pierde - persistencia). -- **Crash-safety**: WAL order preservado — escribimos snap (atómico - via tempfile + fsync + rename) ANTES de compactar el log - (atómico igual). Si crasheamos entre los dos, próximo boot ve - snap@K + log con todas las entries 0..N — replay skippea las que - snap cubre, outcome idéntico. -- **Sólo en startup**: no hay snapshot durante runtime. Para sesiones - largas con muchas escrituras, el log puede crecer arbitrariamente - hasta el próximo restart. Pendiente futuro: snapshot N writes - desde el último compact. -- **Anchor entry sobrevive sin uso útil**: el costo es 1 línea JSON - por compact. No es preocupación a menos que el threshold sea - muy chico (cada compact deja 1 línea de basura). - -Pendientes restantes: -- **EntityRef validation post-submit** — validar UUID parseable al - submit en lugar de al execute del morphism. -- **Atajo Esc para Cancelar** del modal de delete. -- **`FieldOp::Clear`** — para soportar borrar un value vía form vacío. -- **Snapshot durante runtime** (cada N writes, no sólo al startup). - -### feat(nakui-ui): edit delta-only — sólo campos modificados al log/store -Antes de este cambio, editar un record emitía un `FieldOp::Set` por -**cada field del form**, incluso los no tocados. Eso bloata el log -(replay tenía que aplicar N ops cuando 1 alcanzaba) y oscurece el -intent en una auditoría posterior. Con delta-only, el edit emite -sólo los Sets cuyo value nuevo difiere del actual; un edit que no -cambia nada deja el log intacto. - -Cambios: -- **Nuevo helper `compute_field_delta(current, proposed)`** — toma - el record actual del store (un `Value`, posible `Null` si el - record no existe) y el `Map` propuesto desde el form, y devuelve - sólo las entries que difieren. Comparación: `PartialEq` estructural - de `serde_json::Value` (un `Null` en current = todos los proposed - son nuevos). -- **Nuevo enum `CommitOutcome`**: - - `Created(Uuid)` — alta nueva. - - `Updated { id, changed }` — edit con N campos modificados. - - `NoChange(Uuid)` — edit sin diferencias (el toast lo refleja - como "X sin cambios — no log entry"). -- **`commit_seed` en path EDIT**: - - Carga current via `store.load(entity, id)` con fallback a - `Value::Null`. - - Calcula delta. Si vacío → return early sin tocar log ni store. - - Si no vacío → emite `Morphism { ui.edit_record, ops: [Set...] }` - con `params.fields` reflejando el delta (no todo el form), - haciendo la auditoría grep-able por field cambiado. -- **Toast del callsite**: - - `creado X uuid` (Created) - - `actualizado X uuid (N campo(s))` (Updated) - - `X uuid sin cambios — no log entry` (NoChange) -- **`editing` se limpia incluso en NoChange** — el modo edit cierra, - el form vuelve al state limpio. - -5 tests nuevos del helper: -- delta vacío cuando todo coincide. -- delta sólo con el field cambiado. -- delta full cuando current = Null (record no existe). -- distingue int 100 de string "100". -- ignora fields del current que no están en proposed. - -27 tests verdes (+5). El path SEED no cambió; el E2E del morphism -real sigue verde. - -Limitación conocida (consistente con pre-delta): el form no puede -**borrar** un value vaciando el input — empty optional fields hacen -`continue` antes de llegar al delta. Para clearear un value hay que -declarar el field como required, o esperar a un `FieldOp::Clear` -futuro (no necesario hoy: ningún demo lo requiere). - -Pendientes restantes: -- **Snapshot/compaction** del log (replay full cada startup escala - mal con repos grandes). -- **EntityRef validation post-submit** — validar UUID parseable al - submit en lugar de al execute del morphism. -- **Atajo Esc para Cancelar** del modal de delete. -- **`FieldOp::Clear`** — para soportar borrar un value vía form. - -### feat(nakui-ui): confirmación de delete vía banner modal antes de borrar -Cierra el primer pending del último round: borrar un record pedía un -solo click en `✕` y se ejecutaba inmediatamente (irreversible — -queda en el log como `Morphism { name: "ui.delete_record" }`). Ahora -hay un paso intermedio: el click marca el record como pendiente y el -banner amber al tope de la ventana ofrece [Cancelar] o [Confirmar]. - -Cambios: -- **Nuevo state `MetaUi.pending_delete: Option<(String, Uuid)>`**. - Set en el click del `✕`; limpiado por: - - [Cancelar] → toast "delete cancelado (Entity)". - - [Confirmar] → llama `commit_delete` (igual que antes) y emite - el toast usual. - - Navegación a otra view (`select_view`) — el record marcado - podría no estar visible en la nueva pantalla. -- **Click handler de `✕` ya no llama `commit_delete`**: sólo setea - `pending_delete` y limpia toast. La acción destructiva ahora vive - exclusivamente en el botón [Confirmar] del banner. -- **Nuevo método `render_confirm_delete_banner`**: devuelve - `Option
` (None si no hay pending). Banner amber con el texto - `¿Borrar {Entity} {short_uuid}?` + dos botones. Renderea como - sibling del row sidebar+main en `flex_col` raíz — no es overlay - flotante (GPUI no expone z-index trivialmente), pero la posición - fija al tope + color amber lo hacen imposible de ignorar. -- **Limpieza pre-commit**: `pending_delete = None` se ejecuta antes - de `commit_delete`, así un fallo del commit no deja el banner - colgado además del toast de error. - -22 tests verdes — la lógica del store/log no cambió, sólo el state -machine de UI. La confirmación es puramente UX/state, no testable -sin GPUI cx, pero la compilación garantiza wireup correcto de las -closures. - -Pendientes restantes: -- **Snapshot/compaction** del log para repos grandes (replay full - cada startup escala mal). -- **Edit delta-only** — sólo campos modificados, no todos. -- **EntityRef validation post-submit** — validar UUID parseable - al submit en lugar de al execute del morphism. -- **Atajo de teclado Esc para Cancelar** — requiere event - dispatcher de GPUI, fuera de scope inmediato. - -### feat(nakui-ui): validación estricta de params del morphism vía FieldKind del FieldSpec -Cierra el último trade-off documentado: `infer_param_value` adivinaba -el tipo de cada param por la shape del string (i64 → f64 → bool → -string). Ahora cuando hay `FieldSpec` declarado, usamos -`parse_field_value(spec.kind, raw)` — un Boolean field con value -"abc" rebota con mensaje claro en la UI antes de llegar al morphism -Rhai (donde el error sería opaco como "Function not found: * ((), ())"). - -Cambios: -- **Nuevo helper `resolve_param_value(field_name, raw, spec)`**: - - Si hay `FieldSpec`: validación de `required` (rebota empty con - "param 'X' es obligatorio y está vacío") + parseo estricto via - `parse_field_value(spec.kind, raw)`. Errores incluyen el `label` - del spec para que el toast sea interpretable. - - Si NO hay spec (param declarado en `Action::Morphism.params` - que no existe en `form.fields` — módulo mal-formado): fallback - a `infer_param_value` como red de seguridad. - - Empty + opcional → `Value::Null`. -- **`commit_morphism` simplificado**: el loop de params ahora es - 3 líneas (lookup spec + llamada a `resolve_param_value` + - inserción al map). La lógica vive en el helper standalone, - testable sin GPUI. - -Tests: 6 nuevos en `tests` mod, todos contra `resolve_param_value`: -- `resolve_param_strict_number_parses_i64` — happy path. -- `resolve_param_strict_boolean_rejects_non_boolean` — un Boolean - con "abc" rebota con mensaje que incluye el label. -- `resolve_param_strict_number_rejects_garbage` — Number con "abc" - rebota. -- `resolve_param_required_empty_rejected` — required vacío rebota - con "obligatorio". -- `resolve_param_optional_empty_returns_null` — optional vacío - → null. -- `resolve_param_no_spec_falls_back_to_infer` — el fallback - preserva el comportamiento anterior para back-compat. - -22 tests verdes en nakui-ui (+6). E2E del morphism real -(`morphism_pipeline_executes_real_sales_vender`) sigue verde — la -validación estricta no rompe el path correcto, sólo agrega rebotes -tempranos a values mal-tipados. - -Beneficio operativo: -- Mensaje de error en la UI ahora identifica el field problemático - por su label legible ("param 'Cantidad': 'abc' no es número") - en lugar del error opaco del morphism Rhai. -- Errores se ven antes de tocar el log o el store — ningún cambio - parcial. -- El módulo Nakui ya no tiene que defender contra inputs garbage - desde la UI: la metainterfaz se vuelve la primera línea de - validación tipada. - -Pendientes futuros (orden de prioridad): -- **Confirmación de delete** — modal antes de borrar. -- **Snapshot/compaction** del log para repos grandes. -- **Edit delta-only** — sólo campos modificados, no todos. -- **EntityRef validation post-submit**: hoy `parse_field_value` - para EntityRef devuelve string raw; el commit_morphism luego - valida como Uuid sólo cuando es input del morphism. Para - EntityRef como param, podríamos validar UUID al submit. - -### feat(nakui-ui): FieldKind::EntityRef — selector clickable de records existentes -Cierra el principal trade-off documentado del commit anterior: -"Inputs UUID a mano (no dropdown)". Los formularios pueden declarar -un campo `entity_ref` que apunta a una entity y el runtime renderea -una lista clickable de records existentes; click selecciona, el -UUID queda guardado para el submit. - -Schema `nakui-ui-schema`: -- **Nueva variante `FieldKind::EntityRef`** (serializa como - `"entity_ref"` en JSON). -- **`FieldSpec.ref_entity: Option`** nuevo. Indica qué - entity ofrecer en el selector. `validate()` chequea que cualquier - field con `kind=entity_ref` tenga `ref_entity` set. -- Nuevo error tipado `SchemaError::EntityRefMissingTarget`. - -Runtime `nakui-ui`: -- **`render_entity_ref_selector(field_name, target_entity, ...)`** — - helper que arma la lista debajo del input. Cada item: - - Etiqueta humana via `human_label_for_record` (heurística: - `name` → `label` → `title` → `sku` → `sku_id` → fallback al - UUID corto). - - Click handler vía `cx.listener` que llama - `input.set_text(uuid_completo)` — el TextInput interno queda - como source-of-truth, así que `commit_seed` y `commit_morphism` - leen el UUID seleccionado sin saber que vino de un selector. - - Highlight en accent color cuando el item es el actualmente - seleccionado (compara contra el contenido del TextInput). -- **`parse_field_value(EntityRef, raw)`** devuelve string del raw - (la validación como Uuid ocurre downstream en `commit_morphism`). -- Mensaje "(sin {entity}: creá uno antes para referenciar)" cuando - la lista está vacía — el user sabe qué hacer en lugar de quedarse - trabado. - -Demo actualizado: `examples/nakui-modules/sales_engine/module.json`: -- `vender_form.fields.stock_id_input` y `caja_id_input` cambian de - `kind: "text"` a `kind: "entity_ref"` con `ref_entity: "Stock"` - y `"Caja"` respectivamente. -- Ahora el flujo "Vender" es: (1) click en una Stock listada bajo - el input, (2) click en una Caja, (3) escribir venta_id/cantidad/ - precio_unitario/timestamp, (4) submit. Sin copiar UUIDs. - -Tests: -- 2 nuevos en schema: `validate_catches_entity_ref_without_target` - y `entity_ref_with_target_validates_clean`. 8 totales. -- 4 nuevos en runtime: `parse_field_entity_ref_returns_string`, - `human_label_for_record_prefers_name_over_id`, - `human_label_falls_back_through_label_title_sku`, - `human_label_falls_back_to_id_when_no_known_keys`. 16 totales. -- Integration de los 7 demos sigue verde — el demo `sales_engine` - ahora valida con EntityRef + ref_entity correctamente set. - -29 tests totales nakui-ui + schema, 100% verde. El demo -`sales_engine` carga limpio con la nueva forma del schema. - -Pendientes futuros: -- **Confirmación de delete** — modal antes de borrar. -- **Snapshot/compaction** del log para repos grandes. -- **Edit delta-only** (sólo campos modificados). -- **Validación de tipos en params del morphism**: `FieldKind` - declarado en el FieldSpec se podría usar para forzar parseo - estricto en `commit_morphism` en lugar de la heurística - `infer_param_value`. - -### feat(nakui-ui): Action::Morphism wired al pipeline real (compute → log → apply) -Cierra el último gran TODO de la metainterfaz Nakui: las acciones -`Action::Morphism` ya no son un toast informativo; despachan al -`Executor` cargado del manifest nakui-core (`nsmc.json` + schemas -KCL + scripts Rhai), pasando por el pipeline completo de Nakui: -compute (con dry-run + KCL post-checks) → log append → store apply. - -Schema `nakui-ui-schema` extendido: -- **`Module.nakui_module_dir: Option`** nuevo. Path - (relativo al directorio del `module.json` o absoluto) a un módulo - nakui-core. Sin esto, las Action::Morphism del módulo quedan - no-op con toast informativo. Las Action::SeedEntity siguen - funcionando sin manifest (alta administrativa). -- **`Action::Morphism`** ganó dos campos opcionales: - - `inputs: BTreeMap` — mapeo `role → field_name`. - Por cada input declarado en el `MorphismSpec.inputs`, indica - qué field del form contiene el UUID del record. El runtime - parsea como `Uuid` y lo pasa al `execute_and_log`. - - `params: Vec` — lista de fields cuyos values van al - `params` JSON. Si vacío, todos los fields no-input van a params. - -Runtime `nakui-ui`: -- **`MetaUi.executors: BTreeMap>`** nuevo. - Carga `Executor::load_module(nakui_module_dir)` en `MetaUi::new` - por cada módulo UI que declare la entry. Errores de carga van al - banner; el módulo sigue cargado para SeedEntity, sólo Morphism - queda no-op. -- **`commit_morphism(mod_idx, name, inputs_map, params_fields)`** nuevo. - Resuelve inputs (parsea cada field como Uuid), arma params (Value - object con tipos inferidos via `infer_param_value` — int/float/ - bool/string), llama `execute_and_log_with_recovery`. Toast con - cantidad de ops aplicadas o el error tipado. -- **`infer_param_value`** nuevo helper: heurística simple para - pasar values del form al morphism con tipo inferido (i64 → f64 → - bool → string). - -Tests: 2 nuevos: -- `infer_param_value_int_then_float_then_bool_then_string` — - cobertura de la heurística. -- **E2E `morphism_pipeline_executes_real_sales_vender`** — - carga el módulo real `crates/modules/nakui/modules/sales`, - arma store + log, ejecuta el morphism `vender` con inputs - Stock+Caja y params (cantidad=5, precio_unitario=200, - venta_id, timestamp). Asserta: - - el morphism produce ops (no vacío). - - stock.cantidad bajó 100 → 95. - - caja.saldo subió 1_000_000 → 1_001_000. - -12 tests verdes en nakui-ui (+1 vs commit anterior). Schema -extension no rompió nada (6 unit + 5 integration siguen verdes). - -Demo nuevo: **`examples/nakui-modules/sales_engine/module.json`** -- Apunta a `crates/modules/nakui/modules/sales` vía `nakui_module_dir`. -- 6 vistas: list + form para cada Stock, Caja, Venta + form - "Vender" con `Action::Morphism { name: "vender", inputs: {stock, - caja}, params: [venta_id, cantidad, precio_unitario, timestamp] }`. -- El user crea Stocks + Cajas con seed_entity, copia los UUIDs - cortos a los inputs de "Vender", y ejecuta el morphism real: - stock baja, caja sube, Venta se persiste, todo loggeado. -- Validaciones KCL fallan limpio (toast con error) si el morphism - rebota — p. ej. cantidad > stock disponible. - -Activación full: -```sh -NAKUI_EVENT_LOG=~/.nakui/state.jsonl \ -NAKUI_MODULES_DIR=examples/nakui-modules \ -cargo run -p nakui-ui -# Sidebar gana "Ventas (con morphism)" — los 6 menús aparecen y -# el form "Vender" dispara el pipeline nakui-core completo. -``` - -Trade-offs documentados: -- **Inputs UUID a mano**: el form pide que el user copie el UUID de - un Stock/Caja existente. Para UX seria habría que agregar - `FieldKind::EntityRef { entity }` que renderiza un dropdown — no - hecho por scope, queda como nice-to-have. -- **Inferencia de tipo en params**: `infer_param_value` adivina - por shape del string. Para casos sutiles (ej. "true" como string - literal vs bool), el módulo nakui-core puede explicitar tipos - via `kind` en el FieldSpec — el form lo respeta para validación - pre-submit; la inferencia final sigue siendo heurística. - -### feat(nakui-ui): edit + delete de records (ciclo CRUD completo) -Cierra "no hay UI para editar/borrar records existentes" del commit -anterior. Cada fila de la lista gana dos botones (✎ edit, ✕ delete); -el form view se reusa para alta y para edit; el delete es inline. -Las mutaciones pasan por `LogEntry::Morphism` con sus ops, así el -replay restaura el estado correcto. - -Cambios: - -- **`MetaUi.editing: Option<(String, Uuid)>`** nuevo. Set al click - en ✎; cleared al cambiar de view o tras submit exitoso. -- **`open_edit(mod_idx, entity, id, cx)`**: setea `editing`, busca la - primera Form view del módulo cuya `entity` matchee, navega ahí. Si - el módulo no tiene Form para esa entity → toast con error - ("no hay form view para entity X"). -- **`select_view`** extendido: cuando carga un Form, si `editing` - matchea esa entity y el record existe en el store, pre-llena cada - input con el valor del record (vía nuevo helper - `value_to_input_text` — inverso de `parse_field_value`). -- **`commit_seed`** ramifica: - - **Edit path** (cuando `editing.is_some()` y entity matchea): - emite `LogEntry::Morphism { name: "ui.edit_record", ops: - [Set { path, value } for each field], params: { entity, id, - fields } }`. Aplica al store via `apply(&ops)`. - - **Seed path** (alta nueva): comportamiento previo. -- **`commit_delete(entity, id)`**: emite `LogEntry::Morphism { - name: "ui.delete_record", ops: [Delete { entity, id }] }` + apply. -- **Render del form**: título cambia a "Editar customer abc12345" - cuando `editing` matchea; submit label cambia a "Guardar cambios - en customer". -- **Render de la lista**: dos columnas nuevas — "id" y "acciones". - Cada fila tiene ✎ (accent color, click → open_edit) y ✕ (rojo, - click → commit_delete). Hover states. - -Ramificación visible en el event log: -``` -{"kind":"seed","seq":0,"entity":"customer","id":"abc...","data":{"name":"Acme"}} -{"kind":"morphism","seq":1,"morphism":"ui.edit_record","ops":[ - {"op":"set","path":{"entity":"customer","id":"abc...","field":"name"}, - "value":"Acme S.A."} -]} -{"kind":"morphism","seq":2,"morphism":"ui.delete_record","ops":[ - {"op":"delete","entity":"customer","id":"abc..."} -]} -``` -Coherente con el modelo de Nakui — todo cambio post-seed pasa por -ops dentro de Morphism. `nakui-explorer` muestra estos morphisms -con sus ops claros en su timeline. - -Trade-offs documentados: -- **`schema_hash: None`** sigue para los morphism de la UI (legacy/ - pre-versioning path) hasta que `Action::Morphism` cargue Manifest - schemas. -- **Delete sin confirmación**: 1 click, sin modal. Para MVP es OK - (los records son recuperables vía replay parcial), pero un futuro - iter agregaría confirmación. -- **Edit sobreescribe TODOS los campos del form**, no sólo los - cambiados — emite N ops Set, una por field. Adecuado para forms - chicos; para forms con muchos campos optimizar a delta-only. - -Tests: 3 nuevos (10 totales en nakui-ui): -- `value_to_input_text_inverse_of_parse` y - `value_to_input_then_parse_round_trip` — la propiedad fundamental - del pre-llenado: text → parse devuelve el Value original. -- `event_log_replay_handles_full_crud_cycle` — E2E del log: escribe - Seed + Morphism(Set ops) + Morphism(Delete op), replay desde cero, - verifica que el store termina vacío (delete fue el último). Verifica - además que un replay parcial (sin el delete) deja los valores - editados. - -Activación: -```sh -NAKUI_EVENT_LOG=~/.nakui/state.jsonl \ -NAKUI_MODULES_DIR=examples/nakui-modules \ -cargo run -p nakui-ui -# Crear un customer, click ✎ en su fila, modificar campos, -# "Guardar cambios". Click ✕ en otra fila para borrar. -# Cerrar y reabrir: el state persiste con todos los cambios. -``` - -### feat(nakui-ui): persistencia con event log + replay al startup -Cierra "sin persistencia entre runs" del commit anterior. Cada -`SeedEntity` se appendea al `nakui_core::event_log::EventLog` con -WAL semantics (log antes que store) y al re-abrir el binario el -replay reconstruye el `MemoryStore` desde cero. Cerrar y volver a -abrir ya no borra el data. - -Cambios: -- **`MetaUi.event_log: Option>>`** nuevo. - Compartido bajo `Mutex` para que el commit_seed pueda mutar. -- **Apertura + replay al startup** (`MetaUi::new`): path por env - `NAKUI_EVENT_LOG`, default `./nakui-ui-state.jsonl`. - `EventLog::open` + `replay_into` reconstruyen el store. - Toast informativo: "log nuevo" o "log X cargado: N evento(s) - replayed". -- **WAL en `commit_seed`**: si `event_log.is_some()`, primero - `log.append(LogEntry::Seed { ..., schema_hash: None })`, después - `store.seed`. Si el append falla, cancela toda la operación - (el user reintenta sin haber dejado state inconsistente). -- **`schema_hash: None`**: documentado como path "legacy / - pre-versioning" para seeds que no pasan por un Manifest+Executor. - Es el path correcto para alta administrativa vía la metainterfaz - hasta que `Action::Morphism` wireé el Manifest loader. -- **Degradación grácil**: si abrir log falla (permisos, disco), - toast con error pero el runtime sigue en modo in-memory. - -Tests: 1 nuevo E2E `event_log_replay_restores_memory_store` que -escribe 2 seeds via `EventLog::append`, re-abre y `replay_into` un -store fresh, verifica que ambos records están con sus values -correctos. Reproduce el flujo del startup de `MetaUi::new` sin -necesitar GPUI. 7 tests verdes en nakui-ui. - -Activación con persistencia explícita: -```sh -NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\ -NAKUI_MODULES_DIR=examples/nakui-modules \\ -cargo run -p nakui-ui -# Crear varios records vía el form, cerrar el binario, abrir de -# nuevo: los records están. -``` - -Limitaciones que **siguen** (próximos iters): -- **`Action::Morphism`** sigue como TODO: requiere cargar el - `Manifest` de nakui-core junto al `Module` UI para conocer los - inputs/params declarados y poder llamar `execute_and_log`. -- **No hay snapshot/compaction**: el log crece append-only para - siempre. Para repos grandes habría que integrar `Snapshot` de - nakui_core (existe, no se usa todavía). -- **No hay UI para borrar/editar** records existentes — sólo alta - vía form. Edit + delete en futuras iteraciones. -- **Widget input simple** (sin selection/IME/clipboard) — heredado - de la limitación documentada de `yahweh-widget-text-input`. - -### feat(nakui-ui): inputs reales con yahweh-widget-text-input + click handlers funcionales -Cierra dos limitaciones documentadas en el commit anterior de la -metainterfaz: los formularios ahora aceptan teclado real, y los -clicks en menús + botones mutan estado correctamente. - -Cambios: -- **Inputs vivos**: cada `FieldSpec` del Form view materializa un - `Entity` (de `yahweh-widget-text-input`) al entrar a la - vista. Los entities se reemplazan al cambiar de view (drop limpio). - El widget soporta: escribir caracteres, Backspace, Enter (Confirmed - event — no usado todavía; el submit va por botón), Escape - (Cancelled). El cursor se renderea como `|` al final. -- **Click handlers wired vía `cx.listener`**: menús del sidebar - invocan `select_view`; botones de acción (header de list, submit - de form) invocan `apply_action`. Los handlers tienen acceso real - al `Context` y mutan el modelo + emiten `cx.notify()`. -- **Submit lee texto de los inputs**: `commit_seed` reemplaza el - buffer ad-hoc anterior por `input.read(cx).text()` por cada - field. El value parseado va al `MemoryStore` con su tipo correcto - (text/number/boolean/date). -- **Reset de inputs tras submit**: si la acción no tiene `next_view`, - los inputs se vacían (`set_text("")`) para alta consecutiva sin - re-tipear. -- **Hover states**: items del sidebar y botones cambian de bg al - pasar el mouse, feedback visual consistente con el resto del - ecosistema yahweh. -- **Theme global**: `Theme::install_default(cx)` al inicio (lo - requiere el text_input para sus colores). - -Wire en Cargo: -- Deps nuevas: `yahweh-widget-text-input`, `yahweh-theme` (paths - relativos al monorepo). - -Limitaciones que **siguen abiertas** (próximos iters): -- **`Action::Morphism`** sigue como TODO: requiere cargar el - `Manifest` de nakui-core junto al `Module` UI para conocer los - inputs/params declarados. -- **Sin persistencia entre runs**: `MemoryStore` en RAM. Wire con - `EventLog` o `SurrealStore` queda para cuando exista el daemon - Nakui. -- **Inputs simples**: el widget no soporta cursor positioning, - selection, copy/paste, IME, multilínea. Para edits serios habrá - que portar `gpui::examples::input` o adoptar `gpui-input` cuando - exista upstream. -- **Enter no envía**: el `TextInputEvent::Confirmed` que emite el - widget no está suscrito todavía; el submit va por click. Trivial - de wirear si lo necesitamos. - -Tests: los 6 unit del runtime siguen verdes (parse_field_value para -los 5 kinds, lookup_field nested, render_value). El comportamiento -visual requiere correr el binario con `cargo run -p nakui-ui` y -probar a mano — GPUI no provee harness de UI testing en CI hoy. - -Activación full: -```sh -NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui -# Click en un menú → carga vista. Click en "Nuevo" → form. -# Tipear en cada campo → ver el `|` al final. Click "Crear customer" -# → record aparece en la lista. -``` - -### feat(nakui): metainterfaz declarativa + 6 módulos ERP estándar -Salto cualitativo: Nakui pasa de "library + demos + read-only viewer -del event log" a **plataforma ERP con UI dirigida por datos**. Cada -módulo de negocio se declara como un `module.json` (sin código Rust -nuevo) y el runtime GPUI lo carga dinámicamente: sidebar de menús, -listas con columnas configurables, formularios de alta. - -Tres entregables: - -**1) Crate nuevo `nakui-ui-schema`** (datos puros, ~250 LOC + 200 -LOC tests): -- `Module { id, label, entities, menu, views }`. -- `View::List { entity, columns, actions, search_in }` o - `View::Form { entity, fields, on_submit }`. -- `FieldSpec { name, label, kind, default, required, help }` con - `FieldKind = Text|Multiline|Number|Boolean|Date`. -- `Action::OpenView | SeedEntity | Morphism` — el runtime las - dispara desde botones / submits. -- `Module::from_path` parsea un JSON; `Module::validate` chequea que - cada `MenuItem.view` exista en `views`. -- `load_modules_from_dir(dir)` busca `dir//module.json`, - parsea, valida, detecta IDs duplicados, devuelve ordenado. -- 6 tests unit + 4 integration (los 6 demos cargan limpio, todos - tienen list+form, kinds reconocidos, validate pasa). - -**2) Crate nuevo `nakui-ui`** (binario GPUI, ~700 LOC + 100 LOC tests): -- Carga módulos desde `NAKUI_MODULES_DIR` (default `./nakui-modules`). -- Sidebar con módulos + sus menús; click en menu cambia la vista activa. -- **List view**: tabla de instancias del entity con columnas - weighted (header de columnas + filas + id corto). -- **Form view**: campos labeled + botón submit que dispara la action - declarada (`SeedEntity` mete el record al `MemoryStore` - in-process; `Morphism` queda como TODO hasta integrar el manifest - loader nakui-core). -- `MemoryStore` compartido entre todas las vistas (Arc); el - cambio en un módulo se refleja en otro inmediato. -- Toast + error banner para feedback. -- 6 tests unit (parse_field_value para los 5 kinds, lookup_field - nested, render_value). - -**3) 6 módulos demo** en `examples/nakui-modules/` que cubren un -ERP estándar: -- **customers**: nombre, email, teléfono, activo, límite de - crédito, notas. -- **products**: SKU, nombre, categoría, precio, stock, activo. -- **suppliers**: razón social, ID fiscal, contacto, email, - teléfono, términos de pago. -- **inventory_movements**: fecha, tipo (in/out/adjustment), SKU - producto, cantidad, costo unitario, motivo, doc. referencia. -- **sales_orders**: número, cliente, emisión, vencimiento, - estado, subtotal, impuestos, total, notas. -- **invoices**: número, cliente, emisión, vencimiento, subtotal, - impuestos, total, pagado, estado, moneda, orden referenciada. - -Cada módulo tiene su `list` (catálogo) + `form` (alta), con search -field y columns weighted. Los 6 cubren un setup de ERP de ventas -chico funcional para demo. - -Filosofía documentada: -- **UI como datos**: agregar un módulo = escribir un JSON, no - recompilar el binario. -- **Persistencia universal**: el runtime conecta cada vista al - `nakui_core::store::Store`; cambiar de MemoryStore a SurrealStore - no toca los module.json. -- **Schema primero, semántica después**: `nakui-ui-schema` sólo - define la forma; validación de referencias rotas (entity inexistente, - morphism faltante) vive en el runtime. - -Activación: -```sh -NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui -``` - -Limitaciones conocidas (próximas iteraciones): -- **Inputs sin teclado**: GPUI no incluye text input; los forms - muestran los `default` del schema y el submit usa esos. Próximo - iter: integración con `yahweh-widget-text-input`. -- **Click handlers no wired**: GPUI necesita pasar `Entity` - a los handlers para mutar estado; refactor con `cx.listener` + - weak refs queda para el próximo iter. Hoy la navegación es - visual; el código de mutación sí funciona via API programática - (los tests lo cubren). -- **Acción `Morphism`**: pendiente de cargar el `Manifest` de - nakui-core junto con el `Module` UI para wirear `execute_and_log`. -- **Sin persistencia entre runs**: el `MemoryStore` se pierde al - cerrar. Wire con `EventLog` o `SurrealStore` queda para cuando - el daemon Nakui exista. - -Tests: 16 totales nuevos (10 schema + 6 runtime). 100% verde. - -Lo que esto desbloquea: cualquiera puede escribir un `module.json` -para su dominio (pacientes médicos, alumnos de escuela, -reservaciones de hotel) y aparece en la UI sin tocar Rust ni -recompilar. La forma de extender Nakui dejó de ser "agregar código -al ERP" y pasó a ser "escribir el contrato del módulo". - -### feat(nakui-explorer): nuevo binario GPUI — Nakui visible en la interfaz -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 parámetros, breakdown por entity type, polling cada 2s para -detectar nuevos eventos appended sin restart del explorer. - -Diseño: -- Lee directamente el archivo `.jsonl` del `nakui_core::event_log::EventLog`. - Path por env `NAKUI_EVENT_LOG`, default `nakui.jsonl` en pwd. -- Sin discovery vía broker brahman porque nakui hoy es CLI/library/ - demos, no daemon. Cuando se daemonice, sustituir el lector de - archivo por un sidecar consumer (mismo patrón que nouser-explorer - actualmente usa). - -UI: -- **Header**: path del log, count total + breakdown seeds/morphisms, - tiempo del último reload en ms. -- **Breakdown line**: top 5 buckets por frecuencia (entities + nombres - de morphisms con prefijo `→`). -- **Timeline**: tarjetas color-coded por kind (azul=seed, - verde=morphism). Cada tarjeta muestra `#seq`, kind, entity/morphism - name, id corto (8 hex), preview del data/params (80 chars), schema - hash corto (8 hex) o `(legacy)` si pre-versioning. Mostradas - más-recientes-primero, hasta 200 visibles (suficiente para - navegación; sin scroll virtualizado por ahora). -- **Error banner**: si la lectura falla (archivo inexistente o - corrupto), banner rojo con el motivo. El explorer NO crashea — - sigue intentando cada 2s. - -Wire en workspace: -- Nuevo `crates/apps/nakui-explorer/` agregado a `[workspace] members`. -- Deps mínimas: `nakui-core` (para EventLog + LogEntry), `gpui`, - `serde_json`, `uuid` (con feature serde para parsear los IDs). -- Sin deps de brahman por ahora (Nakui standalone). - -Tests: 7 unitarios en `tests` mod del bin: -- `load_log_returns_all_entries_in_order` — cargar un .jsonl - generado a mano, asserta que devuelve 5 entries con seqs 0..4 - contiguous. -- `breakdown_counts_seeds_morphisms_and_buckets` — verifica el - conteo (3 seeds + 2 morphisms) y los buckets esperados. -- `load_missing_file_yields_empty_not_error` — archivo inexistente - devuelve `[]` sin error (delegado al contrato de `EventLog::open`). -- `preview_value_truncates_long_strings` y `_keeps_short_strings_intact`. -- `short_uuid_takes_first_8_chars` y `short_hash_takes_first_4_bytes_hex`. - -Activación: -```sh -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 único que queda es `minga-vfs` (FUSE, -explícitamente diferido por el usuario) y mejoras nice-to-have -(cobertura adicional per-lenguaje, daemon-ización de nakui para -sidecar discovery). - -### feat(minga-core): α-hashing per-language para Python, TypeScript, JavaScript, Go -Cierra el último pendiente fundamentado del CHANGELOG. Cada lenguaje -soportado por `minga` tiene ahora su propio profile α-equivalente — -dos versiones del mismo programa que difieren sólo en nombres de -variables ligadas producen el mismo hash, no importa el lenguaje. -Refactorings tipo "rename variable" no inflan el storage del repo -en ningún dialecto. - -Refactor de `alpha.rs` (639 LOC) a módulo `alpha/`: -- **`alpha/common.rs`**: primitives compartidos (TAG_*, write_kind_and_field, - emit_leaf_marker, emit_binder_body, emit_identifier_ref, push_identifier_name). - Garantiza que el formato wire del hash sea bit-equivalente entre - todos los profiles. -- **`alpha/rust.rs`**: la lógica de Rust (movida desde alpha.rs sin - cambios funcionales). -- **`alpha/python.rs`**: nuevo. -- **`alpha/ecmascript.rs`**: nuevo (cubre TypeScript + JavaScript; - comparten la mayoría de los kinds). -- **`alpha/go.rs`**: nuevo. -- **`alpha/mod.rs`**: re-exporta `hash_node_alpha` (Rust legacy) + - expone `hash_alpha_with(dialect, node)` que despacha al profile - correspondiente. - -Cobertura per-language: - -**Python** (`def`, `lambda`, `for`, comprehensions, `with`): -- `function_definition` y `lambda`: parámetros (incluyendo - typed_parameter, default_parameter, *args, **kwargs) introducen - binders al body. El nombre de la función NO es α-anónimo. -- `for_statement`: el `left` (identifier o tuple) introduce - binder(es) al body. -- `list_comprehension`, `set_comprehension`, `dictionary_comprehension`, - `generator_expression`: cada `for_in_clause` añade binders que - viven en el body + clauses siguientes (semántica de scope - incremental de Python). -- `with_statement`: `as` introduce binder al body (recursando en - `as_pattern_target` para llegar al identifier). - -**ECMAScript** (TS + JS): -- `function_declaration`, `function_expression`, `method_definition`, - `generator_function_*`: parameters → body. Soporta TS - `required_parameter` y `optional_parameter` (`x: number`, - `x?: number`). -- `arrow_function`: tanto `(x, y) => body` como shorthand `x => body`. -- `statement_block`: `lexical_declaration` (let/const) y - `variable_declaration` (var) introducen binders al resto del block. -- `for_in_statement` (cubre `for-of` y `for-in`): `left` → body. -- `for_statement` (C-style): initializer (lexical decl) introduce - binders al condition + increment + body. -- `catch_clause`: parameter → body. - -**Go**: -- `function_declaration`, `method_declaration`, `func_literal` (closure): - `parameter_list` → body. `parameter_declaration` con varios names - agrupa varios binders bajo un mismo tipo (`a, b int`). -- `block`: `short_var_declaration` (`x := ...`) introduce binders - al resto. -- `for_statement` con `range_clause` (`for k, v := range m`): los - identifiers del `left` son binders al body. -- `for_statement` con `for_clause` (C-style): initializer → body. -- `if_statement` con `initializer` (`if x := init(); x > 0`): - binders viven en condition + consequence + alternative. - -API: -- `hash_alpha_with(Dialect, &SemanticNode) -> ContentHash` — - despacho per-dialect. -- `hash_node_alpha(&SemanticNode) -> ContentHash` — alias histórico - asume Rust (back-compat). - -Tests: 26 nuevos en `tests/alpha_polyglot.rs`: -- Python (9): def rename, lambda rename, for-loop rename, list comp, - nested comp, with rename, function name matters, iterable name - matters, sanity negativo (operación distinta → hash distinto). -- JS/TS (9): function rename, function name matters, arrow rename, - arrow shorthand rename, let/const rename, for-of rename, classic - for rename, catch rename, TS typed param rename, TS type matters. -- Go (6): function rename, function name matters, short var decl - rename, range_clause rename, if-init rename, func_literal closure - rename. -- Cross-language (1): mismos shapes en lenguajes distintos - producen hashes distintos (sanity para evitar colisiones). - -141 tests verdes en minga-core (115 antes; +26 polyglot). Refactor -sin regresión: 36 α-Rust tests siguen pasando. - -Pendientes que quedan en Minga (orden de prioridad): -- `minga-vfs` FUSE (proyecto independiente, scope grande). -- Cobertura adicional por-lenguaje: Python class, JS destructuring, - Go type_switch, etc. — cada uno pequeño, no urgente. - -### feat(minga-core): cierre del α-hashing de Rust — if let, while let, let-else, or-pattern, let-chains -Cierra los 5 pendientes documentados en `alpha.rs`. El hash -α-equivalente ahora es estable bajo renombre de TODOS los binders -de Rust, no sólo los del MVP (parámetros, let, for, match arms). - -Pendientes cerrados: -- **`if let X = expr { ... }`**: `if_expression` detecta - `let_condition` en su `condition`, recolecta los binders del - pattern, los propaga al `consequence`. El `alternative` (else) - NO los ve. -- **`while let X = expr { ... }`**: simétrico al if-let, propaga al - `body`. El `condition` mismo se evalúa con scope previo (los - binders todavía no existen). -- **`let-else`**: `let_declaration` con campo `alternative`. El - alternative se procesa con el scope ANTES de los binders (ya - funcionaba: `feed_let` llama `feed` para no-pattern children con - el scope actual; `feed_block` extiende el scope DESPUÉS de - `feed_let`). -- **`or_pattern`**: en `pat1 | pat2` (Rust enforcement: ambos lados - introducen los mismos binders). Para emit, recorremos cada lado - con `feed_pattern`. Para collect, sólo el primer lado — iterar - todos duplicaría binders y rompería los índices de Bruijn. -- **let-chains** (`if let X = a && let Y = b { ... }`): el - `collect_let_condition_binders` recursa en el árbol del condition, - capturando todos los `let_condition` (vivan dentro de - `binary_expression` u otros nodos). Ambos binders quedan en scope - del consequence. - -Helper nuevo: `feed_let_condition` para que el `pattern` del -let_condition pase por `feed_pattern` (que distingue binders vs -constructors). Sin esto, los identifiers del pattern se hasheaban -como variables libres y `Some(x)` ≠ `Some(y)` aún teniendo el -mismo significado. - -Tests: 6 nuevos en `tests/alpha_invariants.rs`: -- `alpha_if_let_binder_rename_invariant` -- `alpha_if_let_else_does_not_see_binder` (sanity) -- `alpha_while_let_binder_rename_invariant` -- `alpha_let_else_binder_rename_invariant` -- `alpha_or_pattern_binder_rename_invariant` -- `alpha_let_chain_binders_propagate_to_consequence` -- `alpha_if_let_does_not_collide_with_unrelated_program` (negativo: - programas distintos NO deben dar el mismo hash) - -36 tests α verdes (eran 30). 115 tests totales en minga-core. - -Lo que esto significa: el hash α-equivalente de Rust en minga es -**completo** — cubre todos los constructos del lenguaje que -introducen bindings. Dos versiones del mismo programa que difieren -sólo en nombres de variables (incluyendo en `if let`, `while let`, -`or-pattern`, etc.) producen el mismo hash y por tanto la misma -identidad CAS. Refactorings del tipo "rename variable" no inflan -el storage del repo. - -Pendientes futuros: -- α-hashing per-language (Python: def/lambda/comprehensions; TS/JS: - function/arrow/destructuring; Go: func/closure). Cada uno - requiere conocimiento profundo de la gramática y tests - exhaustivos. Plantilla genérica no aplica. - -### feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go -Minga deja de ser Rust-only. Cualquiera de los cinco dialectos -(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea -estructuralmente, sincroniza por DHT como cualquier nodo. La -auto-detección por extensión hace que `minga ingest archivo.py` o -`.ts` o `.go` "simplemente funcione". - -API nueva en `minga_core::parse`: -- Funciones por dialecto (~6 LOC c/u sobre el `parse_with` común): - `python`, `typescript`, `javascript`, `go`. Más la `rust` existente. -- Enum `Dialect` con `parse(source) -> Result` y - `name() -> &'static str` para logging. -- `detect_by_extension(ext) -> Option`: mapea `rs`/`py`/ - `pyi`/`ts`/`js`/`mjs`/`cjs`/`go` (case-insensitive). `None` para - extensiones desconocidas — el caller decide si es error o se - ignora silente. - -Wire en `minga-cli`: -- `cmd_ingest` deja de hardcodear `parse::rust` — usa - `detect_dialect(file)?.parse(...)`. Acepta `.py`, `.ts`, `.js`, - `.go` además de `.rs`. -- `initial_scan` y `cmd_watch` cambian `is_rs_file` → `is_supported_source` - para incluir todas las extensiones soportadas en el filtro. -- `CliError::UnsupportedLanguage { path, extension }` nuevo, con - mensaje que lista las extensiones reconocidas. - -Notas sobre hashing: -- El AST normalizado (`SemanticNode`) descarta whitespace y - comentarios — propiedad universal de tree-sitter (extras). Misma - lógica para los 5 dialectos. -- Hashing **estructural** (`cas::hash_node`) funciona para todos: - dos textos semánticamente equivalentes-por-estructura producen el - mismo hash. NO α-equivalente (las variables ligadas distinguen). -- Hashing **α-equivalente** (`alpha::hash_node_alpha`) sigue siendo - Rust-only: cada lenguaje tiene reglas distintas para qué es - binder vs. constructor (def/lambda en Python, arrow functions en - TS/JS, func + closures en Go). Implementación per-language queda - como work futuro — requiere conocimiento profundo de cada - gramática y no se plantilla genéricamente. -- Sanity test `structural_hash_distinguishes_languages` verifica - que `x = 1` parseado como Python ≠ parseado como JavaScript: las - gramáticas no comparten kinds y los hashes salen distintos. - Importante para evitar colisiones cuando el mismo source se - ingresa bajo dialectos distintos. - -Deps nuevas (workspace + minga-core): -- `tree-sitter-python = "0.23"` -- `tree-sitter-typescript = "0.23"` (sólo el modo `LANGUAGE_TYPESCRIPT`, - no TSX — bumpear a TSX es agregar otro dialecto cuando se necesite). -- `tree-sitter-javascript = "0.23"` -- `tree-sitter-go = "0.23"` - -Tests: -- 9 nuevos en `parse::tests`: parse básico para los 5 dialectos - (Python con type hints, TS con tipos, JS sin tipos, Go con - package declaration), `detect_by_extension` canonical + - case-insensitive, `dialect_name`, `structural_hash_distinguishes_languages`. -- 108 tests verdes en minga-core (39 → 48 unit + integration tests - pre-existentes intactos). -- 10 tests verdes en minga-cli (sin regresión en el path Rust; - el refactor a `detect_dialect`/`is_supported_source` no rompe - nada). - -Pendientes futuros del changelog: -- α-hashing per-language (Python: def/lambda/comprehensions; - TS/JS: function/arrow/destructuring; Go: func/closure). Trabajo - profundo, scope independiente. -- α-Rust pendientes documentados en `alpha.rs`: `if let`, - `while let`, `let-else`, let-chains, `or_pattern` con bindings. - -### feat(brahman-handshake): multi-key identity — rotación de session sin perder peer_id lógico -Cierra el último pendiente del plan de red P2P. Hasta ahora, rotar -la keypair libp2p de un nodo cambiaba su `peer_id`, lo que -invalidaba todas las allowlists/denylists remotas que lo -referenciaban. Imposible rotar sin coordinar con todos los pares. - -Solución: separar **identity master** (Ed25519 persistente forever, -identifica al nodo como entidad lógica) de **session libp2p** -(Ed25519 efímera, rotable). El master firma certs de session con -expiración. La política de admisión se evalúa contra el -`master_peer_id` del cert — el session peer_id puede cambiar -libremente sin tocar las allowlists. - -API nueva en `brahman_handshake::identity`: -- `Identity::from_keypair(master)` — wrapper sobre la master kp. -- `Identity::master_peer_id()` — el peer_id estable del nodo. -- `Identity::issue_session_cert(session_kp, ttl) -> SessionCert` — - firma un cert que vincula session_pubkey + expires_at_ms. -- `SessionCert::verify()` — chequea versión, firma criptográfica, - no expiración. Devuelve `(master_peer_id, session_peer_id)`. -- `SessionCert::verify_against_session(expected_session_pk)` — verify - + exige que el cert vincule esa session pubkey (previene reuso de - certs ajenos con keypairs distintas). -- `CertError` tipado: `UnknownVersion`, `DecodeMaster`, - `DecodeSession`, `InvalidSignature`, `Expired`, `SessionMismatch`, - `Sign`. -- `DEFAULT_SESSION_TTL = 24h`. - -Wire: -- `Hello.identity_cert: Option` agregado (default None, - back-compat). -- `Client::connect_with_stream_signed_with_cert(stream, card, wit, - session_kp, cert)` — variante que adjunta el cert. -- `network::connect_libp2p_with_cert(net, peer, card, wit, - session_kp, cert)` — paralelo a `connect_libp2p`. - -Server (`do_handshake`): -- Nuevo paso ANTES del policy gate: si `Hello.identity_cert.is_some()`, - se verifica con `verify_against_session(&hello.signature.public_key)`. - El `logical_peer` que se evalúa contra la policy es el - `master_peer_id` derivado, NO el session peer_id. -- Sin cert (path Fase 3): `logical_peer = expected_peer` (compat). -- Si el cert es inválido (firma rota, expirado, session mismatch), - rechazo con `Unauthorized` antes de evaluar policy. -- Migración gradual: clientes sin cert siguen funcionando contra - servers con policy basada en session peer_ids. - -Canonicalización del payload firmado: -``` -[u8 version][b"sess"][u32 LE session_pubkey_len][session_pubkey][u64 LE expires_at_ms] -``` -`SESSION_CERT_VERSION = 1` documenta el esquema; cualquier cambio -fuerza bump (clientes viejos no validan certs nuevos). - -Sobre el swarm-level deny: -- El `block_list` del swarm sigue operando con session peer_ids - (Noise sólo conoce eso). Si la operatoria lista master_peer_ids - en deny, el handshake-level gate los para; el swarm-level no. - El operador elige granularity: listar masters = robust a - rotaciones; listar sessions = rechazo más temprano. - -Tests: 8 unit en `identity::tests`: -- `issue_and_verify_cert` — roundtrip básico, peer_ids derivados. -- `verify_against_session_admits_matching` y - `_rejects_mismatch` — el cert vincula 1 sola session pubkey. -- `cert_with_zero_ttl_is_expired` — expiración chequeada con tiempo - real. -- `tampered_signature_rejected` y `tampered_expires_at_rejected` — - cualquier mutación del cert post-firma falla. -- `unknown_version_rejected` — schema versionado defensivamente. -- `rotated_session_with_same_master_yields_same_master_peer_id` — - la propiedad fundamental: rotar session NO cambia master_peer_id. - -Plus 1 E2E definitivo en `network_libp2p.rs`: -`identity_cert_allows_session_rotation_without_policy_change`. -- A configura `policy = allowlist[B.master_peer_id]` (master, no - session). -- B se conecta con session1 + cert(master, session1) → admitido. - Sesión registrada, farewell limpio. -- B "rota": genera session2 ≠ session1, mismo master, emite cert2. -- B se conecta con session2 + cert2 → admitido también, **sin que - A toque su allowlist**. -- Sanity: una session sin cert (cuyo session_peer NO está en allow) - es rechazada. - -40 tests verdes en brahman-handshake + brahman-net (24 unit -incluyendo identity + 7 handshake + 3 discovery + 6 libp2p -incluyendo rotation E2E). Ningún regreso. - -Wire en Arje queda como follow-up: ente-zero hoy es server-only y -no necesita identity (su keypair libp2p ya es estable vía -keypair_store). Cuando algún módulo de Arje haga conexiones -salientes con cert, se cargará la identity master separada de la -session vía nueva env `BRAHMAN_IDENTITY_PATH`. La API ya está -lista. - -### feat(brahman-net+handshake): swarm-level deny — la denylist se proyecta al block_list de libp2p -Optimización de seguridad: la denylist ya no espera al handshake -brahman para rechazar — ahora se proyecta al `block_list` behaviour -del swarm libp2p. Conexiones desde peers baneados son rechazadas -**antes del Noise handshake**, ahorrando el round-trip TCP+Noise -por cada intento denegado. - -Wire de bajo nivel (`brahman-net`): -- Nuevo behaviour `block_list: allow_block_list::Behaviour` - añadido al `BrahmanBehaviour` derivado. Vive junto a `stream`, - `kad`, `identify`. Default vacío al construir. -- Nuevos comandos `BlockPeer(PeerId)` y `UnblockPeer(PeerId)` en el - enum interno + handlers que llaman - `swarm.behaviour_mut().block_list.{block_peer,unblock_peer}`. -- API pública: `BrahmanNet::block_peer(peer)` y - `BrahmanNet::unblock_peer(peer)`. Idempotentes. -- Dep nueva: `libp2p-allow-block-list = "0.6"` (sub-crate, no es - feature de `libp2p` en 0.56). - -Wire en la política (`brahman_handshake::peer_policy`): -- `PeerPolicy` gana campo opcional `net: Arc>>>`. - Default `None` para preservar callers existentes. -- Nuevo método `attach_to_net(net: Arc)`: - - Sincronización inicial: itera la deny actual y llama - `net.block_peer(p)` por cada uno. - - Guarda el net para diff-sync en cada `reload`. -- `reload()` extendido: snapshot de `prev_deny` ANTES de mutar el - inner. Tras la mutación, llama `sync_deny_to_swarm(prev, new)` - que aplica `block_peer` por cada added y `unblock_peer` por cada - removed. -- Atomicidad preservada: si un archivo falla al parsear, el sync - no ocurre y la versión anterior persiste tanto en la policy - como en el block_list del swarm. - -Wire en Arje (`ente-zero`): -- Tras setup_brahman_net + setup_brahman_policy, si AMBOS están - presentes se llama `policy.attach_to_net(net.clone())` con un log - informativo. Sin policy o sin net, no hay attach (modo abierto - o solo gate-level deny). - -Tests: 1 nuevo E2E en `network_libp2p.rs`: -`swarm_level_deny_blocks_before_noise`. A configura policy con -deny de un peer + attach_to_net. Cliente baneado intenta -`connect_libp2p`; en lugar del `HandshakeError::Unauthorized` que -recibíamos antes (que requería completar Noise primero), ahora -falla con error de transporte/stream (o timeout, según timing) — -el dial nunca completa porque el swarm rechaza la conexión. - -5 tests verdes en `network_libp2p.rs` (roundtrip, mismatched signing, -allowlist, denylist handshake-level, denylist swarm-level). 31 tests -totales en brahman-handshake + brahman-net. Sin regresión en -ente-zero. - -Trade-offs: -- **Más eficiente** contra DoS: un atacante que prueba miles de - peer_ids no consume CPU del Noise handshake. -- **Misma fuente de verdad**: la denylist sigue viviendo en - `PeerPolicy` (un solo archivo, hot-reloadable). El swarm es un - cache derivado que se actualiza vía diff. No hay drift posible — - cada reload re-sincroniza atómicamente. -- **El handshake-level gate sigue activo** como segunda línea: si - por alguna razón un peer baneado pasa el block_list (race entre - reload y nueva conexión, o bug del crate), el handshake brahman - igual lo rechaza con `Unauthorized`. Defensa en profundidad. - -Pendientes futuros del changelog: -- Rotación de keypair sin perder peer_id (multi-key identity). - -### feat(brahman-handshake+ente-zero): denylist + hot reload de la política de peers -Consolida `PeerAllowlist` + nueva `PeerDenylist` en un único -`PeerPolicy` con allow + deny + hot reload vía `notify`. Cubre los -dos pendientes documentados en el commit anterior y simplifica la -API hacia un sólo punto de entrada. - -API consolidada en `brahman_handshake::peer_policy`: -- `PeerPolicy::open()` — todo permitido (default). -- `PeerPolicy::from_sets(allow: Option>, deny: BTreeSet)` - — política inline para tests. -- `PeerPolicy::from_files(allow_path?, deny_path?)` — carga ambos - archivos opcionales. -- `PeerPolicy::evaluate(peer) -> Decision` — `Admit | - DeniedByDenylist | NotInAllowlist`. Decision lleva su `reason()` - para logging consistente. -- `PeerPolicy::reload()` — recarga atómica desde los paths - asociados. **Si un archivo falla, conserva la versión anterior** - (un typo no debe tirar al Init en modo inseguro). -- `PeerPolicy::spawn_watcher() -> JoinHandle` — vigila los - archivos vía `notify`, debounce 250ms (coalesce de los varios - eventos típicos de un save), recarga atómica al detectar cambio. - -Orden de evaluación (deny-first): -1. Si `peer ∈ denylist` → `DeniedByDenylist`. -2. Si hay allowlist y `peer ∉ allowlist` → `NotInAllowlist`. -3. Resto → `Admit`. - -Esto significa que **deny gana sobre allow**: un peer en ambas listas -es rechazado. Diseño explícito para que la denylist sea la primitiva -de "kill switch" — agregar un peer al deny lo banea inmediatamente -sin importar dónde más esté listado. - -Watcher: vigila el **directorio padre** del archivo, no el archivo -mismo. Razón: editores típicos hacen rename-and-replace (escriben -a tmp y rename al destino), lo que rompe el watch del archivo pero -no el del dir. Filtra eventos por path al procesar. - -Wire en server: -- `ServerConfig.allowlist` → `ServerConfig.policy: Option` - (breaking rename, scope local al monorepo). Gate en `do_handshake` - llama `policy.evaluate(&peer)` y usa `decision.reason()` para el - mensaje de error tipado. - -Wire en Arje (`ente-zero`): -- Nueva env `BRAHMAN_PEER_DENYLIST` complementa - `BRAHMAN_PEER_ALLOWLIST`. Cualquiera (o ambas) activa la política. -- `setup_brahman_policy()` carga + arranca watcher. Devuelve - `(policy, JoinHandle)`; el handle se guarda en main para que el - thread no se aborte. -- Failure modes degradan a "modo abierto" (sin política) con log, - preservando la doctrina PID 1. - -Activación end-to-end con todas las capas activas: -```sh -BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 \ -BRAHMAN_PEER_ALLOWLIST=/etc/brahman/allow.txt \ -BRAHMAN_PEER_DENYLIST=/etc/brahman/deny.txt \ -ente-zero -# El operador puede editar deny.txt en caliente y la nueva regla -# entra en efecto en ~250ms sin restart del Init. -``` - -Tests: 10 unit en `peer_policy::tests`: -- `open_admits_anyone`, `allow_only_admits_listed`, - `deny_overrides_open`, `deny_overrides_allow` (deny-first - semantics). -- `from_files_with_both_lists`, `from_files_only_deny`, - `invalid_file_rejected_at_load`. -- `reload_picks_up_changes` — manualmente recarga y verifica. -- `reload_failure_preserves_previous_state` — invariante de - seguridad: archivo roto NO baja la política activa. -- `watcher_reloads_on_file_change` — E2E del watcher con notify - real: muta archivo, espera < debounce + margen, verifica que - la política refleja el cambio sin haber llamado reload manualmente. - -Plus 1 E2E nuevo en `network_libp2p.rs`: -`libp2p_handshake_denylist_blocks_listed_peer` — A configura -`policy = PeerPolicy::from_sets(None, [banned_peer])`. Cliente -con keypair baneada es rechazado; cliente con keypair distinta -pasa el handshake. - -30 tests verdes en brahman-handshake (16 unit + 7 handshake + 3 -discovery + 4 libp2p incluyendo allowlist + denylist E2E). Sin -regresión en ente-zero. - -Lo que cierra esta entrega: -- Política completa de admisión: open / allow-only / deny-only / - allow+deny. -- Hot reload sin restart del Init — el operador puede banear/admitir - peers en caliente editando archivos. -- Atomicidad: la recarga es del paquete `(allow, deny)` completo, no - de cada lista por separado. No hay momento donde una lista esté - vieja y la otra nueva. -- Resiliencia: errores de parseo NO bajan la política activa. - -Pendientes futuros del changelog: -- Aplicar la política a nivel de swarm vía `libp2p_allow_block_list:: - Behaviour` (rechazar ANTES del Noise handshake, ahorrar el - round-trip TCP+Noise por intento denegado). -- Rotación de keypair sin perder peer_id (multi-key identity). - -### feat(brahman-handshake+ente-zero): allowlist explícita de peers libp2p -Capa de política sobre el trust criptográfico de Fase 3. Hasta ahora -cualquier peer con keypair Ed25519 válida pasaba el handshake remoto; -con allowlist activa, sólo los peers explícitamente listados. Aplica -únicamente al path libp2p — el path Unix sigue usando SO_PEERCRED -del kernel, que es autenticación de proceso local, no de red. - -API nueva en `brahman_handshake::peer_allowlist`: -- `PeerAllowlist::from_iter([peer_id, ...])` para tests/inline. -- `PeerAllowlist::from_file(path)` parsea texto plano: un PeerId - base58 por línea, `#` para comentarios (línea entera o inline), - líneas vacías ignoradas. Errores de parseo incluyen número de - línea para debug rápido. -- `is_allowed(peer)`, `len()`, `is_empty()`, `iter()`. -- `AllowlistError { Io, InvalidPeerId }`. - -Wire en el server: -- `ServerConfig.allowlist: Option`. `None` = modo - abierto (compat con todo lo anterior). `Some` = sólo los listados. -- Gate en `do_handshake` ANTES de la verificación de firma — la - comparación O(log n) en BTreeSet es más barata que crypto, así - que rechazamos peers inválidos antes de gastar ciclos. Se devuelve - `HandshakeError::Unauthorized("peer X no está en la allowlist")`. - -Wire en Arje (`ente-zero`): -- Nueva env var `BRAHMAN_PEER_ALLOWLIST` apuntando a un archivo. -- `setup_brahman_allowlist()` carga al startup; degrada a `None` - (modo abierto) si el archivo falla, consistente con la doctrina - PID 1 de no romper por subsistemas opcionales. - -Ejemplo de archivo de allowlist: -```text -# Peers permitidos en la malla brahman de prod-eu-1 -# Generados con: ente-zero (peer_id loggeado al arrancar) -12D3KooWFooBarBazFooBarBazFooBarBazFooBarBazFooBarBaz -12D3KooWQuxQuxQuxQuxQuxQuxQuxQuxQuxQuxQuxQuxQuxQuxQux # operador 2 -``` - -Activación end-to-end: -```sh -BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 \\ -BRAHMAN_PEER_ALLOWLIST=/etc/brahman/allowlist.txt \\ -ente-zero -``` - -Tests: -- 6 unit en `peer_allowlist::tests`: from_iter, parse limpio, parse - con comentarios inline, parse rechaza PeerId inválido (y reporta - número de línea), I/O error en archivo faltante, empty list - rechaza todo. -- 1 E2E en `network_libp2p.rs`: - `libp2p_handshake_allowlist_admits_listed_rejects_others`. A - configura `allowlist = [allowed_peer]`. Cliente con keypair - permitida pasa el handshake (sesión registrada, farewell limpio). - Segundo cliente con keypair distinta es rechazado con error - ANTES de que se le verifique la firma. Sanidad: el conteo de - sesiones del server queda en 0 tras el rechazo. - -25 tests verdes en brahman-handshake (12 unit + 7 handshake legacy -+ 3 discovery + 3 libp2p). Ningún regreso en ente-zero (4/4 -keypair_store). - -Pendiente futuro: -- Denylist explícita (negada — banear peers específicos sin tener - que listar a todos los demás). -- Hot reload de la allowlist sin restart del Init (signal SIGHUP o - watch del archivo). -- Aplicar la política a nivel de swarm vía - `libp2p_allow_block_list::Behaviour` para rechazar conexiones - ANTES del Noise handshake (hoy se rechaza después, gastando un - round-trip TCP+Noise por cada intento denegado). - -### feat(brahman-net+handshake): stop_providing automático en cleanup de sesión -Cierra el pendiente conocido del DHT: hasta ahora cuando una sesión -con outputs cerraba (Farewell, EOF, error), el record que la -anunciaba en el DHT seguía vivo hasta su TTL natural (~24h en kad -default). Consumers remotos podían descubrir un peer "vivo" que ya -no servía nada. - -Cambios: -- **`BrahmanNet::stop_providing(key)`** (nuevo): contraparte simétrica - de `start_providing`. Manda `Command::StopProviding` al swarm que - llama `kad.stop_providing(&key)`. Borra el record del provider - store local al instante; replicas en peers remotos siguen - expirando por TTL (kad no expone retracción cross-peer, simétrico - al hecho de que `start_providing` también propaga eventualmente). -- **`brahman_handshake::network::withdraw_outputs(net, card)`** - (nuevo): contraparte de `announce_outputs`. Itera `card.flow.output` - y llama `net.stop_providing(flow_dht_key(...))` por cada uno. -- **`server::cleanup`**: extrae la `ResolvedCard` removida del registro - de sesiones (en lugar de descartarla con `remove`) y, si - `config.net` está set, llama `withdraw_outputs(net, &card)` antes - de `broadcast_match_diffs`. - -Tests: nuevo E2E `dht_discovery_withdraws_on_session_cleanup`: -1. A registra Card con `flow.output = monad-list:json`. -2. B descubre a A vía `find_remote_providers` — confirma - `before.contains(&a_peer)`. -3. Cliente local de A hace `farewell` → cleanup → withdraw_outputs. -4. Espera a que la sesión salga del registro (señal de cleanup - completado) + 100ms para que el swarm procese el Command. -5. Nueva query desde B: `after` NO debe contener `a_peer`. - -3 tests verdes en `network_discovery.rs` (positivo, negativo, -withdraw). 18 tests totales en handshake + net. - -Pendiente futuro: retracción cross-peer en kad (requeriría extensión -del protocolo libp2p, no soportada hoy). Aceptable: simétrico al -modelo de propagación eventual del DHT. - -### feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + identidad persistente) -Cierra el último pendiente del plan de red: Arje ahora puede arrancar -opcionalmente con `BrahmanNet` configurado, persistir su identidad -libp2p entre reboots, y participar en la malla brahman como nodo -público. Sin breaking changes: usuarios actuales (sin env vars) siguen -viendo el comportamiento Unix-only de antes. - -Activación por env vars: -- **`BRAHMAN_LISTEN_MULTIADDR`** — si set, activa la red P2P. Ej: - `/ip4/0.0.0.0/tcp/4101` (público), `/ip4/127.0.0.1/tcp/0` (loopback, - port aleatorio). Sin la var, `brahman_net = None` y todo sigue - como antes. -- **`BRAHMAN_KEYPAIR_PATH`** — override del path donde se persiste - la keypair Ed25519 de identidad libp2p del nodo. Defaults sensatos: - - PID 1 (root): `/var/lib/brahman/init-keypair.bin`. - - Dev mode: `$XDG_DATA_HOME/brahman/init-keypair.bin` → - `$HOME/.local/share/brahman/init-keypair.bin` → - `/tmp/brahman-init-keypair.bin` (último recurso). -- **`BRAHMAN_BOOTSTRAP_PEERS`** — lista coma-separada de multiaddrs - para dial-ear al arranque y entrar al DHT. Sin esto, el nodo - arranca aislado hasta que alguien se conecte a él. - -Comportamiento al activarse: -1. `keypair_store::load_or_generate(path)` carga la keypair de disco - o genera+persiste una nueva (32 bytes raw, permisos 0o600, - atomic rename). Reboots conservan el `peer_id`. -2. `BrahmanNet::with_keypair(kp)` arma el swarm con esa identidad. -3. `net.listen(multiaddr)` espera dirección resuelta y la loggea. -4. `BRAHMAN_BOOTSTRAP_PEERS` (si set) → dial a cada multiaddr. -5. El handshake server se levanta con `ServerConfig.net = Some(net)`, - que activa `announce_outputs` automático en el DHT por cada Card - con outputs. -6. Además del Unix accept loop (existing), se monta un libp2p accept - loop sobre el mismo `Server` compartido. Sesiones locales y - remotas conviven en las mismas tablas (sessions, push_table, - broker, last_matches). - -Refactor del Unix accept loop: antes consumía el server vía -`server.run().await`; ahora usa `Arc::accept_one().await` en -loop para coexistir con el libp2p accept loop sin moverse el server. - -Degradación grácil en cada paso: si la keypair no carga, si el -multiaddr es inválido, si el listen falla, si el bootstrap dial -revienta — loggeamos y seguimos en modo Unix-only. La doctrina de -PID 1 ("ningún subsistema opcional rompe el bucle primordial") se -mantiene. - -Tests: 4 unit en `keypair_store`: -- `generate_persist_and_reload_yields_same_peer_id` — peer_id estable - across reloads (la propiedad fundamental). -- `rejects_corrupted_file` — archivo de tamaño incorrecto rechazado. -- `persisted_file_is_owner_only` — permisos 0o600 verificados. -- `default_path_honors_env` — `BRAHMAN_KEYPAIR_PATH` override - respeta tanto dev como root mode. - -Ente-zero compila clean. Ningún test del workspace regresa. - -Lo que esto desbloquea hoy: -- Para activar Arje como nodo público, basta: - ```sh - BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero - ``` -- Cualquier consumer (en otra máquina) puede luego dial-ar a ese - multiaddr + descubrir Cards anunciadas via DHT + abrir handshake - remoto firmado. -- La identidad del nodo (su `peer_id`) sobrevive reboots, así que - los nodos remotos pueden cachear "este peer_id es Arje en - máquina X" sin invalidarse cada vez. - -Pendientes futuros: -- `stop_providing` al cleanup de sesión (records DHT con TTL ~24h). -- Allowlist/Denylist de peers (PKI explícito). -- Rotación de keypair sin perder peer_id (multi-key identity). - -### feat(brahman-handshake): Fase 3 — trust remoto vía firma Ed25519 anclada al peer libp2p -Cuarto y último paso del plan "el encuentro entre Entes no se -restringe a local". Cierra la falla de seguridad que dejaba la red -P2P abierta: hasta ahora, un atacante que pudiera dial-ar al -multiaddr del Init podía registrar cualquier Card con cualquier -label/flow. Fase 3 cierra esto exigiendo que el Hello vía libp2p -venga firmado con la **misma keypair Ed25519 que produce el -`peer_id` autenticado por Noise**. - -Modelo: -- **Local Unix**: SO_PEERCRED del kernel autentica al cliente. Firma - opcional. Si está presente, igual se verifica (defensa en - profundidad). -- **Remoto libp2p**: firma obligatoria. La public key del Hello debe - derivar al `peer_id` que Noise ya autenticó. Si falta o no coincide - → `HandshakeError::Unauthorized`. - -Wire (`brahman_handshake::messages`): -- `Hello.signature: Option` (nuevo, default None). -- `HelloSignature { public_key: Vec, signature: Vec }` — - public_key en formato canónico libp2p (`encode_protobuf`), firma - Ed25519 sobre `(SIGNATURE_VERSION, WireCard, Option)` - serializado postcard. -- `SIGNATURE_VERSION = 1` documenta el esquema del payload firmado; - bump al cambiar. - -Nuevo módulo `brahman_handshake::signature`: -- `sign_hello(keypair, card, wit) -> HelloSignature`. -- `verify_hello(sig, card, wit, expected_peer) -> Result<(), SignatureError>`. -- `SignatureError` tipado (`DecodeKey`, `EncodePayload`, `Invalid`, - `PeerMismatch`, `Missing`, `Unexpected`). - -Server: -- `Session` gana `expected_peer: Option`. -- `Server::session_from_libp2p_stream(stream, peer)` (nuevo) - construye Session con `expected_peer = Some(peer)`. - `session_from_stream` (Unix/in-memory) sigue con `None`. -- `do_handshake` exige firma + verifica peer match cuando - `expected_peer.is_some()`. Si no, verifica firma presente por - consistencia interna pero no exige que esté. -- `network::run_libp2p_accept_loop` ahora usa - `session_from_libp2p_stream(stream.compat(), peer)` para propagar - la identidad libp2p al gate de trust. - -Client: -- `Client::connect_with_stream_signed(stream, card, wit, &Keypair)` - (nuevo) firma el Hello antes de mandarlo. -- `Client::connect_with_stream` sigue existiendo sin firma (path - Unix / tests). -- `Client::connect`/`connect_with` (Unix) no cambian — siguen sin - firma porque SO_PEERCRED autentica. -- `network::connect_libp2p(net, peer, card, wit, keypair)` - **breaking change**: gana parámetro `keypair: &Keypair`. - -BrahmanNet: -- Almacena la `Keypair` en `Arc` (libp2p Keypair no es - Clone; el truco es duplicar el `ed25519::Keypair` interno que sí - es Clone, una copia para Noise/swarm y otra para signing). -- `BrahmanNet::keypair() -> Arc` accessor para que callers - puedan firmar con la misma identidad libp2p del nodo sin tener - que mantener la keypair por separado. -- `with_keypair` rechaza keypairs no-Ed25519 (RSA/ECDSA/Secp256k1 - vendrían a futuro si se necesitan). - -Tests: -- 4 unit en `signature::tests`: roundtrip propio, peer mismatch, - card tampered, signature flipped. -- 1 E2E nuevo en `tests/network_libp2p.rs`: - `libp2p_handshake_rejects_mismatched_signing_key` — el cliente - intenta firmar con keypair distinta a la del net; server rechaza. -- E2E positivo (`libp2p_handshake_roundtrip`) ahora pasa la keypair - del client_net y debe verificar OK. -- Discovery + handshake legacy + signature: 90+ tests verdes en - brahman-handshake/brahman-net/brahman-card/minga-p2p. - -Lo que esto cierra: -- Brahman-net es una malla públicamente dial-able **con - autenticación criptográfica end-to-end**: Noise para el transport, - Ed25519 para las Cards. -- La cadena completa de discovery + connect + trust funciona - cross-machine sin paths hardcodeados ni confianza implícita. -- El plan original ("el encuentro entre Entes no se restringe a - local, la ejecución remota está pensada desde el principio") - está implementado y testeado. - -Pendientes (futuro, no de hoy): -- `stop_providing` al cleanup de sesión (records DHT viven hasta - TTL ~24h). -- Wire de Arje (`ente-zero`) para arrancar opcionalmente con - `BrahmanNet` configurado y `ServerConfig.net = Some(...)`. -- Allowlist/Denylist de peers (hoy cualquier peer Ed25519-válido - pasa el trust gate; producción podría querer un PKI explícito). -- Persistencia de la keypair de identidad del nodo entre reboots. - -### feat(brahman-handshake): Fase 2 — discovery remoto vía DHT por flow type -Tercer paso del plan "el encuentro entre Entes no se restringe a -local". Cuando un Init local acepta una sesión cuya Card declara -outputs, anuncia al DHT (Kademlia, vía `brahman-net`) que él provee -esos flow types. Cualquier nodo conectado al mismo DHT puede -consultar y obtener la lista de `PeerId`s que sirven el flow. - -API nueva en `brahman_handshake::network`: -- `flow_dht_key(flow_name, type_ref) -> [u8; 32]`: blake3 hash de - `"brahman-flow|v1|{flow}|{type_canon}"`. Determinístico cross-host. - Cambiar la canonicalización rompe compatibilidad — el prefijo `v1` - documenta la versión del esquema y obliga a bump al modificar. -- `announce_outputs(net, card)`: llama `start_providing` en el DHT - por cada `Flow` en `card.flow.output`. Idempotente, fire-and-forget. -- `find_remote_providers(net, flow_name, type_ref) -> Vec`: - query DHT por la key derivada. Lista vacía si nadie anuncia o si - la query no resuelve dentro del timeout interno de Kad. - -Wire en el server: -- `ServerConfig` gana `pub net: Option>`. Si está set, - cada Card registrada con outputs se anuncia automáticamente al DHT - desde `register_session`. `None` = server "ciego al DHT" (correcto - cuando no hay conectividad o el operador no quiere exponer). -- `ServerConfig` ahora tiene `Debug` manual (BrahmanNet no implementa - Debug; loggeamos sólo presencia/ausencia). - -Canonicalización del TypeRef: -- `Primitive { name }` → `prim:{name}` -- `Wit { package, interface, name }` → `wit:{package}#{interface_or_empty}#{name}` - -Tests: 2 nuevos en `tests/network_discovery.rs`: -- `dht_discovery_finds_remote_provider`: dos nodos, A registra Card - con `flow.output = monad-list:json`, B dial-ea a A y descubre el - `peer_id` de A vía `find_remote_providers`. Asserts contains. -- `dht_discovery_negative_unknown_flow`: B busca un flow que nadie - anunció, devuelve lista vacía sin colgarse. - -Lo que esto desbloquea: -- Un `nouser daemon` corriendo en máquina A puede ser descubierto por - un `nouser-explorer` en máquina B sin conocimiento previo del peer - — sólo necesitan compartir DHT (vía bootstrap inicial). -- La cadena completa "explorer → daemon → llm-provider" puede cruzar - máquinas, no sólo procesos. - -Lo que queda para Fase 3 (trust): -- Cards remotas se aceptan hoy sin verificación. Para producción se - necesita firma Ed25519 sobre la Card y verificación antes de - aceptar el Hello remoto. Local sigue confiando en SO_PEERCRED. -- Stop-providing al cleanup de sesión (hoy records DHT viven hasta - TTL ~24h aunque la sesión cierre). - -### feat(brahman-handshake): Fase 1 — handshake brahman sobre stream libp2p -Segundo paso del plan "el encuentro entre Entes no se restringe a -local". El protocolo brahman (Hello / HelloAck / Ping / Pong / -MatchEvent / Farewell, frames postcard length-prefixed) ahora también -viaja sobre streams libp2p de la malla `brahman-net` — el mismo Init -acepta sesiones por Unix socket Y por libp2p indistintamente, y un -consumer remoto puede dial-ar al multiaddr y completar handshake. - -Cambios: -- **`Session` y `Client` genéricos**: ambos dejan de estar atados - a `UnixStream` y pasan a ser genéricos sobre `S: AsyncRead + - AsyncWrite + Unpin + Send + 'static`. El path Unix queda como - `Client = Client` (default genérico). Constructores - nuevos: `Server::session_from_stream(stream)`, - `Client::connect_with_stream(stream, card, wit)`. -- **Refactor del post-handshake con split**: `tokio::select!` sobre - `&mut self.stream` requería `S: Sync` indirectamente, y - `libp2p::Stream` no es Sync. Reemplazado por - `tokio::io::split(stream)` → reader loop principal + writer task - separada que drena el push channel. Writer compartido bajo - `Arc>>` para serializar Pong/Error inline con - los MatchEvents pusheados. Cleanup garantizado en todas las ramas. - La lógica del post-handshake migra a funciones libres - (`run_post_handshake`, `handle_inbound_frame`, `cleanup`, - `broadcast_match_diffs`, `do_handshake`, `register_session`, - `validate_hello`). -- **Nuevo módulo `brahman-handshake::network`**: - - `BRAHMAN_HANDSHAKE_PROTOCOL = "/brahman/handshake/1.0.0"`. - - `LibP2pHandshakeStream = Compat` (alias del - stream una vez convertido al mundo `tokio::io`). - - `run_libp2p_accept_loop(server, net)`: bucle accept sobre el - protocolo que delega cada stream entrante a una `Session` - construida vía `server.session_from_stream(stream.compat())`. - Sesiones libp2p y Unix conviven en el mismo `Server` — - comparten broker, push table, last_matches. - - `connect_libp2p(net, peer, card, wit)`: abre stream libp2p al - `peer` y arranca handshake. - - `NetworkError` tipado (`OpenStream`, `Handshake`, `AcceptStream`). - -Deps: `brahman-handshake` gana `brahman-net`, `futures`, `tokio-util`. -`brahman-net` re-exporta `Multiaddr`, `PeerId`, `Stream`, -`StreamProtocol`, `Protocol`, `OpenStreamError` para que callers no -necesiten dep directa a libp2p. - -Tests: -- 9 tests unit + integration verdes (sin regresión en el path Unix). -- Nuevo `tests/network_libp2p.rs`: test E2E que arma server con - Unix socket + BrahmanNet, hace listen TCP, monta el accept loop; - cliente con su propio BrahmanNet dial-ea al peer_id, completa - handshake remoto, pinguea, farewell. Verifica que la sesión se - registró durante la conversación y se removió tras farewell. - -Próximo: Fase 2 (discovery remoto vía DHT — anunciar Cards bajo -flow type, broker query local + remoto). - -### feat(brahman-net): capa P2P compartida — Fase 0 (extracción del swarm libp2p) -Primer paso del plan "el encuentro entre Entes no se restringe a local". -El swarm libp2p que vivía dentro de `minga-p2p::network` (282 LOC) sale -a una crate compartida `brahman-net` para que cualquier protocolo de la -familia (handshake brahman remoto en Fase 1, sync minga, futuros) reuse -una sola malla TCP+Noise+Yamux+Kad+Identify+Stream. - -Diseño: -- `BrahmanNet::{new, with_keypair}` arma el swarm con DHT en modo - Server, Identify auto-poblando el routing table de Kad, y un - `stream::Control` accesible para que cada protocolo registre su - `StreamProtocol` y abra/acepte streams sin acoplarse al swarm. -- API de comandos uniforme: `dial`, `listen`, `add_dht_peer`, - `find_closest_peers`, `start_providing`, `find_providers`. -- Pública: `peer_id` (libp2p) + `control` (stream::Control). -- Re-exporta `Stream` y `StreamProtocol` para que callers no necesiten - importar libp2p directo. - -Migración: -- `minga-p2p::network` reduce de 282 LOC a 22: ahora sólo re-exporta - `BrahmanNet` bajo el alias histórico `LibP2pNode` (zero churn en - `MingaPeer`) y declara la const `SYNC_PROTOCOL = "/minga/sync/1.0.0"` - específica del sub-protocolo Minga. -- Cualquier consumer que necesite armar un nodo P2P puede importar - `brahman_net::BrahmanNet` directo sin pasar por minga. -- Deps de `minga-p2p` ganan `brahman-net`; el resto del grafo - (libp2p, libp2p-stream, futures, tokio-util) sigue igual porque - `MingaPeer` aún los usa para la lógica específica de sync. - -Aclaración semántica anclada por el usuario: **Arje** es el init -(PID 1, runtime, ente-zero/kernel/soma); **Brahman** es el encuentro -entre Entes (handshake/broker/card/sidecar/ahora también net). El -nombre de la crate refleja que la malla pertenece al encuentro, no -al runtime — Arje puede usar la malla, Minga usa la malla, cualquier -futuro módulo (Nakui remoto, p.ej.) la usa, sin acoplarse a Minga. - -Tests: minga-p2p completo verde (58 tests, sin regresión). Behavior -verificado idéntico — sólo se movió código, ningún cambio funcional. -Próximo: Fase 1 (handshake brahman sobre libp2p stream). - -### refactor(explorer+card): independencia jerárquica enforced — cliente con los wire types + fallback al default path -Cierra el único debt estructural detectado en el audit de -independencia: `nouser-explorer` ya no arrastra `nouser-core` -(que aportaba `notify`/`walkdir`/`sled`/`blake3` al grafo de -compilación de una UI que sólo habla JSON contra un socket). - -Cambios: -- **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 dependener de - `nouser-core`. Verificado con `cargo tree`: `notify`, `sled`, - `blake3` desaparecen del grafo del binario. (`walkdir` sigue - pero llega vía `gpui_util` → `rust-embed`, fuera de nuestro - control y pre-existente.) -- **Fallback "falla hacia la simplicidad"**: nueva función - `resolve_socket()` en el explorer intenta primero broker - discovery; si el broker no responde / no hay init vivo, - fallback directo a `nouser_card::query::transport::default_socket_path()`. - El explorer queda funcional contra un daemon "huérfano" - (corriendo standalone sin init) — completa la cadena - "consciente cuando hay ecosistema, soberano cuando está solo". -- `socket_source` en el header gana un tercer estado - `"default-path"` para que el usuario vea por dónde se conectó. - -Audit estructural confirmó que el resto del ecosistema ya -respeta el principio: todos los `yahweh-*` viewers, `minga-cli`, -`minga-core`, `nouser-card`, `nouser-nous`, los providers -`nouser-nous-{mock,real}` y `nakui-core` corren standalone con -soft-fail hacia infra brahman cuando está ausente. Brahman es -"pegamento opcional, no chasis obligatorio" — y ahora el grafo -de Cargo lo enforcea, no sólo la convención. - -Tests: 4 (sidecar) + 10 (nouser-card) + 27 (nouser-core) verdes. -El cliente movido se ejercita end-to-end por los 3 tests integración -de `engine_socket` (importa ahora `nouser_card::query::client`). - -### 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 }`. -- 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`: 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` cache + `snapshot: Option` - + `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` 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>`. -- `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` - 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` 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` 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` 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>` 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` — 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` 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: ()` 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` 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` 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`. 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 - 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 reales por cada Mónada. -- bin nouser nuevo subcomando: `attract `. - - 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 `. - - 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` 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 ` 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` 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 `, `show `, - `json `. 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, priority_offset: i8 }` - declara un override per-contexto. -- `Card.priority_contexts: BTreeMap` y mismo en - `WireCard` (cruza el wire). Las conversiones `From` lo propagan. -- `BrokerConfig.current_context: Option`. 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: ` 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` 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` recursivo). Conversiones `From` y - `From` 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`. 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)`. -- `brahman-broker::Broker::register` ahora toma `Option` - 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` (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>>>) - 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` 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>>`. -- `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, 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). +Histórico particionado el 2026-05-19 por proyecto/intención. Cada +archivo bajo `docs/changelog/` consolida las entradas de un +subdirectorio del workspace. + +- [`akasha`](docs/changelog/akasha.md) — 15 entradas +- [`init`](docs/changelog/init.md) — 1 entradas +- [`minga`](docs/changelog/minga.md) — 5 entradas +- [`misc`](docs/changelog/misc.md) — 2 entradas +- [`nahual`](docs/changelog/nahual.md) — 17 entradas +- [`nakui`](docs/changelog/nakui.md) — 20 entradas +- [`protocol`](docs/changelog/protocol.md) — 24 entradas diff --git a/Cargo.lock b/Cargo.lock index 4993f3f..88352fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,97 @@ dependencies = [ "memchr", ] +[[package]] +name = "akasha-card" +version = "0.1.0" +dependencies = [ + "brahman-card", + "serde", + "serde_json", + "thiserror 2.0.18", + "ulid", +] + +[[package]] +name = "akasha-core" +version = "0.1.0" +dependencies = [ + "akasha-card", + "akasha-nous", + "blake3", + "brahman-card", + "brahman-handshake", + "brahman-sidecar", + "notify", + "serde", + "serde_json", + "shuma-discern", + "sled", + "tempfile", + "thiserror 2.0.18", + "tokio", + "ulid", + "walkdir", +] + +[[package]] +name = "akasha-explorer" +version = "0.1.0" +dependencies = [ + "akasha-card", + "brahman-card", + "brahman-sidecar", + "gpui", + "nahual-launcher", + "nahual-theme", + "nahual-widget-app-header", + "nahual-widget-banner", + "nahual-widget-card", +] + +[[package]] +name = "akasha-nous" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "akasha-nous-mock" +version = "0.1.0" +dependencies = [ + "akasha-card", + "akasha-core", + "akasha-nous", + "brahman-card", + "brahman-sidecar", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "ulid", +] + +[[package]] +name = "akasha-nous-real" +version = "0.1.0" +dependencies = [ + "akasha-nous", + "brahman-card", + "brahman-sidecar", + "ente-cas", + "fastembed", + "serde_json", + "sled", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "ulid", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -940,10 +1031,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "barra-core" +version = "0.1.0" + [[package]] name = "barra-web" version = "0.1.0" dependencies = [ + "barra-core", "js-sys", "wasm-bindgen", "web-sys", @@ -1308,12 +1404,12 @@ dependencies = [ "brahman-handshake", "brahman-sidecar", "gpui", + "nahual-launcher", + "nahual-theme", + "nahual-widget-app-header", + "nahual-widget-banner", + "nahual-widget-stat-card", "ulid", - "yahweh-launcher", - "yahweh-theme", - "yahweh-widget-app-header", - "yahweh-widget-banner", - "yahweh-widget-stat-card", ] [[package]] @@ -1342,14 +1438,14 @@ dependencies = [ name = "brahman-cards" version = "0.1.0" dependencies = [ + "akasha-card", "brahman-card", + "nahual-meta-schema", "nickel-lang", - "nouser-card", "serde", "serde_json", "thiserror 2.0.18", "ulid", - "yahweh-meta-schema", ] [[package]] @@ -2201,12 +2297,12 @@ dependencies = [ "cosmobiologia-tree", "directories", "gpui", + "nahual-core", + "nahual-theme", + "nahual-widget-container-core", + "nahual-widget-splitter", + "nahual-widget-theme-switcher", "serde_json", - "yahweh-core", - "yahweh-theme", - "yahweh-widget-container-core", - "yahweh-widget-splitter", - "yahweh-widget-theme-switcher", ] [[package]] @@ -2219,7 +2315,7 @@ dependencies = [ "cosmobiologia-render", "cosmobiologia-theme", "gpui", - "yahweh-theme", + "nahual-theme", ] [[package]] @@ -2291,8 +2387,8 @@ dependencies = [ "cosmobiologia-modules", "cosmobiologia-theme", "gpui", + "nahual-theme", "serde_json", - "yahweh-theme", ] [[package]] @@ -2340,7 +2436,7 @@ name = "cosmobiologia-theme" version = "0.1.0" dependencies = [ "gpui", - "yahweh-theme", + "nahual-theme", ] [[package]] @@ -2350,9 +2446,9 @@ dependencies = [ "cosmobiologia-model", "cosmobiologia-store", "gpui", - "yahweh-theme", - "yahweh-widget-text-input", - "yahweh-widget-tree", + "nahual-theme", + "nahual-widget-text-input", + "nahual-widget-tree", ] [[package]] @@ -5724,174 +5820,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "lapaloma" -version = "0.1.0" -dependencies = [ - "lapaloma-cartesian", - "lapaloma-core", - "lapaloma-export", - "lapaloma-financial", - "lapaloma-flow", - "lapaloma-heatmap", - "lapaloma-mesh", - "lapaloma-phosphor", - "lapaloma-polar", - "lapaloma-render", - "lapaloma-stream", - "lapaloma-treemap", -] - -[[package]] -name = "lapaloma-cartesian" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-core" -version = "0.1.0" - -[[package]] -name = "lapaloma-demo" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-cartesian", - "lapaloma-core", - "lapaloma-render", - "yahweh-launcher", - "yahweh-theme", -] - -[[package]] -name = "lapaloma-export" -version = "0.1.0" -dependencies = [ - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-financial" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-cartesian", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-financial-demo" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-cartesian", - "lapaloma-financial", - "lapaloma-render", - "yahweh-launcher", - "yahweh-theme", -] - -[[package]] -name = "lapaloma-flow" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-heatmap" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-mesh" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-phosphor" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-phosphor-demo" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-phosphor", - "lapaloma-render", - "yahweh-launcher", - "yahweh-theme", -] - -[[package]] -name = "lapaloma-polar" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-render" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", -] - -[[package]] -name = "lapaloma-stream" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - -[[package]] -name = "lapaloma-stream-demo" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", - "lapaloma-stream", - "yahweh-launcher", - "yahweh-theme", -] - -[[package]] -name = "lapaloma-treemap" -version = "0.1.0" -dependencies = [ - "gpui", - "lapaloma-core", - "lapaloma-render", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -6828,11 +6756,11 @@ version = "0.1.0" dependencies = [ "gpui", "minga-store", - "yahweh-launcher", - "yahweh-theme", - "yahweh-widget-app-header", - "yahweh-widget-banner", - "yahweh-widget-stat-card", + "nahual-launcher", + "nahual-theme", + "nahual-widget-app-header", + "nahual-widget-banner", + "nahual-widget-stat-card", ] [[package]] @@ -7070,6 +6998,262 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "nahual-bus" +version = "0.1.0" +dependencies = [ + "gpui", +] + +[[package]] +name = "nahual-core" +version = "0.1.0" +dependencies = [ + "async-trait", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "nahual-database-explorer" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", + "nahual-provider-sqlite", + "nahual-theme", + "nahual-widget-tree", +] + +[[package]] +name = "nahual-file-explorer" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", + "nahual-provider-fs", + "nahual-theme", + "nahual-widget-text-input", + "nahual-widget-tree", +] + +[[package]] +name = "nahual-image-viewer" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-bus", + "nahual-theme", +] + +[[package]] +name = "nahual-launcher" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + +[[package]] +name = "nahual-meta-runtime" +version = "0.1.0" +dependencies = [ + "nahual-meta-schema", + "serde_json", + "thiserror 2.0.18", + "uuid", +] + +[[package]] +name = "nahual-meta-schema" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.18", +] + +[[package]] +name = "nahual-provider-fs" +version = "0.1.0" +dependencies = [ + "async-trait", + "nahual-core", + "notify", + "shuma-discern", + "tokio", +] + +[[package]] +name = "nahual-provider-sqlite" +version = "0.1.0" +dependencies = [ + "async-trait", + "nahual-core", + "rusqlite", + "tokio", +] + +[[package]] +name = "nahual-shell" +version = "0.1.0" +dependencies = [ + "brahman-card", + "brahman-sidecar", + "gpui", + "nahual-bus", + "nahual-core", + "nahual-database-explorer", + "nahual-file-explorer", + "nahual-image-viewer", + "nahual-provider-fs", + "nahual-provider-sqlite", + "nahual-text-viewer", + "nahual-theme", + "nahual-widget-container-core", + "nahual-widget-splitter", + "nahual-widget-tabs", + "nahual-widget-tiled", + "nahual-widget-tree", + "notify", + "serde", + "serde_json", + "tokio", + "ulid", +] + +[[package]] +name = "nahual-text-viewer" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-bus", + "nahual-core", + "nahual-provider-fs", + "nahual-provider-sqlite", + "nahual-theme", +] + +[[package]] +name = "nahual-theme" +version = "0.1.0" +dependencies = [ + "gpui", +] + +[[package]] +name = "nahual-widget-app-header" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", + "nahual-widget-theme-switcher", +] + +[[package]] +name = "nahual-widget-banner" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + +[[package]] +name = "nahual-widget-card" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + +[[package]] +name = "nahual-widget-container-core" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", +] + +[[package]] +name = "nahual-widget-meta-form" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-meta-runtime", + "nahual-meta-schema", + "nahual-theme", + "nahual-widget-banner", + "nahual-widget-text-input", + "nahual-widget-theme-switcher", + "serde_json", + "uuid", +] + +[[package]] +name = "nahual-widget-splitter" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", + "nahual-theme", + "nahual-widget-container-core", +] + +[[package]] +name = "nahual-widget-stat-card" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", + "nahual-widget-card", +] + +[[package]] +name = "nahual-widget-tabs" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", + "nahual-theme", + "nahual-widget-container-core", +] + +[[package]] +name = "nahual-widget-text-input" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + +[[package]] +name = "nahual-widget-theme-switcher" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + +[[package]] +name = "nahual-widget-tiled" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-core", + "nahual-theme", + "nahual-widget-container-core", +] + +[[package]] +name = "nahual-widget-tree" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-theme", +] + [[package]] name = "nakui-core" version = "0.1.0" @@ -7094,16 +7278,16 @@ name = "nakui-explorer" version = "0.1.0" dependencies = [ "gpui", + "nahual-launcher", + "nahual-meta-runtime", + "nahual-theme", + "nahual-widget-app-header", + "nahual-widget-banner", + "nahual-widget-card", "nakui-core", "serde_json", "tempfile", "uuid", - "yahweh-launcher", - "yahweh-meta-runtime", - "yahweh-theme", - "yahweh-widget-app-header", - "yahweh-widget-banner", - "yahweh-widget-card", ] [[package]] @@ -7112,15 +7296,15 @@ version = "0.1.0" dependencies = [ "brahman-cards", "gpui", + "nahual-meta-runtime", + "nahual-meta-schema", + "nahual-theme", + "nahual-widget-meta-form", + "nahual-widget-text-input", "nakui-core", "serde_json", "tempfile", "uuid", - "yahweh-meta-runtime", - "yahweh-meta-schema", - "yahweh-theme", - "yahweh-widget-meta-form", - "yahweh-widget-text-input", ] [[package]] @@ -7457,97 +7641,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "nouser-card" -version = "0.1.0" -dependencies = [ - "brahman-card", - "serde", - "serde_json", - "thiserror 2.0.18", - "ulid", -] - -[[package]] -name = "nouser-core" -version = "0.1.0" -dependencies = [ - "blake3", - "brahman-card", - "brahman-handshake", - "brahman-sidecar", - "notify", - "nouser-card", - "nouser-nous", - "serde", - "serde_json", - "shipote-discern", - "sled", - "tempfile", - "thiserror 2.0.18", - "tokio", - "ulid", - "walkdir", -] - -[[package]] -name = "nouser-explorer" -version = "0.1.0" -dependencies = [ - "brahman-card", - "brahman-sidecar", - "gpui", - "nouser-card", - "yahweh-launcher", - "yahweh-theme", - "yahweh-widget-app-header", - "yahweh-widget-banner", - "yahweh-widget-card", -] - -[[package]] -name = "nouser-nous" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "nouser-nous-mock" -version = "0.1.0" -dependencies = [ - "brahman-card", - "brahman-sidecar", - "nouser-card", - "nouser-core", - "nouser-nous", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", - "ulid", -] - -[[package]] -name = "nouser-nous-real" -version = "0.1.0" -dependencies = [ - "brahman-card", - "brahman-sidecar", - "ente-cas", - "fastembed", - "nouser-nous", - "serde_json", - "sled", - "tempfile", - "tokio", - "tracing", - "tracing-subscriber", - "ulid", -] - [[package]] name = "ntapi" version = "0.4.3" @@ -8330,6 +8423,174 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pineal" +version = "0.1.0" +dependencies = [ + "pineal-cartesian", + "pineal-core", + "pineal-export", + "pineal-financial", + "pineal-flow", + "pineal-heatmap", + "pineal-mesh", + "pineal-phosphor", + "pineal-polar", + "pineal-render", + "pineal-stream", + "pineal-treemap", +] + +[[package]] +name = "pineal-cartesian" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-core" +version = "0.1.0" + +[[package]] +name = "pineal-demo" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-launcher", + "nahual-theme", + "pineal-cartesian", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-export" +version = "0.1.0" +dependencies = [ + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-financial" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-cartesian", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-financial-demo" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-launcher", + "nahual-theme", + "pineal-cartesian", + "pineal-financial", + "pineal-render", +] + +[[package]] +name = "pineal-flow" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-heatmap" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-mesh" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-phosphor" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-phosphor-demo" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-launcher", + "nahual-theme", + "pineal-core", + "pineal-phosphor", + "pineal-render", +] + +[[package]] +name = "pineal-polar" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-render" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", +] + +[[package]] +name = "pineal-stream" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + +[[package]] +name = "pineal-stream-demo" +version = "0.1.0" +dependencies = [ + "gpui", + "nahual-launcher", + "nahual-theme", + "pineal-core", + "pineal-render", + "pineal-stream", +] + +[[package]] +name = "pineal-treemap" +version = "0.1.0" +dependencies = [ + "gpui", + "pineal-core", + "pineal-render", +] + [[package]] name = "piper" version = "0.2.5" @@ -10215,7 +10476,13 @@ dependencies = [ ] [[package]] -name = "shipote-card" +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shuma-card" version = "0.1.0" dependencies = [ "brahman-card", @@ -10227,21 +10494,21 @@ dependencies = [ ] [[package]] -name = "shipote-cli" +name = "shuma-cli" version = "0.1.0" dependencies = [ "anyhow", "brahman-card", "clap", "serde_json", - "shipote-card", - "shipote-protocol", + "shuma-card", + "shuma-protocol", "tokio", "ulid", ] [[package]] -name = "shipote-core" +name = "shuma-core" version = "0.1.0" dependencies = [ "anyhow", @@ -10251,8 +10518,8 @@ dependencies = [ "nix 0.29.0", "serde", "serde_json", - "shipote-card", - "shipote-discern", + "shuma-card", + "shuma-discern", "tempfile", "thiserror 2.0.18", "tokio", @@ -10261,7 +10528,7 @@ dependencies = [ ] [[package]] -name = "shipote-daemon" +name = "shuma-daemon" version = "0.1.0" dependencies = [ "anyhow", @@ -10270,10 +10537,10 @@ dependencies = [ "ente-incarnate", "libc", "nix 0.29.0", - "shipote-card", - "shipote-core", - "shipote-discern", - "shipote-protocol", + "shuma-card", + "shuma-core", + "shuma-discern", + "shuma-protocol", "tokio", "tracing", "tracing-subscriber", @@ -10281,7 +10548,7 @@ dependencies = [ ] [[package]] -name = "shipote-discern" +name = "shuma-discern" version = "0.1.0" dependencies = [ "brahman-card", @@ -10291,53 +10558,47 @@ dependencies = [ ] [[package]] -name = "shipote-gateway" +name = "shuma-gateway" version = "0.1.0" dependencies = [ "anyhow", "serde_json", - "shipote-protocol", + "shuma-protocol", "tokio", "tracing", "tracing-subscriber", ] [[package]] -name = "shipote-protocol" +name = "shuma-protocol" version = "0.1.0" dependencies = [ "brahman-card", "nix 0.29.0", "postcard", "serde", - "shipote-card", + "shuma-card", "thiserror 2.0.18", "tokio", "ulid", ] [[package]] -name = "shipote-shell" +name = "shuma-shell" version = "0.1.0" dependencies = [ "gpui", - "shipote-card", - "shipote-protocol", + "nahual-launcher", + "nahual-theme", + "nahual-widget-app-header", + "nahual-widget-banner", + "nahual-widget-stat-card", + "shuma-card", + "shuma-protocol", "tokio", "ulid", - "yahweh-launcher", - "yahweh-theme", - "yahweh-widget-app-header", - "yahweh-widget-banner", - "yahweh-widget-stat-card", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -12269,11 +12530,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vista-core" +version = "0.1.0" + [[package]] name = "vista-web" version = "0.1.0" dependencies = [ "js-sys", + "vista-core", "wasm-bindgen", "web-sys", ] @@ -13698,262 +13964,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" -[[package]] -name = "yahweh-bus" -version = "0.1.0" -dependencies = [ - "gpui", -] - -[[package]] -name = "yahweh-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "serde", - "serde_json", - "tokio", -] - -[[package]] -name = "yahweh-database-explorer" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", - "yahweh-provider-sqlite", - "yahweh-theme", - "yahweh-widget-tree", -] - -[[package]] -name = "yahweh-file-explorer" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", - "yahweh-provider-fs", - "yahweh-theme", - "yahweh-widget-text-input", - "yahweh-widget-tree", -] - -[[package]] -name = "yahweh-image-viewer" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-bus", - "yahweh-theme", -] - -[[package]] -name = "yahweh-launcher" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - -[[package]] -name = "yahweh-meta-runtime" -version = "0.1.0" -dependencies = [ - "serde_json", - "thiserror 2.0.18", - "uuid", - "yahweh-meta-schema", -] - -[[package]] -name = "yahweh-meta-schema" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", -] - -[[package]] -name = "yahweh-provider-fs" -version = "0.1.0" -dependencies = [ - "async-trait", - "notify", - "shipote-discern", - "tokio", - "yahweh-core", -] - -[[package]] -name = "yahweh-provider-sqlite" -version = "0.1.0" -dependencies = [ - "async-trait", - "rusqlite", - "tokio", - "yahweh-core", -] - -[[package]] -name = "yahweh-shell" -version = "0.1.0" -dependencies = [ - "brahman-card", - "brahman-sidecar", - "gpui", - "notify", - "serde", - "serde_json", - "tokio", - "ulid", - "yahweh-bus", - "yahweh-core", - "yahweh-database-explorer", - "yahweh-file-explorer", - "yahweh-image-viewer", - "yahweh-provider-fs", - "yahweh-provider-sqlite", - "yahweh-text-viewer", - "yahweh-theme", - "yahweh-widget-container-core", - "yahweh-widget-splitter", - "yahweh-widget-tabs", - "yahweh-widget-tiled", - "yahweh-widget-tree", -] - -[[package]] -name = "yahweh-text-viewer" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-bus", - "yahweh-core", - "yahweh-provider-fs", - "yahweh-provider-sqlite", - "yahweh-theme", -] - -[[package]] -name = "yahweh-theme" -version = "0.1.0" -dependencies = [ - "gpui", -] - -[[package]] -name = "yahweh-widget-app-header" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", - "yahweh-widget-theme-switcher", -] - -[[package]] -name = "yahweh-widget-banner" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - -[[package]] -name = "yahweh-widget-card" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - -[[package]] -name = "yahweh-widget-container-core" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", -] - -[[package]] -name = "yahweh-widget-meta-form" -version = "0.1.0" -dependencies = [ - "gpui", - "serde_json", - "uuid", - "yahweh-meta-runtime", - "yahweh-meta-schema", - "yahweh-theme", - "yahweh-widget-banner", - "yahweh-widget-text-input", - "yahweh-widget-theme-switcher", -] - -[[package]] -name = "yahweh-widget-splitter" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", - "yahweh-theme", - "yahweh-widget-container-core", -] - -[[package]] -name = "yahweh-widget-stat-card" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", - "yahweh-widget-card", -] - -[[package]] -name = "yahweh-widget-tabs" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", - "yahweh-theme", - "yahweh-widget-container-core", -] - -[[package]] -name = "yahweh-widget-text-input" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - -[[package]] -name = "yahweh-widget-theme-switcher" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - -[[package]] -name = "yahweh-widget-tiled" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-core", - "yahweh-theme", - "yahweh-widget-container-core", -] - -[[package]] -name = "yahweh-widget-tree" -version = "0.1.0" -dependencies = [ - "gpui", - "yahweh-theme", -] - [[package]] name = "yamux" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 84d5f0a..73ffac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,44 +2,56 @@ resolver = "2" members = [ # ============================================================ - # core/ — Init y compat (arje absorbido) + # protocol/ — Contratos canónicos + routing entre módulos # ============================================================ - "crates/core/brahman-card", - "crates/core/brahman-card-wit", - "crates/core/brahman-cards", - "crates/core/brahman-handshake", - "crates/core/brahman-broker", - "crates/core/brahman-admin", - "crates/shared/brahman-sidecar", - "crates/shared/brahman-net", - "crates/shared/ente-incarnate", - "crates/core/ente-card", - "crates/core/ente-bus", - "crates/core/ente-cas", - "crates/core/ente-kernel", - "crates/core/ente-soma", - "crates/core/ente-wasm", - "crates/core/ente-snapshot", - "crates/core/ente-brain", - "crates/core/ente-zero", - "crates/core/ente-echo", - "crates/core/ente-policy-provider", - "crates/core/ente-logind-compat", - "crates/core/ente-hostnamed-compat", - "crates/core/ente-timedated-compat", - "crates/core/ente-localed-compat", - "crates/core/ente-journald-compat", - "crates/core/ente-resolved-compat", - "crates/core/ente-polkit-compat", - "crates/core/ente-machined-compat", - "crates/core/ente-tmpfiles-compat", - "crates/core/ente-systemd1-compat", - "crates/core/ente-notify-compat", - "crates/core/ente-binfmt-compat", - "crates/core/ente-timer-compat", + "crates/protocol/brahman-card", + "crates/protocol/brahman-card-wit", + "crates/protocol/brahman-cards", + "crates/protocol/brahman-handshake", + "crates/protocol/brahman-broker", + "crates/protocol/brahman-admin", + "crates/protocol/brahman-sidecar", + "crates/protocol/brahman-net", + "crates/protocol/ente-card", # ============================================================ - # modules/semantic_dht/ — DHT semántico (minga absorbido) + # init/ — PID 1 + encarnación Linux (arje) + # ============================================================ + "crates/init/ente-zero", + "crates/init/ente-kernel", + "crates/init/ente-soma", + "crates/init/ente-snapshot", + "crates/init/ente-incarnate", + + # ============================================================ + # runtime/ — Infraestructura de ejecución (bus + cas + wasm + brain) + # ============================================================ + "crates/runtime/ente-bus", + "crates/runtime/ente-cas", + "crates/runtime/ente-wasm", + "crates/runtime/ente-brain", + "crates/runtime/ente-echo", + + # ============================================================ + # compat/ — Shims D-Bus para correr software systemd-aware + # ============================================================ + "crates/compat/ente-policy-provider", + "crates/compat/ente-logind-compat", + "crates/compat/ente-hostnamed-compat", + "crates/compat/ente-timedated-compat", + "crates/compat/ente-localed-compat", + "crates/compat/ente-journald-compat", + "crates/compat/ente-resolved-compat", + "crates/compat/ente-polkit-compat", + "crates/compat/ente-machined-compat", + "crates/compat/ente-tmpfiles-compat", + "crates/compat/ente-systemd1-compat", + "crates/compat/ente-notify-compat", + "crates/compat/ente-binfmt-compat", + "crates/compat/ente-timer-compat", + + # ============================================================ + # modules/semantic_dht/ (minga) — DHT semántico de código # ============================================================ "crates/modules/semantic_dht/minga-core", "crates/modules/semantic_dht/minga-store", @@ -48,68 +60,70 @@ members = [ "crates/modules/semantic_dht/minga-cli", # ============================================================ - # modules/ui_engine/ — Motor de widgets (yahweh libs+widgets) + # modules/nahual/ — Motor GPUI: libs + widgets (era yahweh) # ============================================================ - "crates/modules/ui_engine/libs/core", - "crates/modules/ui_engine/libs/theme", - "crates/modules/ui_engine/libs/launcher", - "crates/modules/ui_engine/libs/bus", - "crates/modules/ui_engine/libs/meta-schema", - "crates/modules/ui_engine/libs/meta-runtime", - "crates/modules/ui_engine/libs/providers/fs", - "crates/modules/ui_engine/libs/providers/sqlite", - "crates/modules/ui_engine/widgets/tree", - "crates/modules/ui_engine/widgets/container_core", - "crates/modules/ui_engine/widgets/splitter", - "crates/modules/ui_engine/widgets/tabs", - "crates/modules/ui_engine/widgets/tiled", - "crates/modules/ui_engine/widgets/text_input", - "crates/modules/ui_engine/widgets/meta-form", - "crates/modules/ui_engine/widgets/banner", - "crates/modules/ui_engine/widgets/card", - "crates/modules/ui_engine/widgets/stat-card", - "crates/modules/ui_engine/widgets/app-header", - "crates/modules/ui_engine/widgets/theme-switcher", - - # --- lapaloma: módulo de gráficos data-viz (ver ARCHITECTURE.md fuente) --- - "crates/modules/ui_engine/libs/lapaloma-core", - "crates/modules/ui_engine/widgets/lapaloma-render", - "crates/modules/ui_engine/widgets/lapaloma-cartesian", - "crates/modules/ui_engine/widgets/lapaloma-stream", - "crates/modules/ui_engine/widgets/lapaloma-mesh", - "crates/modules/ui_engine/widgets/lapaloma-financial", - "crates/modules/ui_engine/widgets/lapaloma-polar", - "crates/modules/ui_engine/widgets/lapaloma-heatmap", - "crates/modules/ui_engine/widgets/lapaloma-treemap", - "crates/modules/ui_engine/widgets/lapaloma-flow", - "crates/modules/ui_engine/widgets/lapaloma-phosphor", - "crates/modules/ui_engine/widgets/lapaloma-export", - "crates/modules/ui_engine/widgets/lapaloma", + "crates/modules/nahual/libs/core", + "crates/modules/nahual/libs/theme", + "crates/modules/nahual/libs/launcher", + "crates/modules/nahual/libs/bus", + "crates/modules/nahual/libs/meta-schema", + "crates/modules/nahual/libs/meta-runtime", + "crates/modules/nahual/libs/providers/fs", + "crates/modules/nahual/libs/providers/sqlite", + "crates/modules/nahual/widgets/tree", + "crates/modules/nahual/widgets/container_core", + "crates/modules/nahual/widgets/splitter", + "crates/modules/nahual/widgets/tabs", + "crates/modules/nahual/widgets/tiled", + "crates/modules/nahual/widgets/text_input", + "crates/modules/nahual/widgets/meta-form", + "crates/modules/nahual/widgets/banner", + "crates/modules/nahual/widgets/card", + "crates/modules/nahual/widgets/stat-card", + "crates/modules/nahual/widgets/app-header", + "crates/modules/nahual/widgets/theme-switcher", # ============================================================ - # modules/nakui/ — ERP matemático (nakui absorbido) + # modules/pineal/ — Data-viz agnóstica con backends (era lapaloma) + # ============================================================ + "crates/modules/pineal/core", + "crates/modules/pineal/render", + "crates/modules/pineal/cartesian", + "crates/modules/pineal/stream", + "crates/modules/pineal/mesh", + "crates/modules/pineal/financial", + "crates/modules/pineal/polar", + "crates/modules/pineal/heatmap", + "crates/modules/pineal/treemap", + "crates/modules/pineal/flow", + "crates/modules/pineal/phosphor", + "crates/modules/pineal/export", + "crates/modules/pineal/umbrella", + + # ============================================================ + # modules/nakui/ — ERP matemático (categórico) # ============================================================ "crates/modules/nakui/core", # ============================================================ - # modules/nouser/ — explorador de Mónadas (nuevo) + # modules/akasha/ — Explorador semántico de Mónadas (era nouser) # ============================================================ - "crates/modules/nouser/card", - "crates/modules/nouser/core", - "crates/modules/nouser/nous", - "crates/modules/nouser/nous-mock", - "crates/modules/nouser/nous-real", + "crates/modules/akasha/card", + "crates/modules/akasha/core", + "crates/modules/akasha/nous", + "crates/modules/akasha/nous-mock", + "crates/modules/akasha/nous-real", # ============================================================ - # modules/shipote/ — runtime de espacios aislados con flujo tipado + # modules/shuma/ — Runtime de espacios aislados (era shipote) # ============================================================ - "crates/modules/shipote/shipote-card", - "crates/modules/shipote/shipote-protocol", - "crates/modules/shipote/shipote-discern", - "crates/modules/shipote/shipote-core", + "crates/modules/shuma/shuma-card", + "crates/modules/shuma/shuma-protocol", + "crates/modules/shuma/shuma-discern", + "crates/modules/shuma/shuma-core", # ============================================================ - # modules/gioser/ — landing WASM (chacana + 4 elementos) + # modules/gioser/ — Landing WASM (chacana + 4 elementos) # ============================================================ "crates/modules/gioser/gioser-geom", "crates/modules/gioser/gioser-physics", @@ -118,23 +132,25 @@ members = [ "crates/modules/gioser/gioser-canvas-web", # ============================================================ - # modules/pluma/ — markdown agnóstico + visor web elegante + # modules/pluma/ — Markdown agnóstico + visor web # ============================================================ "crates/modules/pluma/pluma-md", "crates/modules/pluma/pluma-reader-web", # ============================================================ - # modules/vista/ — deck horizontal swipe estilo Flutter PageView + # modules/vista/ — Deck horizontal swipe (Flutter PageView) # ============================================================ + "crates/modules/vista/vista-core", "crates/modules/vista/vista-web", # ============================================================ - # modules/barra/ — taskbar agnóstica estilo Windows + # modules/barra/ — Taskbar agnóstica estilo Windows # ============================================================ + "crates/modules/barra/barra-core", "crates/modules/barra/barra-web", # ============================================================ - # modules/cosmobiologia/ — estudio de astrología profesional + # modules/cosmobiologia/ — Estudio de astrología profesional # ============================================================ "crates/modules/cosmobiologia/cosmobiologia-card", "crates/modules/cosmobiologia/cosmobiologia-model", @@ -149,28 +165,28 @@ members = [ "crates/modules/cosmobiologia/cosmobiologia-web", # ============================================================ - # apps/ — apps que consumen el protocolo (yahweh modules+shell) + # apps/ — Binarios finales que consumen el protocolo # ============================================================ - "crates/apps/file_explorer", - "crates/apps/database_explorer", - "crates/apps/text_viewer", - "crates/apps/image_viewer", - "crates/apps/yahweh-shell", - "crates/apps/nouser-explorer", + "crates/apps/brahman-broker-explorer", + "crates/apps/brahman-demo", + "crates/apps/nahual-shell", + "crates/apps/nahual-file-explorer", + "crates/apps/nahual-database-explorer", + "crates/apps/nahual-text-viewer", + "crates/apps/nahual-image-viewer", + "crates/apps/akasha-explorer", "crates/apps/nakui-explorer", "crates/apps/nakui-ui", "crates/apps/minga-explorer", - "crates/apps/brahman-broker-explorer", - "crates/apps/brahman-demo", - "crates/apps/shipote-daemon", - "crates/apps/shipote-cli", - "crates/apps/shipote-gateway", - "crates/apps/shipote-shell", + "crates/apps/shuma-daemon", + "crates/apps/shuma-cli", + "crates/apps/shuma-gateway", + "crates/apps/shuma-shell", "crates/apps/gioser-web", - "crates/apps/lapaloma-demo", - "crates/apps/lapaloma-stream-demo", - "crates/apps/lapaloma-phosphor-demo", - "crates/apps/lapaloma-financial-demo", + "crates/apps/pineal-demo", + "crates/apps/pineal-stream-demo", + "crates/apps/pineal-phosphor-demo", + "crates/apps/pineal-financial-demo", "crates/apps/cosmobiologia", "crates/apps/cosmobiologia-cli", "crates/apps/cosmobiologia-server", @@ -257,7 +273,7 @@ zbus = { version = "4", default-features = false, features = ["tokio"] } # === Tests === tempfile = "3" -# === GPUI (yahweh) === +# === GPUI (nahual) === gpui = "0.2" # === Filesystem helpers === @@ -274,40 +290,40 @@ glam = "0.30" pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] } # ============================================================ -# Intra-workspace deps de yahweh (referenciadas por workspace = true) +# Intra-workspace deps de nahual (referenciadas por workspace = true) # ============================================================ -yahweh-core = { path = "crates/modules/ui_engine/libs/core" } -yahweh-theme = { path = "crates/modules/ui_engine/libs/theme" } -yahweh-bus = { path = "crates/modules/ui_engine/libs/bus" } -yahweh-provider-fs = { path = "crates/modules/ui_engine/libs/providers/fs" } -yahweh-provider-sqlite = { path = "crates/modules/ui_engine/libs/providers/sqlite" } -yahweh-widget-tree = { path = "crates/modules/ui_engine/widgets/tree" } -yahweh-widget-container-core = { path = "crates/modules/ui_engine/widgets/container_core" } -yahweh-widget-splitter = { path = "crates/modules/ui_engine/widgets/splitter" } -yahweh-widget-tabs = { path = "crates/modules/ui_engine/widgets/tabs" } -yahweh-widget-tiled = { path = "crates/modules/ui_engine/widgets/tiled" } -yahweh-widget-text-input = { path = "crates/modules/ui_engine/widgets/text_input" } -yahweh-file-explorer = { path = "crates/apps/file_explorer" } -yahweh-database-explorer = { path = "crates/apps/database_explorer" } -yahweh-text-viewer = { path = "crates/apps/text_viewer" } -yahweh-image-viewer = { path = "crates/apps/image_viewer" } +nahual-core = { path = "crates/modules/nahual/libs/core" } +nahual-theme = { path = "crates/modules/nahual/libs/theme" } +nahual-bus = { path = "crates/modules/nahual/libs/bus" } +nahual-provider-fs = { path = "crates/modules/nahual/libs/providers/fs" } +nahual-provider-sqlite = { path = "crates/modules/nahual/libs/providers/sqlite" } +nahual-widget-tree = { path = "crates/modules/nahual/widgets/tree" } +nahual-widget-container-core = { path = "crates/modules/nahual/widgets/container_core" } +nahual-widget-splitter = { path = "crates/modules/nahual/widgets/splitter" } +nahual-widget-tabs = { path = "crates/modules/nahual/widgets/tabs" } +nahual-widget-tiled = { path = "crates/modules/nahual/widgets/tiled" } +nahual-widget-text-input = { path = "crates/modules/nahual/widgets/text_input" } +nahual-file-explorer = { path = "crates/apps/nahual-file-explorer" } +nahual-database-explorer = { path = "crates/apps/nahual-database-explorer" } +nahual-text-viewer = { path = "crates/apps/nahual-text-viewer" } +nahual-image-viewer = { path = "crates/apps/nahual-image-viewer" } # ============================================================ -# Intra-workspace deps de lapaloma (módulo de gráficos) +# Intra-workspace deps de pineal (módulo de gráficos) # ============================================================ -lapaloma-core = { path = "crates/modules/ui_engine/libs/lapaloma-core" } -lapaloma-render = { path = "crates/modules/ui_engine/widgets/lapaloma-render" } -lapaloma-cartesian = { path = "crates/modules/ui_engine/widgets/lapaloma-cartesian" } -lapaloma-stream = { path = "crates/modules/ui_engine/widgets/lapaloma-stream" } -lapaloma-mesh = { path = "crates/modules/ui_engine/widgets/lapaloma-mesh" } -lapaloma-financial = { path = "crates/modules/ui_engine/widgets/lapaloma-financial" } -lapaloma-polar = { path = "crates/modules/ui_engine/widgets/lapaloma-polar" } -lapaloma-heatmap = { path = "crates/modules/ui_engine/widgets/lapaloma-heatmap" } -lapaloma-treemap = { path = "crates/modules/ui_engine/widgets/lapaloma-treemap" } -lapaloma-flow = { path = "crates/modules/ui_engine/widgets/lapaloma-flow" } -lapaloma-phosphor = { path = "crates/modules/ui_engine/widgets/lapaloma-phosphor" } -lapaloma-export = { path = "crates/modules/ui_engine/widgets/lapaloma-export" } -lapaloma = { path = "crates/modules/ui_engine/widgets/lapaloma" } +pineal-core = { path = "crates/modules/pineal/core" } +pineal-render = { path = "crates/modules/pineal/render" } +pineal-cartesian = { path = "crates/modules/pineal/cartesian" } +pineal-stream = { path = "crates/modules/pineal/stream" } +pineal-mesh = { path = "crates/modules/pineal/mesh" } +pineal-financial = { path = "crates/modules/pineal/financial" } +pineal-polar = { path = "crates/modules/pineal/polar" } +pineal-heatmap = { path = "crates/modules/pineal/heatmap" } +pineal-treemap = { path = "crates/modules/pineal/treemap" } +pineal-flow = { path = "crates/modules/pineal/flow" } +pineal-phosphor = { path = "crates/modules/pineal/phosphor" } +pineal-export = { path = "crates/modules/pineal/export" } +pineal = { path = "crates/modules/pineal/umbrella" } [profile.release] lto = "thin" diff --git a/crates/apps/SDD.md b/crates/apps/SDD.md new file mode 100644 index 0000000..e4aa605 --- /dev/null +++ b/crates/apps/SDD.md @@ -0,0 +1,52 @@ +# apps/ — Binarios finales + +**Propósito.** Aplicaciones ejecutables que consumen el protocolo +brahman y los módulos. Cada app es un `[[bin]]` o `cdylib` standalone. + +## Mapa + +### Protocol / Init +- `brahman-broker-explorer` — probe GPUI del broker (matches + sessions) +- `brahman-demo` — bootstrap reproducible: broker + producer + consumer + +### Nahual (GPUI suite) +- `nahual-shell` — shell standard de explorers (sidebar+main+status) +- `nahual-file-explorer`, `nahual-database-explorer`, + `nahual-text-viewer`, `nahual-image-viewer` + +### Akasha +- `akasha-explorer` — descubre el daemon `akasha-core` y lista Mónadas + +### Nakui (ERP) +- `nakui-ui` — MetaUi+MetaForm con event log + replay +- `nakui-explorer` — dashboard sobre stack nahual + +### Minga +- `minga-explorer` — dashboard de DHT semántico + indexer status + +### Shuma (sandboxes) +- `shuma-daemon` — dueño de Workspaces (postcard sobre Unix socket) +- `shuma-cli` — CLI admin (`shipote` binario por compat) +- `shuma-gateway` — HTTP/JSON ↔ postcard +- `shuma-shell` — GUI GPUI de Workspaces + +### Pineal (demos data-viz) +- `pineal-demo`, `pineal-stream-demo`, `pineal-phosphor-demo`, + `pineal-financial-demo` + +### Web targets (cdylib WASM) +- `gioser-web` — landing chacana +- `cosmobiologia-web` (en modules/, no apps/) — bridge SVG + +### Cosmobiología +- `cosmobiologia` — app GPUI principal (tree + canvas + panel) +- `cosmobiologia-cli` — calcula cartas headless +- `cosmobiologia-server` — server HTTP single-user con CRUD + +## Convenciones + +- Cada app declara su `Card` y se anuncia al Init vía `brahman-sidecar`. +- Apps que viven dentro de GPUI consumen `nahual-shell` para el chrome. +- Apps headless usan `clap` para argv. +- Tests E2E: usar `gpui::TestAppContext` para GPUI; CLI tests via + `tempfile` + `assert_cmd`. diff --git a/crates/apps/akasha-explorer/Cargo.toml b/crates/apps/akasha-explorer/Cargo.toml new file mode 100644 index 0000000..3fe5ce1 --- /dev/null +++ b/crates/apps/akasha-explorer/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "akasha-explorer" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Explorador GPUI de Mónadas: panel que descubre al daemon nouser vía broker brahman y consulta sus Mónadas dinámicamente." + +[dependencies] +brahman-card = { path = "../../protocol/brahman-card" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } +akasha-card = { path = "../../modules/akasha/card" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-widget-banner = { path = "../../modules/nahual/widgets/banner" } +nahual-widget-card = { path = "../../modules/nahual/widgets/card" } +nahual-widget-app-header = { path = "../../modules/nahual/widgets/app-header" } +gpui = { workspace = true } + +[[bin]] +name = "akasha-explorer" +path = "src/main.rs" diff --git a/crates/apps/nouser-explorer/src/main.rs b/crates/apps/akasha-explorer/src/main.rs similarity index 94% rename from crates/apps/nouser-explorer/src/main.rs rename to crates/apps/akasha-explorer/src/main.rs index 2205556..e9db07c 100644 --- a/crates/apps/nouser-explorer/src/main.rs +++ b/crates/apps/akasha-explorer/src/main.rs @@ -1,21 +1,21 @@ -//! `nouser-explorer` — panel GPUI que descubre al daemon `nouser` +//! `akasha-explorer` — panel GPUI que descubre al daemon `akasha` //! vía broker brahman y muestra sus Mónadas en vivo. //! //! Diseño: ventana standalone que cada N segundos consulta el query -//! socket del daemon (`nouser_core::engine_socket::client::list_monads`). +//! socket del daemon (`akasha_core::engine_socket::client::list_monads`). //! El path del socket NO está hardcoded — se descubre vía //! `brahman_sidecar::await_provider_blocking` para el flow //! `monad-list:json`. Si el daemon cae, el socket cacheado se invalida //! y la próxima iteración re-descubre. //! -//! Sin integración con yahweh-shell — es su propio binario para que el +//! Sin integración con nahual-shell — es su propio binario para que el //! ecosistema sea visible incluso sin la shell completa. //! //! Uso: //! ```sh -//! cargo run -p nouser-explorer +//! cargo run -p akasha-explorer //! # con override del init socket (heredado de brahman-handshake): -//! BRAHMAN_INIT_SOCKET=/tmp/init.sock cargo run -p nouser-explorer +//! BRAHMAN_INIT_SOCKET=/tmp/init.sock cargo run -p akasha-explorer //! ``` use std::path::PathBuf; @@ -25,14 +25,14 @@ use brahman_sidecar::{await_provider_blocking, build_consumer_card, ConsumerErro use gpui::{ div, prelude::*, px, rgb, Context, IntoElement, Render, SharedString, Window, }; -use nouser_card::query::client as query_client; -use nouser_card::query::{transport, ListMonadsResponse, FLOW_MONAD_LIST, FLOW_TYPE_NAME}; -use nouser_card::Lens; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; -use yahweh_widget_app_header::app_header; -use yahweh_widget_banner::{banner_themed, Banner}; -use yahweh_widget_card::card_themed; +use akasha_card::query::client as query_client; +use akasha_card::query::{transport, ListMonadsResponse, FLOW_MONAD_LIST, FLOW_TYPE_NAME}; +use akasha_card::Lens; +use nahual_launcher::launch_app; +use nahual_theme::Theme; +use nahual_widget_app_header::app_header; +use nahual_widget_banner::{banner_themed, Banner}; +use nahual_widget_card::card_themed; const REFRESH_INTERVAL: Duration = Duration::from_secs(2); const DISCOVERY_TIMEOUT: Duration = Duration::from_secs(3); @@ -176,7 +176,7 @@ fn resolve_socket() -> Result<(PathBuf, &'static str), String> { /// Card con `flow.input = monad-list:json`, espera al primer /// `MatchEvent::Available`, devuelve el `producer_service_socket`. fn discover_via_broker() -> Result { - let card = build_consumer_card("nouser-explorer", FLOW_MONAD_LIST, FLOW_TYPE_NAME); + let card = build_consumer_card("akasha-explorer", FLOW_MONAD_LIST, FLOW_TYPE_NAME); await_provider_blocking(card, DISCOVERY_TIMEOUT) } @@ -184,7 +184,7 @@ impl Render for Explorer { fn render(&mut self, _w: &mut Window, cx: &mut Context) -> impl IntoElement { // Chrome viene del Theme global; los acentos por kind // (engine cyan, data purple) son señales semánticas del - // dominio nouser y se mantienen locales. + // dominio akasha y se mantienen locales. let theme = Theme::global(cx).clone(); let bg = theme.bg_app.clone(); let text = theme.fg_text; @@ -205,7 +205,7 @@ impl Render for Explorer { .map(|w| format!(" · watching: {}", w)) .unwrap_or_default() ), - _ => "Buscando daemon nouser vía brahman-broker…".to_string(), + _ => "Buscando daemon akasha vía brahman-broker…".to_string(), }; // Header standard via widget compartido. diff --git a/crates/apps/brahman-broker-explorer/Cargo.toml b/crates/apps/brahman-broker-explorer/Cargo.toml index 584ecaf..b1fae95 100644 --- a/crates/apps/brahman-broker-explorer/Cargo.toml +++ b/crates/apps/brahman-broker-explorer/Cargo.toml @@ -6,16 +6,16 @@ license.workspace = true description = "Probe GUI del broker brahman: conecta cada N segundos vía await_provider_blocking con un Card observer agnóstico, reporta 3 estados (down / up sin provider / up con provider)." [dependencies] -brahman-broker = { path = "../../core/brahman-broker" } -brahman-card = { path = "../../core/brahman-card" } -brahman-handshake = { path = "../../core/brahman-handshake" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } +brahman-broker = { path = "../../protocol/brahman-broker" } +brahman-card = { path = "../../protocol/brahman-card" } +brahman-handshake = { path = "../../protocol/brahman-handshake" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } ulid = { workspace = true } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" } -yahweh-widget-stat-card = { path = "../../modules/ui_engine/widgets/stat-card" } -yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-widget-banner = { path = "../../modules/nahual/widgets/banner" } +nahual-widget-stat-card = { path = "../../modules/nahual/widgets/stat-card" } +nahual-widget-app-header = { path = "../../modules/nahual/widgets/app-header" } gpui = { workspace = true } [[bin]] diff --git a/crates/apps/brahman-broker-explorer/src/main.rs b/crates/apps/brahman-broker-explorer/src/main.rs index 5e14333..405658a 100644 --- a/crates/apps/brahman-broker-explorer/src/main.rs +++ b/crates/apps/brahman-broker-explorer/src/main.rs @@ -38,11 +38,11 @@ use ulid::Ulid; use gpui::{ div, prelude::*, px, Context, IntoElement, Render, SharedString, Window, }; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; -use yahweh_widget_app_header::app_header; -use yahweh_widget_banner::{banner_themed, Banner}; -use yahweh_widget_stat_card::stat_card; +use nahual_launcher::launch_app; +use nahual_theme::Theme; +use nahual_widget_app_header::app_header; +use nahual_widget_banner::{banner_themed, Banner}; +use nahual_widget_stat_card::stat_card; const POLL_INTERVAL: Duration = Duration::from_secs(5); const PROBE_TIMEOUT: Duration = Duration::from_secs(1); diff --git a/crates/apps/brahman-demo/Cargo.toml b/crates/apps/brahman-demo/Cargo.toml index e3fc6bc..353b1be 100644 --- a/crates/apps/brahman-demo/Cargo.toml +++ b/crates/apps/brahman-demo/Cargo.toml @@ -6,10 +6,10 @@ license.workspace = true description = "Demo binaries de brahman: broker standalone + producer/consumer dummy. Pensados para que `scripts/bootstrap-demo.sh` arranque un escenario reproducible donde los 5 explorers ven sesiones, matches, y timeline." [dependencies] -brahman-broker = { path = "../../core/brahman-broker" } -brahman-card = { path = "../../core/brahman-card" } -brahman-handshake = { path = "../../core/brahman-handshake" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } +brahman-broker = { path = "../../protocol/brahman-broker" } +brahman-card = { path = "../../protocol/brahman-card" } +brahman-handshake = { path = "../../protocol/brahman-handshake" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/apps/cosmobiologia/Cargo.toml b/crates/apps/cosmobiologia/Cargo.toml index bb8afb7..b8f698e 100644 --- a/crates/apps/cosmobiologia/Cargo.toml +++ b/crates/apps/cosmobiologia/Cargo.toml @@ -15,13 +15,13 @@ cosmobiologia-panel = { path = "../../modules/cosmobiologia/cosmobiologia-panel" cosmobiologia-store = { path = "../../modules/cosmobiologia/cosmobiologia-store" } cosmobiologia-theme = { path = "../../modules/cosmobiologia/cosmobiologia-theme" } cosmobiologia-tree = { path = "../../modules/cosmobiologia/cosmobiologia-tree" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } -yahweh-core = { workspace = true } -yahweh-theme = { workspace = true } -yahweh-widget-theme-switcher = { path = "../../modules/ui_engine/widgets/theme-switcher" } -yahweh-widget-splitter = { workspace = true } -yahweh-widget-container-core = { workspace = true } +nahual-core = { workspace = true } +nahual-theme = { workspace = true } +nahual-widget-theme-switcher = { path = "../../modules/nahual/widgets/theme-switcher" } +nahual-widget-splitter = { workspace = true } +nahual-widget-container-core = { workspace = true } gpui = { workspace = true } directories = { workspace = true } serde_json = { workspace = true } diff --git a/crates/apps/cosmobiologia/src/main.rs b/crates/apps/cosmobiologia/src/main.rs index 62a27ae..11f5bf1 100644 --- a/crates/apps/cosmobiologia/src/main.rs +++ b/crates/apps/cosmobiologia/src/main.rs @@ -5,7 +5,7 @@ //! (fire-and-forget; si no hay Init, la app sigue standalone). //! 2. Abre la DB SQLite en `$XDG_DATA_HOME/cosmobiologia/charts.db` //! (fallback a `~/.local/share/cosmobiologia/charts.db`). -//! 3. Levanta GPUI con [`yahweh_theme::Theme::install_default`]. +//! 3. Levanta GPUI con [`nahual_theme::Theme::install_default`]. //! 4. Compone el shell: [`Shell`] dueño del tree (izq), canvas (centro) //! y panel (abajo). Cablea las suscripciones cross-widget. //! @@ -34,7 +34,7 @@ use gpui::{ }; use cosmobiologia_store::Store; -use yahweh_theme::Theme; +use nahual_theme::Theme; use crate::shell::Shell; diff --git a/crates/apps/cosmobiologia/src/shell.rs b/crates/apps/cosmobiologia/src/shell.rs index dcbaf97..96f31ec 100644 --- a/crates/apps/cosmobiologia/src/shell.rs +++ b/crates/apps/cosmobiologia/src/shell.rs @@ -44,11 +44,11 @@ use cosmobiologia_store::Store; use cosmobiologia_tree::{ parse_city_atlas_tsv, FreeChartEntry, TahuantinsuyuTree, TreeEvent, }; -use yahweh_core::{LayoutDirection, NodeId}; -use yahweh_theme::Theme; -use yahweh_widget_container_core::ChildSlot; -use yahweh_widget_splitter::{SplitContainer, SplitEvent}; -use yahweh_widget_theme_switcher::theme_switcher; +use nahual_core::{LayoutDirection, NodeId}; +use nahual_theme::Theme; +use nahual_widget_container_core::ChildSlot; +use nahual_widget_splitter::{SplitContainer, SplitEvent}; +use nahual_widget_theme_switcher::theme_switcher; /// Posición del panel de control dentro del shell. `Bottom` mantiene /// el layout histórico (tree+canvas arriba, panel abajo); las variantes @@ -1671,7 +1671,7 @@ impl Shell { /// Click llama a `apply_dock` que reorganiza splitters y persiste. fn render_dock_switcher( &self, - theme: &yahweh_theme::Theme, + theme: &nahual_theme::Theme, cx: &mut Context, ) -> impl IntoElement { let mut row = div() diff --git a/crates/apps/lapaloma-demo/Cargo.toml b/crates/apps/lapaloma-demo/Cargo.toml deleted file mode 100644 index abf040e..0000000 --- a/crates/apps/lapaloma-demo/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "lapaloma-demo" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -authors = { workspace = true } -publish = { workspace = true } -description = "Lapaloma — demo app: una serie sin(x) sobre ChartViewport rendereada con LapalomaChartElement. Valida la cadena core → render → cartesian → gpui en vivo." - -[dependencies] -gpui = { workspace = true } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -lapaloma-core = { path = "../../modules/ui_engine/libs/lapaloma-core" } -lapaloma-render = { path = "../../modules/ui_engine/widgets/lapaloma-render", features = ["gpui"] } -lapaloma-cartesian = { path = "../../modules/ui_engine/widgets/lapaloma-cartesian" } diff --git a/crates/apps/lapaloma-financial-demo/Cargo.toml b/crates/apps/lapaloma-financial-demo/Cargo.toml deleted file mode 100644 index b9247da..0000000 --- a/crates/apps/lapaloma-financial-demo/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "lapaloma-financial-demo" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -authors = { workspace = true } -publish = { workspace = true } -description = "Lapaloma — demo de candlesticks OHLC. Random walk sintético de 120 días con pan + zoom." - -[dependencies] -gpui = { workspace = true } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -lapaloma-render = { path = "../../modules/ui_engine/widgets/lapaloma-render", features = ["gpui"] } -lapaloma-cartesian = { path = "../../modules/ui_engine/widgets/lapaloma-cartesian" } -lapaloma-financial = { path = "../../modules/ui_engine/widgets/lapaloma-financial" } diff --git a/crates/apps/lapaloma-phosphor-demo/Cargo.toml b/crates/apps/lapaloma-phosphor-demo/Cargo.toml deleted file mode 100644 index 65c296a..0000000 --- a/crates/apps/lapaloma-phosphor-demo/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "lapaloma-phosphor-demo" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -authors = { workspace = true } -publish = { workspace = true } -description = "Lapaloma — demo del trail CRT (phosphor) sobre un RingBuffer streaming a 60Hz. Compará con lapaloma-stream-demo para ver el contraste." - -[dependencies] -gpui = { workspace = true } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -lapaloma-core = { path = "../../modules/ui_engine/libs/lapaloma-core" } -lapaloma-render = { path = "../../modules/ui_engine/widgets/lapaloma-render", features = ["gpui"] } -lapaloma-phosphor = { path = "../../modules/ui_engine/widgets/lapaloma-phosphor" } diff --git a/crates/apps/lapaloma-stream-demo/Cargo.toml b/crates/apps/lapaloma-stream-demo/Cargo.toml deleted file mode 100644 index 4c52714..0000000 --- a/crates/apps/lapaloma-stream-demo/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "lapaloma-stream-demo" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -authors = { workspace = true } -publish = { workspace = true } -description = "Lapaloma — demo de streaming: RingBuffer + timer 60 Hz + sweep render. Showcase del zero-alloc en hot path." - -[dependencies] -gpui = { workspace = true } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -lapaloma-core = { path = "../../modules/ui_engine/libs/lapaloma-core" } -lapaloma-render = { path = "../../modules/ui_engine/widgets/lapaloma-render", features = ["gpui"] } -lapaloma-stream = { path = "../../modules/ui_engine/widgets/lapaloma-stream" } diff --git a/crates/apps/minga-explorer/Cargo.toml b/crates/apps/minga-explorer/Cargo.toml index f4d55d2..8b1cde3 100644 --- a/crates/apps/minga-explorer/Cargo.toml +++ b/crates/apps/minga-explorer/Cargo.toml @@ -7,11 +7,11 @@ description = "Dashboard GPUI del repo Minga: counts de nodos AST, atestaciones, [dependencies] minga-store = { path = "../../modules/semantic_dht/minga-store" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" } -yahweh-widget-stat-card = { path = "../../modules/ui_engine/widgets/stat-card" } -yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-widget-banner = { path = "../../modules/nahual/widgets/banner" } +nahual-widget-stat-card = { path = "../../modules/nahual/widgets/stat-card" } +nahual-widget-app-header = { path = "../../modules/nahual/widgets/app-header" } gpui = { workspace = true } [[bin]] diff --git a/crates/apps/minga-explorer/src/main.rs b/crates/apps/minga-explorer/src/main.rs index 1200ed0..2af1a92 100644 --- a/crates/apps/minga-explorer/src/main.rs +++ b/crates/apps/minga-explorer/src/main.rs @@ -12,9 +12,9 @@ //! (`minga status`) cuando hace falta el DID. El explorer foco es //! observabilidad rápida. //! -//! Stack visual: yahweh-theme + banner_themed + card_themed + +//! Stack visual: nahual-theme + banner_themed + card_themed + //! theme_switcher. Mismo patrón que `nakui-explorer` / -//! `nouser-explorer`. +//! `akasha-explorer`. //! //! Uso: //! ```sh @@ -30,11 +30,11 @@ use gpui::{ div, prelude::*, px, Context, IntoElement, Render, SharedString, Window, }; use minga_store::PersistentRepo; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; -use yahweh_widget_app_header::app_header; -use yahweh_widget_banner::{banner_themed, Banner}; -use yahweh_widget_stat_card::stat_card; +use nahual_launcher::launch_app; +use nahual_theme::Theme; +use nahual_widget_app_header::app_header; +use nahual_widget_banner::{banner_themed, Banner}; +use nahual_widget_stat_card::stat_card; const REFRESH_INTERVAL: Duration = Duration::from_secs(2); const REPO_DIRNAME: &str = "repo"; @@ -288,7 +288,7 @@ impl Render for Explorer { } } -// `stat_card` se promovió a `yahweh-widget-stat-card` y se importa +// `stat_card` se promovió a `nahual-widget-stat-card` y se importa // arriba. La fn local fue eliminada en la iter 15 del refactor. #[cfg(test)] diff --git a/crates/apps/database_explorer/Cargo.toml b/crates/apps/nahual-database-explorer/Cargo.toml similarity index 55% rename from crates/apps/database_explorer/Cargo.toml rename to crates/apps/nahual-database-explorer/Cargo.toml index 08bd932..a63b6df 100644 --- a/crates/apps/database_explorer/Cargo.toml +++ b/crates/apps/nahual-database-explorer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "yahweh-database-explorer" +name = "nahual-database-explorer" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -7,7 +7,7 @@ description = "Explorer de SQLite — composición TreeView + SqliteProvider con [dependencies] gpui = { workspace = true } -yahweh-core = { workspace = true } -yahweh-theme = { workspace = true } -yahweh-widget-tree = { workspace = true } -yahweh-provider-sqlite = { workspace = true } +nahual-core = { workspace = true } +nahual-theme = { workspace = true } +nahual-widget-tree = { workspace = true } +nahual-provider-sqlite = { workspace = true } diff --git a/crates/apps/database_explorer/src/lib.rs b/crates/apps/nahual-database-explorer/src/lib.rs similarity index 96% rename from crates/apps/database_explorer/src/lib.rs rename to crates/apps/nahual-database-explorer/src/lib.rs index 49782da..43c5459 100644 --- a/crates/apps/database_explorer/src/lib.rs +++ b/crates/apps/nahual-database-explorer/src/lib.rs @@ -1,6 +1,6 @@ -//! `yahweh_database_explorer` — explorer de SQLite. +//! `nahual_database_explorer` — explorer de SQLite. //! -//! Mismo patrón que `yahweh_file_explorer` pero con `SqliteProvider`. La +//! Mismo patrón que `nahual_file_explorer` pero con `SqliteProvider`. La //! UX es idéntica (TreeView con lazy load por chevron); cambia solo el //! origen de los datos: filas de una tabla `items(id, parent_id, name, //! display_type, content)` en lugar del filesystem. @@ -13,10 +13,10 @@ use gpui::{ px, }; -use yahweh_core::{DataProvider, DisplayType, EntityNode}; -use yahweh_provider_sqlite::SqliteDataProvider; -use yahweh_theme::Theme; -use yahweh_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; +use nahual_core::{DataProvider, DisplayType, EntityNode}; +use nahual_provider_sqlite::SqliteDataProvider; +use nahual_theme::Theme; +use nahual_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; #[derive(Clone, Debug)] #[allow(dead_code)] // Consumido por el AppBus en Fase 4+. diff --git a/crates/apps/file_explorer/Cargo.toml b/crates/apps/nahual-file-explorer/Cargo.toml similarity index 50% rename from crates/apps/file_explorer/Cargo.toml rename to crates/apps/nahual-file-explorer/Cargo.toml index 0835f69..b6fb318 100644 --- a/crates/apps/file_explorer/Cargo.toml +++ b/crates/apps/nahual-file-explorer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "yahweh-file-explorer" +name = "nahual-file-explorer" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -7,8 +7,8 @@ description = "Explorer de filesystem — composición TreeView + FsProvider con [dependencies] gpui = { workspace = true } -yahweh-core = { workspace = true } -yahweh-theme = { workspace = true } -yahweh-widget-tree = { workspace = true } -yahweh-widget-text-input = { workspace = true } -yahweh-provider-fs = { workspace = true } +nahual-core = { workspace = true } +nahual-theme = { workspace = true } +nahual-widget-tree = { workspace = true } +nahual-widget-text-input = { workspace = true } +nahual-provider-fs = { workspace = true } diff --git a/crates/apps/file_explorer/src/lib.rs b/crates/apps/nahual-file-explorer/src/lib.rs similarity index 98% rename from crates/apps/file_explorer/src/lib.rs rename to crates/apps/nahual-file-explorer/src/lib.rs index d7eacb9..a893337 100644 --- a/crates/apps/file_explorer/src/lib.rs +++ b/crates/apps/nahual-file-explorer/src/lib.rs @@ -1,4 +1,4 @@ -//! `yahweh_file_explorer` — explorer de filesystem con menú contextual. +//! `nahual_file_explorer` — explorer de filesystem con menú contextual. //! //! Composición canónica del patrón "explorer = TreeView + provider": //! @@ -42,11 +42,11 @@ use gpui::{ PromptLevel, Render, SharedString, Window, div, prelude::*, px, }; -use yahweh_core::{DataProvider, DisplayType, EntityNode}; -use yahweh_provider_fs::FileDataProvider; -use yahweh_theme::Theme; -use yahweh_widget_text_input::{TextInput, TextInputEvent}; -use yahweh_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; +use nahual_core::{DataProvider, DisplayType, EntityNode}; +use nahual_provider_fs::FileDataProvider; +use nahual_theme::Theme; +use nahual_widget_text_input::{TextInput, TextInputEvent}; +use nahual_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; #[derive(Clone, Debug)] #[allow(dead_code)] diff --git a/crates/apps/image_viewer/Cargo.toml b/crates/apps/nahual-image-viewer/Cargo.toml similarity index 70% rename from crates/apps/image_viewer/Cargo.toml rename to crates/apps/nahual-image-viewer/Cargo.toml index 4d36de2..0d67080 100644 --- a/crates/apps/image_viewer/Cargo.toml +++ b/crates/apps/nahual-image-viewer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "yahweh-image-viewer" +name = "nahual-image-viewer" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -7,5 +7,5 @@ description = "Visor de imágenes. Suscribe al AppBus y renderea con gpui::img(p [dependencies] gpui = { workspace = true } -yahweh-bus = { workspace = true } -yahweh-theme = { workspace = true } +nahual-bus = { workspace = true } +nahual-theme = { workspace = true } diff --git a/crates/apps/image_viewer/src/lib.rs b/crates/apps/nahual-image-viewer/src/lib.rs similarity index 97% rename from crates/apps/image_viewer/src/lib.rs rename to crates/apps/nahual-image-viewer/src/lib.rs index c4eb516..7d2c09e 100644 --- a/crates/apps/image_viewer/src/lib.rs +++ b/crates/apps/nahual-image-viewer/src/lib.rs @@ -1,4 +1,4 @@ -//! `yahweh_image_viewer` — visor de imágenes. +//! `nahual_image_viewer` — visor de imágenes. //! //! Suscribe al `AppBus` y, en cada `EntitySelected` cuyo provider sea //! `local_fs` y la extensión sugiera imagen (jpg, png, webp, gif), pasa el @@ -17,8 +17,8 @@ use gpui::{ Context, Entity, IntoElement, Render, SharedString, Window, div, img, prelude::*, px, }; -use yahweh_bus::{AppBus, AppEvent}; -use yahweh_theme::Theme; +use nahual_bus::{AppBus, AppEvent}; +use nahual_theme::Theme; const FS_PROVIDER: &str = "local_fs"; diff --git a/crates/apps/nahual-shell/Cargo.toml b/crates/apps/nahual-shell/Cargo.toml new file mode 100644 index 0000000..7ffe65b --- /dev/null +++ b/crates/apps/nahual-shell/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "nahual-shell" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +description = "Bootstrap GPUI + LayoutHost de Yahweh." + +[dependencies] +nahual-core = { workspace = true } +nahual-theme = { workspace = true } +nahual-provider-fs = { workspace = true } +nahual-provider-sqlite = { workspace = true } +nahual-widget-tree = { workspace = true } +nahual-widget-container-core = { workspace = true } +nahual-widget-splitter = { workspace = true } +nahual-widget-tabs = { workspace = true } +nahual-widget-tiled = { workspace = true } +nahual-bus = { workspace = true } +nahual-file-explorer = { workspace = true } +nahual-database-explorer = { workspace = true } +nahual-text-viewer = { workspace = true } +nahual-image-viewer = { workspace = true } +gpui = { workspace = true } +tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +notify = { workspace = true } + +# Brahman protocol — sidecar thread que se presenta al Init. +brahman-card = { path = "../../protocol/brahman-card" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } +ulid = { workspace = true } + +[[bin]] +name = "nahual" +path = "src/main.rs" diff --git a/crates/apps/yahweh-shell/src/brahman_client.rs b/crates/apps/nahual-shell/src/brahman_client.rs similarity index 89% rename from crates/apps/yahweh-shell/src/brahman_client.rs rename to crates/apps/nahual-shell/src/brahman_client.rs index 525ec2d..e360594 100644 --- a/crates/apps/yahweh-shell/src/brahman_client.rs +++ b/crates/apps/nahual-shell/src/brahman_client.rs @@ -1,7 +1,7 @@ -//! Card de yahweh-shell + spawn del sidecar brahman compartido. +//! Card de nahual-shell + spawn del sidecar brahman compartido. //! //! La lógica de thread + tokio + ping-loop vive en `brahman-sidecar`; -//! aquí sólo declaramos la identidad de yahweh como módulo Widget. +//! aquí sólo declaramos la identidad de nahual como módulo Widget. use std::collections::BTreeSet; @@ -11,7 +11,7 @@ use brahman_card::{ }; use ulid::Ulid; -/// Spawn del sidecar con la Card de yahweh. +/// Spawn del sidecar con la Card de nahual. pub fn spawn() { brahman_sidecar::spawn(build_card()); } diff --git a/crates/apps/yahweh-shell/src/hot_reload.rs b/crates/apps/nahual-shell/src/hot_reload.rs similarity index 99% rename from crates/apps/yahweh-shell/src/hot_reload.rs rename to crates/apps/nahual-shell/src/hot_reload.rs index 960b086..f025020 100644 --- a/crates/apps/yahweh-shell/src/hot_reload.rs +++ b/crates/apps/nahual-shell/src/hot_reload.rs @@ -26,7 +26,7 @@ use std::time::Duration; use gpui::{App, AsyncApp, Entity}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; -use yahweh_core::LayerConfig; +use nahual_core::LayerConfig; use crate::layout_model::LayoutModel; diff --git a/crates/apps/yahweh-shell/src/layout_host.rs b/crates/apps/nahual-shell/src/layout_host.rs similarity index 96% rename from crates/apps/yahweh-shell/src/layout_host.rs rename to crates/apps/nahual-shell/src/layout_host.rs index a9fe8f1..83e129f 100644 --- a/crates/apps/yahweh-shell/src/layout_host.rs +++ b/crates/apps/nahual-shell/src/layout_host.rs @@ -26,16 +26,16 @@ use gpui::{ AnyView, Context, Entity, IntoElement, Render, SharedString, Window, div, prelude::*, }; -use yahweh_bus::{AppBus, AppEvent}; -use yahweh_core::{LayerConfig, LayoutDirection, NodeId}; -use yahweh_database_explorer::{DatabaseExplorer, DatabaseExplorerEvent}; -use yahweh_file_explorer::{FileExplorer, FileExplorerEvent}; -use yahweh_image_viewer::ImageViewer; -use yahweh_text_viewer::TextViewer; -use yahweh_widget_container_core::ChildSlot; -use yahweh_widget_splitter::{SplitContainer, SplitEvent}; -use yahweh_widget_tabs::TabContainer; -use yahweh_widget_tiled::{TiledContainer, TiledEvent}; +use nahual_bus::{AppBus, AppEvent}; +use nahual_core::{LayerConfig, LayoutDirection, NodeId}; +use nahual_database_explorer::{DatabaseExplorer, DatabaseExplorerEvent}; +use nahual_file_explorer::{FileExplorer, FileExplorerEvent}; +use nahual_image_viewer::ImageViewer; +use nahual_text_viewer::TextViewer; +use nahual_widget_container_core::ChildSlot; +use nahual_widget_splitter::{SplitContainer, SplitEvent}; +use nahual_widget_tabs::TabContainer; +use nahual_widget_tiled::{TiledContainer, TiledEvent}; use crate::layout_model::{LayoutModel, LayoutModelEvent}; use crate::managed_tree::ManagedTree; @@ -392,11 +392,11 @@ impl LayoutHost { cfg: &LayerConfig, cx: &mut Context, ) -> AnyView { - // Param `path` define el .sqlite. Default: "yahweh.db" en cwd. + // Param `path` define el .sqlite. Default: "nahual.db" en cwd. let path = cfg .get_param("path") .cloned() - .unwrap_or_else(|| "yahweh.db".to_string()); + .unwrap_or_else(|| "nahual.db".to_string()); let entity = match self.nodes.get(&id) { Some(NodeSlot::DatabaseExplorer(e)) => e.clone(), @@ -544,7 +544,7 @@ pub struct PlaceholderView { impl PlaceholderView { pub fn new(kind: String, cx: &mut Context) -> Self { - cx.observe_global::(|_, cx| cx.notify()) + cx.observe_global::(|_, cx| cx.notify()) .detach(); Self { kind } } @@ -552,7 +552,7 @@ impl PlaceholderView { impl Render for PlaceholderView { fn render(&mut self, _w: &mut Window, cx: &mut Context) -> impl IntoElement { - let theme = yahweh_theme::Theme::global(cx).clone(); + let theme = nahual_theme::Theme::global(cx).clone(); div() .size_full() .bg(theme.bg_panel.clone()) diff --git a/crates/apps/yahweh-shell/src/layout_model.rs b/crates/apps/nahual-shell/src/layout_model.rs similarity index 99% rename from crates/apps/yahweh-shell/src/layout_model.rs rename to crates/apps/nahual-shell/src/layout_model.rs index 3202b96..d0ffaf3 100644 --- a/crates/apps/yahweh-shell/src/layout_model.rs +++ b/crates/apps/nahual-shell/src/layout_model.rs @@ -18,7 +18,7 @@ use gpui::{Context, EventEmitter}; -use yahweh_core::{LayerConfig, NodeId}; +use nahual_core::{LayerConfig, NodeId}; #[derive(Clone, Debug)] pub enum LayoutModelEvent { diff --git a/crates/apps/yahweh-shell/src/main.rs b/crates/apps/nahual-shell/src/main.rs similarity index 93% rename from crates/apps/yahweh-shell/src/main.rs rename to crates/apps/nahual-shell/src/main.rs index 8afbea6..1fe4e63 100644 --- a/crates/apps/yahweh-shell/src/main.rs +++ b/crates/apps/nahual-shell/src/main.rs @@ -16,9 +16,9 @@ mod status_panel; use gpui::{App, Application, Bounds, WindowBounds, WindowOptions, prelude::*, px, size}; -use yahweh_bus::AppBus; -use yahweh_core::LayerConfig; -use yahweh_theme::Theme; +use nahual_bus::AppBus; +use nahual_core::LayerConfig; +use nahual_theme::Theme; use crate::layout_host::LayoutHost; use crate::layout_model::LayoutModel; @@ -27,7 +27,7 @@ use crate::persister::Persister; const LAYOUT_PATH: &str = "layout.json"; fn main() { - // Sidecar brahman: yahweh se presenta al Init antes de levantar GPUI. + // Sidecar brahman: nahual se presenta al Init antes de levantar GPUI. // No bloquea: si el Init no está, el thread loggea y termina. brahman_client::spawn(); diff --git a/crates/apps/yahweh-shell/src/managed_tree.rs b/crates/apps/nahual-shell/src/managed_tree.rs similarity index 99% rename from crates/apps/yahweh-shell/src/managed_tree.rs rename to crates/apps/nahual-shell/src/managed_tree.rs index 1d8e03d..1671a78 100644 --- a/crates/apps/yahweh-shell/src/managed_tree.rs +++ b/crates/apps/nahual-shell/src/managed_tree.rs @@ -16,7 +16,7 @@ use gpui::{ Context, Entity, EventEmitter, IntoElement, Render, SharedString, Window, div, prelude::*, }; -use yahweh_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; +use nahual_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView}; // ===================================================================== // Datasets stub (Fase 3). En Fase 4 los reemplazan los providers reales. @@ -57,7 +57,7 @@ pub fn dataset_for(key: &str) -> DemoNode { fn yahweh_sources_tree() -> DemoNode { DemoNode::branch( "src-root", - "yahweh (src)", + "nahual (src)", vec![ DemoNode::branch( "shell", diff --git a/crates/apps/yahweh-shell/src/persister.rs b/crates/apps/nahual-shell/src/persister.rs similarity index 97% rename from crates/apps/yahweh-shell/src/persister.rs rename to crates/apps/nahual-shell/src/persister.rs index 0b9caa0..c679273 100644 --- a/crates/apps/yahweh-shell/src/persister.rs +++ b/crates/apps/nahual-shell/src/persister.rs @@ -30,7 +30,7 @@ impl Persister { Self { path } } - fn write(&self, tree: &yahweh_core::LayerConfig) { + fn write(&self, tree: &nahual_core::LayerConfig) { let json = tree.serialize_json(); // Anti-loop: si el contenido en disco ya coincide, skip. Esto // matters cuando el watcher está corriendo: persister write → diff --git a/crates/apps/yahweh-shell/src/status_panel.rs b/crates/apps/nahual-shell/src/status_panel.rs similarity index 98% rename from crates/apps/yahweh-shell/src/status_panel.rs rename to crates/apps/nahual-shell/src/status_panel.rs index 73abbbe..06bffe0 100644 --- a/crates/apps/yahweh-shell/src/status_panel.rs +++ b/crates/apps/nahual-shell/src/status_panel.rs @@ -13,8 +13,8 @@ use gpui::{ ClickEvent, Context, Entity, IntoElement, Render, SharedString, Window, div, prelude::*, px, }; -use yahweh_core::{LayerConfig, NodeId}; -use yahweh_theme::Theme; +use nahual_core::{LayerConfig, NodeId}; +use nahual_theme::Theme; use crate::layout_model::LayoutModel; @@ -107,7 +107,7 @@ impl StatusPanel { } } -fn find_kind(node: &yahweh_core::LayerConfig, target: &str) -> Option { +fn find_kind(node: &nahual_core::LayerConfig, target: &str) -> Option { if let Some(id) = &node.id { if id == target { return Some(node.kind.clone()); diff --git a/crates/apps/text_viewer/Cargo.toml b/crates/apps/nahual-text-viewer/Cargo.toml similarity index 50% rename from crates/apps/text_viewer/Cargo.toml rename to crates/apps/nahual-text-viewer/Cargo.toml index fd462a7..4fdf95d 100644 --- a/crates/apps/text_viewer/Cargo.toml +++ b/crates/apps/nahual-text-viewer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "yahweh-text-viewer" +name = "nahual-text-viewer" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -7,8 +7,8 @@ description = "Visor de texto plano. Suscribe al AppBus y carga contenido async. [dependencies] gpui = { workspace = true } -yahweh-core = { workspace = true } -yahweh-theme = { workspace = true } -yahweh-bus = { workspace = true } -yahweh-provider-fs = { workspace = true } -yahweh-provider-sqlite = { workspace = true } +nahual-core = { workspace = true } +nahual-theme = { workspace = true } +nahual-bus = { workspace = true } +nahual-provider-fs = { workspace = true } +nahual-provider-sqlite = { workspace = true } diff --git a/crates/apps/text_viewer/src/lib.rs b/crates/apps/nahual-text-viewer/src/lib.rs similarity index 96% rename from crates/apps/text_viewer/src/lib.rs rename to crates/apps/nahual-text-viewer/src/lib.rs index 070bb94..33fb280 100644 --- a/crates/apps/text_viewer/src/lib.rs +++ b/crates/apps/nahual-text-viewer/src/lib.rs @@ -1,4 +1,4 @@ -//! `yahweh_text_viewer` — visor de texto plano. +//! `nahual_text_viewer` — visor de texto plano. //! //! Suscribe al `AppBus` y, en cada `EntitySelected` / `EntityOpened`, //! decide si el `provider` corresponde a uno que sabe leer (por ahora @@ -17,11 +17,11 @@ use gpui::{ Context, Entity, IntoElement, Render, SharedString, Window, div, prelude::*, px, }; -use yahweh_bus::{AppBus, AppEvent}; -use yahweh_core::DataProvider; -use yahweh_provider_fs::{FileDataProvider, PROVIDER_ID as FS_PROVIDER_ID}; -use yahweh_provider_sqlite::{PROVIDER_ID as SQL_PROVIDER_ID, SqliteDataProvider}; -use yahweh_theme::Theme; +use nahual_bus::{AppBus, AppEvent}; +use nahual_core::DataProvider; +use nahual_provider_fs::{FileDataProvider, PROVIDER_ID as FS_PROVIDER_ID}; +use nahual_provider_sqlite::{PROVIDER_ID as SQL_PROVIDER_ID, SqliteDataProvider}; +use nahual_theme::Theme; const PREVIEW_HEX_BYTES: usize = 256; const MAX_TEXT_BYTES: usize = 256 * 1024; @@ -129,7 +129,7 @@ impl TextViewer { gen: u64, cx: &mut Context, ) { - let db_path = provider_path.unwrap_or_else(|| "yahweh.db".to_string()); + let db_path = provider_path.unwrap_or_else(|| "nahual.db".to_string()); cx.spawn(async move |this, cx| { // El SqliteDataProvider abre la DB en su constructor — si // falla, reportamos error y salimos. diff --git a/crates/apps/nakui-explorer/Cargo.toml b/crates/apps/nakui-explorer/Cargo.toml index bb69773..a826a80 100644 --- a/crates/apps/nakui-explorer/Cargo.toml +++ b/crates/apps/nakui-explorer/Cargo.toml @@ -7,12 +7,12 @@ description = "Explorador GPUI del event log de Nakui: timeline de seeds + morph [dependencies] nakui-core = { path = "../../modules/nakui/core" } -yahweh-meta-runtime = { path = "../../modules/ui_engine/libs/meta-runtime" } -yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" } -yahweh-widget-card = { path = "../../modules/ui_engine/widgets/card" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" } +nahual-meta-runtime = { path = "../../modules/nahual/libs/meta-runtime" } +nahual-widget-banner = { path = "../../modules/nahual/widgets/banner" } +nahual-widget-card = { path = "../../modules/nahual/widgets/card" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-widget-app-header = { path = "../../modules/nahual/widgets/app-header" } gpui = { workspace = true } serde_json = { workspace = true } uuid = { workspace = true, features = ["serde"] } diff --git a/crates/apps/nakui-explorer/src/main.rs b/crates/apps/nakui-explorer/src/main.rs index 632c95f..479a20a 100644 --- a/crates/apps/nakui-explorer/src/main.rs +++ b/crates/apps/nakui-explorer/src/main.rs @@ -10,7 +10,7 @@ //! producción que va escribiendo). Sin discovery dinámico vía broker //! brahman porque nakui hoy es CLI/library/demos, no daemon — cuando //! se daemonice, sustituir el lector de archivo por un sidecar -//! consumer (mismo patrón que `nouser-explorer`). +//! consumer (mismo patrón que `akasha-explorer`). //! //! ## Uso //! @@ -29,12 +29,12 @@ use gpui::{ div, prelude::*, px, rgb, Context, IntoElement, Render, SharedString, Window, }; use nakui_core::event_log::{EventLog, LogEntry}; -use yahweh_launcher::launch_app; -use yahweh_meta_runtime::{preview_value, short_hash, short_uuid}; -use yahweh_theme::Theme; -use yahweh_widget_app_header::app_header; -use yahweh_widget_banner::{banner_themed, Banner}; -use yahweh_widget_card::card_themed; +use nahual_launcher::launch_app; +use nahual_meta_runtime::{preview_value, short_hash, short_uuid}; +use nahual_theme::Theme; +use nahual_widget_app_header::app_header; +use nahual_widget_banner::{banner_themed, Banner}; +use nahual_widget_card::card_themed; const REFRESH_INTERVAL: Duration = Duration::from_secs(2); @@ -149,7 +149,7 @@ impl Render for Explorer { self.last_load_ms, ); - // Header standard via widget compartido yahweh-widget-app-header + // Header standard via widget compartido nahual-widget-app-header // (label flex_grow + theme switcher derecha + bg panel + border // bottom + text styling consistente). let header = app_header(cx, header_text); @@ -351,7 +351,7 @@ impl Render for Explorer { } // Helpers `short_uuid`, `short_hash`, `preview_value` viven en -// `yahweh_meta_runtime::format`. Se usan acá via el `use` de arriba. +// `nahual_meta_runtime::format`. Se usan acá via el `use` de arriba. #[cfg(test)] mod tests { @@ -418,7 +418,7 @@ mod tests { } // Tests de `short_uuid` / `short_hash` / `preview_value` viven - // en `yahweh-meta-runtime::format` tras la migración. Si esos se + // en `nahual-meta-runtime::format` tras la migración. Si esos se // vuelven a romper, los tests específicos del crate runtime los // capturan; acá no duplicamos. } diff --git a/crates/apps/nakui-ui/Cargo.toml b/crates/apps/nakui-ui/Cargo.toml index 2305be1..2b02467 100644 --- a/crates/apps/nakui-ui/Cargo.toml +++ b/crates/apps/nakui-ui/Cargo.toml @@ -7,12 +7,12 @@ description = "Nakui — runtime GPUI de la metainterfaz: carga module.json desd [dependencies] nakui-core = { path = "../../modules/nakui/core" } -yahweh-meta-schema = { path = "../../modules/ui_engine/libs/meta-schema" } -yahweh-meta-runtime = { path = "../../modules/ui_engine/libs/meta-runtime" } -brahman-cards = { path = "../../core/brahman-cards" } -yahweh-widget-text-input = { path = "../../modules/ui_engine/widgets/text_input" } -yahweh-widget-meta-form = { path = "../../modules/ui_engine/widgets/meta-form" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } +nahual-meta-schema = { path = "../../modules/nahual/libs/meta-schema" } +nahual-meta-runtime = { path = "../../modules/nahual/libs/meta-runtime" } +brahman-cards = { path = "../../protocol/brahman-cards" } +nahual-widget-text-input = { path = "../../modules/nahual/widgets/text_input" } +nahual-widget-meta-form = { path = "../../modules/nahual/widgets/meta-form" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } gpui = { workspace = true } serde_json = { workspace = true } uuid = { workspace = true, features = ["serde"] } diff --git a/crates/apps/nakui-ui/src/backend.rs b/crates/apps/nakui-ui/src/backend.rs index a7c747f..665f781 100644 --- a/crates/apps/nakui-ui/src/backend.rs +++ b/crates/apps/nakui-ui/src/backend.rs @@ -18,7 +18,7 @@ use nakui_core::event_log::{ }; use nakui_core::executor::Executor; use nakui_core::store::{MemoryStore, Store}; -use yahweh_meta_runtime::{MetaBackend, WriteOutcome}; +use nahual_meta_runtime::{MetaBackend, WriteOutcome}; /// Path del snapshot sibling del log: /// `nakui-ui-state.jsonl` ↔ `nakui-ui-state.snap.json`. diff --git a/crates/apps/nakui-ui/src/main.rs b/crates/apps/nakui-ui/src/main.rs index 00da256..543b866 100644 --- a/crates/apps/nakui-ui/src/main.rs +++ b/crates/apps/nakui-ui/src/main.rs @@ -1,7 +1,7 @@ //! `nakui-ui` — binario shell de la metainterfaz Nakui. //! //! Compone: -//! - **Yahweh widget** [`yahweh_widget_meta_form::MetaApp`] genérico +//! - **Yahweh widget** [`nahual_widget_meta_form::MetaApp`] genérico //! sobre cualquier `MetaBackend` — toda la lógica de //! render/edit/delete/morphism vive ahí. //! - **Backend** [`backend::NakuiBackend`] — implementa el trait @@ -31,9 +31,9 @@ use gpui::{ use brahman_cards::CardBody; use nakui_core::executor::Executor; -use yahweh_meta_schema::Module; -use yahweh_theme::Theme; -use yahweh_widget_meta_form::MetaApp; +use nahual_meta_schema::Module; +use nahual_theme::Theme; +use nahual_widget_meta_form::MetaApp; use crate::backend::NakuiBackend; @@ -190,8 +190,8 @@ fn load_ui_modules( mod tests { //! Tests del shell. Los tests del backend impl viven en //! `backend.rs`. Los tests del widget viven en - //! `yahweh-widget-meta-form`. Los helpers puros en - //! `yahweh-meta-runtime`. + //! `nahual-widget-meta-form`. Los helpers puros en + //! `nahual-meta-runtime`. use super::*; use serde_json::json; diff --git a/crates/apps/nouser-explorer/Cargo.toml b/crates/apps/nouser-explorer/Cargo.toml deleted file mode 100644 index e29f4f5..0000000 --- a/crates/apps/nouser-explorer/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "nouser-explorer" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Explorador GPUI de Mónadas: panel que descubre al daemon nouser vía broker brahman y consulta sus Mónadas dinámicamente." - -[dependencies] -brahman-card = { path = "../../core/brahman-card" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } -nouser-card = { path = "../../modules/nouser/card" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" } -yahweh-widget-card = { path = "../../modules/ui_engine/widgets/card" } -yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" } -gpui = { workspace = true } - -[[bin]] -name = "nouser-explorer" -path = "src/main.rs" diff --git a/crates/apps/pineal-demo/Cargo.toml b/crates/apps/pineal-demo/Cargo.toml new file mode 100644 index 0000000..ad6f916 --- /dev/null +++ b/crates/apps/pineal-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pineal-demo" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +publish = { workspace = true } +description = "Lapaloma — demo app: una serie sin(x) sobre ChartViewport rendereada con LapalomaChartElement. Valida la cadena core → render → cartesian → gpui en vivo." + +[dependencies] +gpui = { workspace = true } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +pineal-core = { path = "../../modules/pineal/core" } +pineal-render = { path = "../../modules/pineal/render", features = ["gpui"] } +pineal-cartesian = { path = "../../modules/pineal/cartesian" } diff --git a/crates/apps/lapaloma-demo/src/main.rs b/crates/apps/pineal-demo/src/main.rs similarity index 94% rename from crates/apps/lapaloma-demo/src/main.rs rename to crates/apps/pineal-demo/src/main.rs index 615f342..f3d6892 100644 --- a/crates/apps/lapaloma-demo/src/main.rs +++ b/crates/apps/pineal-demo/src/main.rs @@ -1,4 +1,4 @@ -//! `lapaloma-demo` — demo visual de Lapaloma sobre yahweh. +//! `pineal-demo` — demo visual de Lapaloma sobre nahual. //! //! Ventana 900×560 con un chart cartesiano de **3 series** //! simultáneas sobre 1024 muestras: @@ -14,11 +14,11 @@ use gpui::{ MouseMoveEvent, MouseUpEvent, Point, Render, ScrollDelta, ScrollWheelEvent, Window, }; -use lapaloma_cartesian::{chart_cache, ChartCacheHandle, ChartViewport, LapalomaChartElement}; -use lapaloma_core::buffer::DataBuffer; -use lapaloma_render::{Color, StrokeStyle}; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; +use pineal_cartesian::{chart_cache, ChartCacheHandle, ChartViewport, LapalomaChartElement}; +use pineal_core::buffer::DataBuffer; +use pineal_render::{Color, StrokeStyle}; +use nahual_launcher::launch_app; +use nahual_theme::Theme; const N_SAMPLES: usize = 1024; const WHEEL_SENSITIVITY: f64 = 0.0015; @@ -134,7 +134,7 @@ impl Demo { } } -/// Color helper para usar el mismo hex tanto en `lapaloma_render` +/// Color helper para usar el mismo hex tanto en `pineal_render` /// como en el body de texto del header del demo. const COLOR_SIN: u32 = 0x88c0d0; // azul nórdico const COLOR_COS: u32 = 0xd08770; // naranja @@ -171,7 +171,7 @@ impl Render for Demo { }; div() - .id("lapaloma-demo-root") + .id("pineal-demo-root") .size_full() .bg(theme.bg_app.clone()) .p(px(16.)) @@ -217,7 +217,7 @@ impl Render for Demo { ) .child( div() - .id("lapaloma-chart-host") + .id("pineal-chart-host") .w_full() .flex_grow() .child(chart) diff --git a/crates/apps/pineal-financial-demo/Cargo.toml b/crates/apps/pineal-financial-demo/Cargo.toml new file mode 100644 index 0000000..1bae64a --- /dev/null +++ b/crates/apps/pineal-financial-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pineal-financial-demo" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +publish = { workspace = true } +description = "Lapaloma — demo de candlesticks OHLC. Random walk sintético de 120 días con pan + zoom." + +[dependencies] +gpui = { workspace = true } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +pineal-render = { path = "../../modules/pineal/render", features = ["gpui"] } +pineal-cartesian = { path = "../../modules/pineal/cartesian" } +pineal-financial = { path = "../../modules/pineal/financial" } diff --git a/crates/apps/lapaloma-financial-demo/src/main.rs b/crates/apps/pineal-financial-demo/src/main.rs similarity index 95% rename from crates/apps/lapaloma-financial-demo/src/main.rs rename to crates/apps/pineal-financial-demo/src/main.rs index ae59f5d..05dff74 100644 --- a/crates/apps/lapaloma-financial-demo/src/main.rs +++ b/crates/apps/pineal-financial-demo/src/main.rs @@ -1,4 +1,4 @@ -//! `lapaloma-financial-demo` — chart OHLC con random walk. +//! `pineal-financial-demo` — chart OHLC con random walk. //! //! Genera 120 "días" de bars con un random walk determinístico //! (sin RNG runtime — derivado de un seed fijo + xorshift32 inline) @@ -10,13 +10,13 @@ use gpui::{ MouseMoveEvent, MouseUpEvent, Point, Render, ScrollDelta, ScrollWheelEvent, Window, }; -use lapaloma_cartesian::ChartViewport; -use lapaloma_financial::{ +use pineal_cartesian::ChartViewport; +use pineal_financial::{ lapaloma_candlestick, Bar, CandlestickStyle, OhlcBuffer, }; -use lapaloma_render::Color; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; +use pineal_render::Color; +use nahual_launcher::launch_app; +use nahual_theme::Theme; const N_BARS: usize = 120; const WHEEL_SENSITIVITY: f64 = 0.0015; @@ -140,7 +140,7 @@ impl Render for FinancialDemo { let drag_active = self.drag.is_some(); div() - .id("lapaloma-financial-root") + .id("pineal-financial-root") .size_full() .bg(theme.bg_app.clone()) .p(px(16.)) @@ -165,7 +165,7 @@ impl Render for FinancialDemo { ) .child( div() - .id("lapaloma-financial-chart") + .id("pineal-financial-chart") .w_full() .flex_grow() .child(chart) diff --git a/crates/apps/pineal-phosphor-demo/Cargo.toml b/crates/apps/pineal-phosphor-demo/Cargo.toml new file mode 100644 index 0000000..950815e --- /dev/null +++ b/crates/apps/pineal-phosphor-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pineal-phosphor-demo" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +publish = { workspace = true } +description = "Lapaloma — demo del trail CRT (phosphor) sobre un RingBuffer streaming a 60Hz. Compará con lapaloma-stream-demo para ver el contraste." + +[dependencies] +gpui = { workspace = true } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +pineal-core = { path = "../../modules/pineal/core" } +pineal-render = { path = "../../modules/pineal/render", features = ["gpui"] } +pineal-phosphor = { path = "../../modules/pineal/phosphor" } diff --git a/crates/apps/lapaloma-phosphor-demo/src/main.rs b/crates/apps/pineal-phosphor-demo/src/main.rs similarity index 89% rename from crates/apps/lapaloma-phosphor-demo/src/main.rs rename to crates/apps/pineal-phosphor-demo/src/main.rs index 4b1d23a..fa8bb5f 100644 --- a/crates/apps/lapaloma-phosphor-demo/src/main.rs +++ b/crates/apps/pineal-phosphor-demo/src/main.rs @@ -1,6 +1,6 @@ -//! `lapaloma-phosphor-demo` — osciloscopio con trail CRT. +//! `pineal-phosphor-demo` — osciloscopio con trail CRT. //! -//! Igual setup que `lapaloma-stream-demo` (RingBuffer 512 + +//! Igual setup que `pineal-stream-demo` (RingBuffer 512 + //! timer 60 Hz) pero el render usa `LapalomaPhosphorElement`: //! el trail decae en alpha del cursor hacia atrás y arrastra un //! halo (glow). Visualmente queda como un osciloscopio analógico @@ -13,11 +13,11 @@ use std::time::Duration; use gpui::{div, prelude::*, px, Context, IntoElement, Render, Window}; -use lapaloma_core::ring::RingBuffer; -use lapaloma_phosphor::lapaloma_phosphor; -use lapaloma_render::{Color, StrokeStyle}; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; +use pineal_core::ring::RingBuffer; +use pineal_phosphor::pineal_phosphor; +use pineal_render::{Color, StrokeStyle}; +use nahual_launcher::launch_app; +use nahual_theme::Theme; const RING_CAPACITY: usize = 512; const SAMPLE_PERIOD: Duration = Duration::from_millis(16); @@ -80,7 +80,7 @@ impl Render for PhosphorDemo { let plot_bg = Color::rgba(0.03, 0.05, 0.04, 1.0); let trace = StrokeStyle::new(1.6, Color::from_hex(0x9bff8c)); - let phosphor = lapaloma_phosphor(self.buffer.clone(), trace) + let phosphor = pineal_phosphor(self.buffer.clone(), trace) .background(plot_bg) .y_range(-1.2, 1.2) .trail_segments(24) diff --git a/crates/apps/pineal-stream-demo/Cargo.toml b/crates/apps/pineal-stream-demo/Cargo.toml new file mode 100644 index 0000000..1035884 --- /dev/null +++ b/crates/apps/pineal-stream-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pineal-stream-demo" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +publish = { workspace = true } +description = "Lapaloma — demo de streaming: RingBuffer + timer 60 Hz + sweep render. Showcase del zero-alloc en hot path." + +[dependencies] +gpui = { workspace = true } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +pineal-core = { path = "../../modules/pineal/core" } +pineal-render = { path = "../../modules/pineal/render", features = ["gpui"] } +pineal-stream = { path = "../../modules/pineal/stream" } diff --git a/crates/apps/lapaloma-stream-demo/src/main.rs b/crates/apps/pineal-stream-demo/src/main.rs similarity index 92% rename from crates/apps/lapaloma-stream-demo/src/main.rs rename to crates/apps/pineal-stream-demo/src/main.rs index 10266cd..ced1382 100644 --- a/crates/apps/lapaloma-stream-demo/src/main.rs +++ b/crates/apps/pineal-stream-demo/src/main.rs @@ -1,4 +1,4 @@ -//! `lapaloma-stream-demo` — osciloscopio sintético. +//! `pineal-stream-demo` — osciloscopio sintético. //! //! Ventana con un `LapalomaStreamElement` montado sobre un //! `RingBuffer` de 512 slots. Un timer en el background executor @@ -19,11 +19,11 @@ use std::time::Duration; use gpui::{div, prelude::*, px, Context, IntoElement, Render, Window}; -use lapaloma_core::ring::RingBuffer; -use lapaloma_render::{Color, StrokeStyle}; -use lapaloma_stream::lapaloma_stream; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; +use pineal_core::ring::RingBuffer; +use pineal_render::{Color, StrokeStyle}; +use pineal_stream::pineal_stream; +use nahual_launcher::launch_app; +use nahual_theme::Theme; const RING_CAPACITY: usize = 512; const SAMPLE_PERIOD: Duration = Duration::from_millis(16); @@ -89,7 +89,7 @@ impl Render for StreamDemo { let plot_bg = Color::rgba(0.08, 0.10, 0.13, 1.0); let stroke = StrokeStyle::new(1.8, Color::from_hex(0xa3be8c)); - let stream = lapaloma_stream(self.buffer.clone(), stroke) + let stream = pineal_stream(self.buffer.clone(), stroke) .background(plot_bg) .y_range(-1.2, 1.2); diff --git a/crates/apps/shipote-shell/Cargo.toml b/crates/apps/shipote-shell/Cargo.toml deleted file mode 100644 index 6eaa0cb..0000000 --- a/crates/apps/shipote-shell/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "shipote-shell" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -authors.workspace = true -publish.workspace = true -description = "GUI de shipote: vista de Workspaces+comandos+capabilities. Conecta al daemon vía shipote-protocol." - -[[bin]] -name = "shipote-shell" -path = "src/main.rs" - -[dependencies] -shipote-card = { path = "../../modules/shipote/shipote-card" } -shipote-protocol = { path = "../../modules/shipote/shipote-protocol" } -yahweh-theme = { path = "../../modules/ui_engine/libs/theme" } -yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" } -yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" } -yahweh-widget-stat-card = { path = "../../modules/ui_engine/widgets/stat-card" } -yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" } -tokio = { workspace = true } -gpui = { workspace = true } -ulid = { workspace = true } diff --git a/crates/apps/shipote-cli/Cargo.toml b/crates/apps/shuma-cli/Cargo.toml similarity index 65% rename from crates/apps/shipote-cli/Cargo.toml rename to crates/apps/shuma-cli/Cargo.toml index d4f4b58..f99d4d8 100644 --- a/crates/apps/shipote-cli/Cargo.toml +++ b/crates/apps/shuma-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shipote-cli" +name = "shuma-cli" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,13 +9,13 @@ publish.workspace = true description = "CLI de administración de shipote-daemon." [[bin]] -name = "shipote" +name = "shuma" path = "src/main.rs" [dependencies] -shipote-card = { path = "../../modules/shipote/shipote-card" } -shipote-protocol = { path = "../../modules/shipote/shipote-protocol" } -brahman-card = { path = "../../core/brahman-card" } +shuma-card = { path = "../../modules/shuma/shuma-card" } +shuma-protocol = { path = "../../modules/shuma/shuma-protocol" } +brahman-card = { path = "../../protocol/brahman-card" } anyhow = { workspace = true } clap = { workspace = true } tokio = { workspace = true } diff --git a/crates/apps/shipote-cli/src/main.rs b/crates/apps/shuma-cli/src/main.rs similarity index 98% rename from crates/apps/shipote-cli/src/main.rs rename to crates/apps/shuma-cli/src/main.rs index ee4d41c..d644ecb 100644 --- a/crates/apps/shipote-cli/src/main.rs +++ b/crates/apps/shuma-cli/src/main.rs @@ -1,17 +1,17 @@ -//! `shipote` — CLI de administración del daemon. +//! `shuma` — CLI de administración del daemon. use anyhow::{anyhow, Context, Result}; use clap::{Parser, Subcommand}; -use shipote_card::{load_pipeline_spec, load_workspace_spec, WorkspaceId}; -use shipote_protocol::{default_socket_path, read_frame, write_frame, Request, Response}; +use shuma_card::{load_pipeline_spec, load_workspace_spec, WorkspaceId}; +use shuma_protocol::{default_socket_path, read_frame, write_frame, Request, Response}; use std::path::PathBuf; use tokio::net::UnixStream; use ulid::Ulid; #[derive(Parser, Debug)] -#[command(name = "shipote", version, about = "Administración de shipote-daemon")] +#[command(name = "shuma", version, about = "Administración de shuma-daemon")] struct Cli { - /// Path al socket del daemon. Default: $XDG_RUNTIME_DIR/shipote.sock. + /// Path al socket del daemon. Default: $XDG_RUNTIME_DIR/shuma.sock. #[arg(long, global = true)] socket: Option, diff --git a/crates/apps/shipote-daemon/Cargo.toml b/crates/apps/shuma-daemon/Cargo.toml similarity index 52% rename from crates/apps/shipote-daemon/Cargo.toml rename to crates/apps/shuma-daemon/Cargo.toml index 5302210..e1f0143 100644 --- a/crates/apps/shipote-daemon/Cargo.toml +++ b/crates/apps/shuma-daemon/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shipote-daemon" +name = "shuma-daemon" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,17 +9,17 @@ publish.workspace = true description = "Daemon de shipote: dueño de los Workspaces, expone admin socket para shipote-cli." [[bin]] -name = "shipote-daemon" +name = "shuma-daemon" path = "src/main.rs" [dependencies] -shipote-card = { path = "../../modules/shipote/shipote-card" } -shipote-protocol = { path = "../../modules/shipote/shipote-protocol" } -shipote-discern = { path = "../../modules/shipote/shipote-discern" } -shipote-core = { path = "../../modules/shipote/shipote-core" } -ente-incarnate = { path = "../../shared/ente-incarnate" } -brahman-card = { path = "../../core/brahman-card" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } +shuma-card = { path = "../../modules/shuma/shuma-card" } +shuma-protocol = { path = "../../modules/shuma/shuma-protocol" } +shuma-discern = { path = "../../modules/shuma/shuma-discern" } +shuma-core = { path = "../../modules/shuma/shuma-core" } +ente-incarnate = { path = "../../init/ente-incarnate" } +brahman-card = { path = "../../protocol/brahman-card" } +brahman-sidecar = { path = "../../protocol/brahman-sidecar" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/apps/shipote-daemon/src/main.rs b/crates/apps/shuma-daemon/src/main.rs similarity index 94% rename from crates/apps/shipote-daemon/src/main.rs rename to crates/apps/shuma-daemon/src/main.rs index d4fa0b6..76d4a62 100644 --- a/crates/apps/shipote-daemon/src/main.rs +++ b/crates/apps/shuma-daemon/src/main.rs @@ -1,21 +1,21 @@ -//! `shipote-daemon` — punto de entrada del runtime de shipote. +//! `shuma-daemon` — punto de entrada del runtime de shuma. //! //! Responsabilidades: -//! - Escuchar el Unix socket admin (default: `$XDG_RUNTIME_DIR/shipote.sock`). -//! - Despachar mensajes del [`shipote_protocol`] al [`WorkspaceManager`]. +//! - Escuchar el Unix socket admin (default: `$XDG_RUNTIME_DIR/shuma.sock`). +//! - Despachar mensajes del [`shuma_protocol`] al [`WorkspaceManager`]. //! - Reapear hijos periódicamente. //! //! Lo que NO hace en v1: //! - Sidecar al broker / handshake con Init (futuro: cuando un workspace //! exponga `service_socket`, anunciar al broker). -//! - GUI (futuro `shipote-shell` con yahweh_launcher). +//! - GUI (futuro `shuma-shell` con nahual_launcher). use anyhow::Context; use brahman_card::{Card, CardKind, Flow, Flows, Lifecycle, Payload, Supervision, TypeRef}; use ente_incarnate::IncarnatorConfig; -use shipote_core::WorkspaceManager; -use shipote_discern::{DiscernPipeline, Hint}; -use shipote_protocol::{ +use shuma_core::WorkspaceManager; +use shuma_discern::{DiscernPipeline, Hint}; +use shuma_protocol::{ default_socket_path, read_frame, write_frame, CommandInfo as ProtoCommandInfo, EdgeDiscernmentInfo, FlowInfo, FlowThroughputInfo, QuotaReportInfo, Request, Response, WorkspaceStatsInfo, WorkspaceSummary, @@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> { let _ = std::fs::create_dir_all(parent); } let listener = UnixListener::bind(&sock).with_context(|| format!("bind {}", sock.display()))?; - info!(socket = %sock.display(), "shipote-daemon listening"); + info!(socket = %sock.display(), "shuma-daemon listening"); let daemon_started = std::time::Instant::now(); // Sidecar pool: una sesión global del daemon + N sesiones efímeras @@ -67,12 +67,12 @@ async fn main() -> anyhow::Result<()> { // Restaurar snapshot previo si existe. Workspaces se recrean; los // pids de comandos viejos NO se recuperan (kernel los mató). Los // pipelines vivos (con supervisor) se relanzan desde cero. - let snapshot_path = shipote_core::persist::default_snapshot_path(); + let snapshot_path = shuma_core::persist::default_snapshot_path(); let restore = match mgr.restore_snapshot(&snapshot_path).await { Ok(r) => r, Err(e) => { warn!(?e, "restore_snapshot falló — start fresh"); - shipote_core::persist::RestoreOutcome::default() + shuma_core::persist::RestoreOutcome::default() } }; // Relauncher de live_pipelines: como necesita inc+disc del daemon, @@ -85,7 +85,7 @@ async fn main() -> anyhow::Result<()> { let ws_label = mgr.workspace_label(workspace).await.unwrap_or_default(); let tap = entry.tap; let spec = entry.spec; - match shipote_core::pipeline::run_pipeline( + match shuma_core::pipeline::run_pipeline( &spec, &ws_label, tap, disc, inc, Some(mgr.clone()), ) .await @@ -114,7 +114,7 @@ async fn main() -> anyhow::Result<()> { _ = term.recv() => "SIGTERM", _ = int.recv() => "SIGINT", }; - info!(signal = sig_name, "shipote-daemon shutdown: draining workspaces"); + info!(signal = sig_name, "shuma-daemon shutdown: draining workspaces"); // 1) Snapshot ANTES del drain — preserva intención declarada // (los workspace specs siguen vivos en el snapshot aunque @@ -172,7 +172,7 @@ async fn main() -> anyhow::Result<()> { // Escalar el backoff para la PRÓXIMA falla. let next_backoff = (sup.current_backoff_ms * 2) .min(new_spec.restart_max_backoff_ms); - match shipote_core::pipeline::run_pipeline( + match shuma_core::pipeline::run_pipeline( &new_spec, &ws_label, tap, @@ -288,7 +288,7 @@ async fn handle_client( loop { let req: Request = match read_frame(&mut stream).await { Ok(r) => r, - Err(shipote_protocol::ProtocolError::Closed) => return Ok(()), + Err(shuma_protocol::ProtocolError::Closed) => return Ok(()), Err(e) => return Err(e.into()), }; audit_request(peer, &req); @@ -297,16 +297,16 @@ async fn handle_client( } } -/// Path canónico del audit log: `$XDG_STATE_HOME/shipote/audit.log` o -/// fallback `$HOME/.local/state/shipote/audit.log`. +/// Path canónico del audit log: `$XDG_STATE_HOME/shuma/audit.log` o +/// fallback `$HOME/.local/state/shuma/audit.log`. fn default_audit_log_path() -> std::path::PathBuf { if let Ok(state) = std::env::var("XDG_STATE_HOME") { - return std::path::PathBuf::from(state).join("shipote/audit.log"); + return std::path::PathBuf::from(state).join("shuma/audit.log"); } if let Ok(home) = std::env::var("HOME") { - return std::path::PathBuf::from(home).join(".local/state/shipote/audit.log"); + return std::path::PathBuf::from(home).join(".local/state/shuma/audit.log"); } - std::path::PathBuf::from("/tmp/shipote-audit.log") + std::path::PathBuf::from("/tmp/shuma-audit.log") } /// Cap del audit log antes de rotar a `audit.log.1`. 1 MiB. @@ -450,14 +450,14 @@ async fn dispatch( Request::PipelineRun { spec, tap, vars } => { let vars_map: std::collections::HashMap = vars.into_iter().collect(); - let spec = match shipote_card::substitute_vars(&spec, &vars_map) { + let spec = match shuma_card::substitute_vars(&spec, &vars_map) { Ok(s) => s, Err(e) => return Response::Error { message: format!("template: {e}") }, }; let disc = DiscernPipeline::default_pipeline(); let inc = mgr.incarnator_handle(); let ws_label = mgr.workspace_label(spec.workspace).await.unwrap_or_default(); - match shipote_core::pipeline::run_pipeline( + match shuma_core::pipeline::run_pipeline( &spec, &ws_label, tap, @@ -520,9 +520,9 @@ async fn dispatch( Request::CommandLogs { workspace, command, tail_bytes, stream } => { let s = match stream.as_str() { - "stdout" => shipote_core::LogStream::Stdout, - "stderr" => shipote_core::LogStream::Stderr, - _ => shipote_core::LogStream::Both, + "stdout" => shuma_core::LogStream::Stdout, + "stderr" => shuma_core::LogStream::Stderr, + _ => shuma_core::LogStream::Both, }; match mgr.get_command_logs(workspace, command, tail_bytes, s).await { Some(bytes) => Response::CommandLogs { bytes }, @@ -550,14 +550,14 @@ async fn dispatch( Request::PipelineRunSaved { name, tap, vars } => match mgr.get_saved_pipeline(&name).await { Some(spec) => { let vars_map: std::collections::HashMap = vars.into_iter().collect(); - let spec = match shipote_card::substitute_vars(&spec, &vars_map) { + let spec = match shuma_card::substitute_vars(&spec, &vars_map) { Ok(s) => s, Err(e) => return Response::Error { message: format!("template: {e}") }, }; let disc = DiscernPipeline::default_pipeline(); let inc = mgr.incarnator_handle(); let ws_label = mgr.workspace_label(spec.workspace).await.unwrap_or_default(); - match shipote_core::pipeline::run_pipeline( + match shuma_core::pipeline::run_pipeline( &spec, &ws_label, tap, @@ -740,7 +740,7 @@ async fn dispatch( } } -fn map_edge_to_info(e: shipote_core::pipeline::EdgeDiscernment) -> EdgeDiscernmentInfo { +fn map_edge_to_info(e: shuma_core::pipeline::EdgeDiscernment) -> EdgeDiscernmentInfo { EdgeDiscernmentInfo { from_label: e.from_label, from_output: e.from_output, @@ -757,17 +757,17 @@ fn map_edge_to_info(e: shipote_core::pipeline::EdgeDiscernment) -> EdgeDiscernme /// Por cada edge con TypeRef detectado, spawneamos una Card efímera en el /// SidecarPool que se anuncia al broker como producer del TypeRef /// enriquecido. Esto permite a otros explorers (broker-explorer, etc.) -/// ver que shipote vio JSON/text/wasm/etc. saliendo de un pipeline. +/// ver que shuma vio JSON/text/wasm/etc. saliendo de un pipeline. fn announce_edges_to_broker( pool: Option<&brahman_sidecar::SidecarPool>, pipeline: &ulid::Ulid, - edges: &[shipote_core::pipeline::EdgeDiscernment], + edges: &[shuma_core::pipeline::EdgeDiscernment], ) { let Some(pool) = pool else { return }; for e in edges { let Some(d) = &e.discernment else { continue }; let label = format!( - "shipote.flow.{}.{}.{}.{}", + "shuma.flow.{}.{}.{}.{}", short_ulid(pipeline), e.from_label, e.from_output, @@ -804,11 +804,11 @@ fn type_label(t: &TypeRef) -> String { } /// Card del daemon. La presentamos al broker así otras sesiones pueden -/// descubrir que shipote está corriendo y, eventualmente, conectarse +/// descubrir que shuma está corriendo y, eventualmente, conectarse /// como consumidoras del flow `workspaces` (futuro: que la GUI o el /// broker-explorer los listen vía broker en lugar de socket directo). fn build_daemon_card(service_socket: &std::path::Path) -> Card { - let mut card = Card::new("shipote.daemon"); + let mut card = Card::new("shuma.daemon"); card.kind = CardKind::Ente; card.lifecycle = Lifecycle::Daemon; card.payload = Payload::Virtual; // el daemon ya está corriendo (no es PID 1 quien lo encarna) @@ -820,7 +820,7 @@ fn build_daemon_card(service_socket: &std::path::Path) -> Card { Flow { name: "workspaces".into(), ty: TypeRef::Wit { - package: "shipote:admin".into(), + package: "shuma:admin".into(), interface: None, name: "workspace-list".into(), }, @@ -829,7 +829,7 @@ fn build_daemon_card(service_socket: &std::path::Path) -> Card { Flow { name: "discern".into(), ty: TypeRef::Wit { - package: "shipote:admin".into(), + package: "shuma:admin".into(), interface: None, name: "discernment".into(), }, diff --git a/crates/apps/shipote-gateway/Cargo.toml b/crates/apps/shuma-gateway/Cargo.toml similarity index 79% rename from crates/apps/shipote-gateway/Cargo.toml rename to crates/apps/shuma-gateway/Cargo.toml index 8a10a91..58fa343 100644 --- a/crates/apps/shipote-gateway/Cargo.toml +++ b/crates/apps/shuma-gateway/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shipote-gateway" +name = "shuma-gateway" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,11 +9,11 @@ publish.workspace = true description = "HTTP/JSON gateway para shipote — traduce JSON ↔ postcard contra el admin socket." [[bin]] -name = "shipote-gateway" +name = "shuma-gateway" path = "src/main.rs" [dependencies] -shipote-protocol = { path = "../../modules/shipote/shipote-protocol" } +shuma-protocol = { path = "../../modules/shuma/shuma-protocol" } anyhow = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } diff --git a/crates/apps/shipote-gateway/src/main.rs b/crates/apps/shuma-gateway/src/main.rs similarity index 94% rename from crates/apps/shipote-gateway/src/main.rs rename to crates/apps/shuma-gateway/src/main.rs index 77a00d8..9f77a43 100644 --- a/crates/apps/shipote-gateway/src/main.rs +++ b/crates/apps/shuma-gateway/src/main.rs @@ -1,6 +1,6 @@ -//! `shipote-gateway` — HTTP/JSON adapter para el daemon. +//! `shuma-gateway` — HTTP/JSON adapter para el daemon. //! -//! Acepta `POST /rpc` con body JSON serializado como `shipote_protocol::Request`, +//! Acepta `POST /rpc` con body JSON serializado como `shuma_protocol::Request`, //! hace round-trip al admin socket via postcard, devuelve `Response` como JSON. //! //! Diseñado para clients no-Rust (curl, Python, web app) que no pueden @@ -9,7 +9,7 @@ //! //! Sin dep de axum/hyper: HTTP parser ad-hoc, suficiente para 1 endpoint. -use shipote_protocol::{default_socket_path, read_frame, write_frame, Request, Response}; +use shuma_protocol::{default_socket_path, read_frame, write_frame, Request, Response}; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, UnixStream}; @@ -23,7 +23,7 @@ async fn main() -> anyhow::Result<()> { let listen = std::env::var("SHIPOTE_GATEWAY_LISTEN").unwrap_or_else(|_| DEFAULT_LISTEN.into()); let daemon_sock = Arc::new(default_socket_path()); let listener = TcpListener::bind(&listen).await?; - info!(listen = %listen, daemon = %daemon_sock.display(), "shipote-gateway listening"); + info!(listen = %listen, daemon = %daemon_sock.display(), "shuma-gateway listening"); loop { match listener.accept().await { @@ -79,7 +79,7 @@ async fn handle_http(mut stream: TcpStream, daemon_sock: Arc // Rutas: if method == "GET" && (path == "/" || path == "/health") { - return write_text(&mut stream, 200, "shipote-gateway ok\n").await; + return write_text(&mut stream, 200, "shuma-gateway ok\n").await; } if method != "POST" || path != "/rpc" { return write_error(&mut stream, 404, "use POST /rpc").await; diff --git a/crates/apps/shuma-shell/Cargo.toml b/crates/apps/shuma-shell/Cargo.toml new file mode 100644 index 0000000..306ebee --- /dev/null +++ b/crates/apps/shuma-shell/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "shuma-shell" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true +description = "GUI de shipote: vista de Workspaces+comandos+capabilities. Conecta al daemon vía shipote-protocol." + +[[bin]] +name = "shuma-shell" +path = "src/main.rs" + +[dependencies] +shuma-card = { path = "../../modules/shuma/shuma-card" } +shuma-protocol = { path = "../../modules/shuma/shuma-protocol" } +nahual-theme = { path = "../../modules/nahual/libs/theme" } +nahual-launcher = { path = "../../modules/nahual/libs/launcher" } +nahual-widget-banner = { path = "../../modules/nahual/widgets/banner" } +nahual-widget-stat-card = { path = "../../modules/nahual/widgets/stat-card" } +nahual-widget-app-header = { path = "../../modules/nahual/widgets/app-header" } +tokio = { workspace = true } +gpui = { workspace = true } +ulid = { workspace = true } diff --git a/crates/apps/shipote-shell/src/main.rs b/crates/apps/shuma-shell/src/main.rs similarity index 97% rename from crates/apps/shipote-shell/src/main.rs rename to crates/apps/shuma-shell/src/main.rs index fae8099..0e26a10 100644 --- a/crates/apps/shipote-shell/src/main.rs +++ b/crates/apps/shuma-shell/src/main.rs @@ -1,22 +1,22 @@ -//! `shipote-shell` — GUI dashboard del daemon shipote. +//! `shuma-shell` — GUI dashboard del daemon shuma. //! //! Probe-style: conecta al socket del daemon cada 2s, pide //! capabilities + workspace-list y los muestra en cards. //! Si el daemon no está corriendo, marca DOWN. use gpui::{div, prelude::*, px, Context, IntoElement, Render, SharedString, Window}; -use shipote_protocol::{ +use shuma_protocol::{ default_socket_path, read_frame, write_frame, CommandInfo, FlowInfo, FlowThroughputInfo, QuotaReportInfo, Request, Response, WorkspaceStatsInfo, WorkspaceSummary, }; use std::path::PathBuf; use std::time::Duration; use tokio::net::UnixStream; -use yahweh_launcher::launch_app; -use yahweh_theme::Theme; -use yahweh_widget_app_header::app_header; -use yahweh_widget_banner::{banner_themed, Banner}; -use yahweh_widget_stat_card::stat_card; +use nahual_launcher::launch_app; +use nahual_theme::Theme; +use nahual_widget_app_header::app_header; +use nahual_widget_banner::{banner_themed, Banner}; +use nahual_widget_stat_card::stat_card; const POLL_INTERVAL: Duration = Duration::from_secs(2); @@ -292,9 +292,9 @@ fn probe_blocking(path: &std::path::Path) -> Result { } match best { Some((ws_str, cmd)) => { - let ws_id: shipote_card::WorkspaceId = ws_str + let ws_id: shuma_card::WorkspaceId = ws_str .parse::() - .map(shipote_card::WorkspaceId) + .map(shuma_card::WorkspaceId) .map_err(|e| format!("ulid parse: {e}"))?; write_frame( &mut stream, @@ -415,7 +415,7 @@ impl Render for Shell { let (status_value, status_descr, status_accent) = match &self.state { DaemonState::Pending => ("PENDING".to_string(), "primer probe…".to_string(), accent_pending), DaemonState::Down { reason } => ("DOWN".to_string(), reason.clone(), accent_down), - DaemonState::Up => ("UP".to_string(), "shipote-daemon respondiendo".to_string(), accent_up), + DaemonState::Up => ("UP".to_string(), "shuma-daemon respondiendo".to_string(), accent_up), }; let caps_items: Vec = self @@ -507,7 +507,7 @@ impl Render for Shell { let saved_count = self.saved_pipelines.len().to_string(); let saved_items: Vec = self.saved_pipelines.clone(); let saved_descr = if saved_items.is_empty() { - "shipote pipeline save para persistir".to_string() + "shuma pipeline save para persistir".to_string() } else { "definiciones reusables vía run-saved".to_string() }; diff --git a/crates/apps/yahweh-shell/Cargo.toml b/crates/apps/yahweh-shell/Cargo.toml deleted file mode 100644 index 7aba7b3..0000000 --- a/crates/apps/yahweh-shell/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "yahweh-shell" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -description = "Bootstrap GPUI + LayoutHost de Yahweh." - -[dependencies] -yahweh-core = { workspace = true } -yahweh-theme = { workspace = true } -yahweh-provider-fs = { workspace = true } -yahweh-provider-sqlite = { workspace = true } -yahweh-widget-tree = { workspace = true } -yahweh-widget-container-core = { workspace = true } -yahweh-widget-splitter = { workspace = true } -yahweh-widget-tabs = { workspace = true } -yahweh-widget-tiled = { workspace = true } -yahweh-bus = { workspace = true } -yahweh-file-explorer = { workspace = true } -yahweh-database-explorer = { workspace = true } -yahweh-text-viewer = { workspace = true } -yahweh-image-viewer = { workspace = true } -gpui = { workspace = true } -tokio = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -notify = { workspace = true } - -# Brahman protocol — sidecar thread que se presenta al Init. -brahman-card = { path = "../../core/brahman-card" } -brahman-sidecar = { path = "../../shared/brahman-sidecar" } -ulid = { workspace = true } - -[[bin]] -name = "yahweh" -path = "src/main.rs" diff --git a/crates/compat/SDD.md b/crates/compat/SDD.md new file mode 100644 index 0000000..1363b23 --- /dev/null +++ b/crates/compat/SDD.md @@ -0,0 +1,44 @@ +# compat/ — Shims D-Bus systemd + +**Propósito.** Permitir que software systemd-aware (GNOME, KDE, +PolicyKit, NetworkManager, etc.) corra sobre `ente-zero` sin systemd. +Cada shim es un binario standalone que se anuncia con un nombre +well-known D-Bus y traduce las llamadas al bus interno. + +## Crates + +| binario | reemplaza | D-Bus name | +| --------------------------- | --------------------- | ----------------------------------- | +| `ente-logind-compat` | systemd-logind | `org.freedesktop.login1` | +| `ente-hostnamed-compat` | systemd-hostnamed | `org.freedesktop.hostname1` | +| `ente-timedated-compat` | systemd-timedated | `org.freedesktop.timedate1` | +| `ente-localed-compat` | systemd-localed | `org.freedesktop.locale1` | +| `ente-journald-compat` | systemd-journald | `org.freedesktop.LogControl1` | +| `ente-resolved-compat` | systemd-resolved | `org.freedesktop.resolve1` | +| `ente-polkit-compat` | polkitd | `org.freedesktop.PolicyKit1` | +| `ente-machined-compat` | systemd-machined | `org.freedesktop.machine1` | +| `ente-systemd1-compat` | systemd Manager | `org.freedesktop.systemd1` | +| `ente-notify-compat` | sd_notify socket | `/run/systemd/notify` (datagram) | +| `ente-timer-compat` | systemd timers | (cron-like, sin D-Bus) | +| `ente-tmpfiles-compat` | systemd-tmpfiles | (aplica tmpfiles.d al boot) | +| `ente-binfmt-compat` | systemd-binfmt | (registra handlers binfmt_misc) | +| `ente-policy-provider` | (interno) | provider de decisiones polkit | + +## Dependencias + +- Todos ← `zbus`, `ente-bus`, `protocol/brahman-card`. Sin tests + (esperado: stubs D-Bus que delegan al bus interno). + +## Patrón común + +Cada shim: +1. Se conecta a `/run/brahman/bus`. +2. Reclama un well-known name vía zbus. +3. Implementa los métodos de la interfaz mínima usada por el ecosistema. +4. Loggea eventos al audit log de `ente-brain`. + +## Estado + +LOC ~5K total (~300 LOC c/u). Suficiente para que `desktop-file-utils`, +`xdg-open`, login managers, y CLIs systemd-aware no rompan. Pendiente: +cobertura de métodos avanzados (Inhibit en logind, SetVariable en localed). diff --git a/crates/core/ente-binfmt-compat/Cargo.toml b/crates/compat/ente-binfmt-compat/Cargo.toml similarity index 100% rename from crates/core/ente-binfmt-compat/Cargo.toml rename to crates/compat/ente-binfmt-compat/Cargo.toml diff --git a/crates/core/ente-binfmt-compat/src/main.rs b/crates/compat/ente-binfmt-compat/src/main.rs similarity index 100% rename from crates/core/ente-binfmt-compat/src/main.rs rename to crates/compat/ente-binfmt-compat/src/main.rs diff --git a/crates/core/ente-hostnamed-compat/Cargo.toml b/crates/compat/ente-hostnamed-compat/Cargo.toml similarity index 82% rename from crates/core/ente-hostnamed-compat/Cargo.toml rename to crates/compat/ente-hostnamed-compat/Cargo.toml index 04e3568..c8545e2 100644 --- a/crates/core/ente-hostnamed-compat/Cargo.toml +++ b/crates/compat/ente-hostnamed-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-hostnamed-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } nix = { workspace = true } libc = { workspace = true } anyhow = { workspace = true } diff --git a/crates/core/ente-hostnamed-compat/src/main.rs b/crates/compat/ente-hostnamed-compat/src/main.rs similarity index 100% rename from crates/core/ente-hostnamed-compat/src/main.rs rename to crates/compat/ente-hostnamed-compat/src/main.rs diff --git a/crates/core/ente-journald-compat/Cargo.toml b/crates/compat/ente-journald-compat/Cargo.toml similarity index 76% rename from crates/core/ente-journald-compat/Cargo.toml rename to crates/compat/ente-journald-compat/Cargo.toml index c248dfe..b766b6c 100644 --- a/crates/core/ente-journald-compat/Cargo.toml +++ b/crates/compat/ente-journald-compat/Cargo.toml @@ -14,9 +14,9 @@ name = "ente-journalctl" path = "src/journalctl.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } -ente-cas = { path = "../ente-cas" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } +ente-cas = { path = "../../runtime/ente-cas" } nix = { workspace = true } libc = { workspace = true } anyhow = { workspace = true } diff --git a/crates/core/ente-journald-compat/src/journalctl.rs b/crates/compat/ente-journald-compat/src/journalctl.rs similarity index 100% rename from crates/core/ente-journald-compat/src/journalctl.rs rename to crates/compat/ente-journald-compat/src/journalctl.rs diff --git a/crates/core/ente-journald-compat/src/main.rs b/crates/compat/ente-journald-compat/src/main.rs similarity index 100% rename from crates/core/ente-journald-compat/src/main.rs rename to crates/compat/ente-journald-compat/src/main.rs diff --git a/crates/core/ente-localed-compat/Cargo.toml b/crates/compat/ente-localed-compat/Cargo.toml similarity index 80% rename from crates/core/ente-localed-compat/Cargo.toml rename to crates/compat/ente-localed-compat/Cargo.toml index 04a5c5b..9e4c0d8 100644 --- a/crates/core/ente-localed-compat/Cargo.toml +++ b/crates/compat/ente-localed-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-localed-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-localed-compat/src/main.rs b/crates/compat/ente-localed-compat/src/main.rs similarity index 100% rename from crates/core/ente-localed-compat/src/main.rs rename to crates/compat/ente-localed-compat/src/main.rs diff --git a/crates/core/ente-logind-compat/Cargo.toml b/crates/compat/ente-logind-compat/Cargo.toml similarity index 80% rename from crates/core/ente-logind-compat/Cargo.toml rename to crates/compat/ente-logind-compat/Cargo.toml index 3a4f883..c84dbf1 100644 --- a/crates/core/ente-logind-compat/Cargo.toml +++ b/crates/compat/ente-logind-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-logind-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-logind-compat/src/main.rs b/crates/compat/ente-logind-compat/src/main.rs similarity index 100% rename from crates/core/ente-logind-compat/src/main.rs rename to crates/compat/ente-logind-compat/src/main.rs diff --git a/crates/core/ente-machined-compat/Cargo.toml b/crates/compat/ente-machined-compat/Cargo.toml similarity index 81% rename from crates/core/ente-machined-compat/Cargo.toml rename to crates/compat/ente-machined-compat/Cargo.toml index eb88b4a..04cc159 100644 --- a/crates/core/ente-machined-compat/Cargo.toml +++ b/crates/compat/ente-machined-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-machined-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-machined-compat/src/main.rs b/crates/compat/ente-machined-compat/src/main.rs similarity index 100% rename from crates/core/ente-machined-compat/src/main.rs rename to crates/compat/ente-machined-compat/src/main.rs diff --git a/crates/core/ente-notify-compat/Cargo.toml b/crates/compat/ente-notify-compat/Cargo.toml similarity index 80% rename from crates/core/ente-notify-compat/Cargo.toml rename to crates/compat/ente-notify-compat/Cargo.toml index fdba47a..c3662d9 100644 --- a/crates/core/ente-notify-compat/Cargo.toml +++ b/crates/compat/ente-notify-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-notify-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } nix = { workspace = true } libc = { workspace = true } anyhow = { workspace = true } diff --git a/crates/core/ente-notify-compat/src/main.rs b/crates/compat/ente-notify-compat/src/main.rs similarity index 100% rename from crates/core/ente-notify-compat/src/main.rs rename to crates/compat/ente-notify-compat/src/main.rs diff --git a/crates/core/ente-policy-provider/Cargo.toml b/crates/compat/ente-policy-provider/Cargo.toml similarity index 80% rename from crates/core/ente-policy-provider/Cargo.toml rename to crates/compat/ente-policy-provider/Cargo.toml index 9b2e83c..f035fcb 100644 --- a/crates/core/ente-policy-provider/Cargo.toml +++ b/crates/compat/ente-policy-provider/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-policy-provider" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } diff --git a/crates/core/ente-policy-provider/src/main.rs b/crates/compat/ente-policy-provider/src/main.rs similarity index 100% rename from crates/core/ente-policy-provider/src/main.rs rename to crates/compat/ente-policy-provider/src/main.rs diff --git a/crates/core/ente-polkit-compat/Cargo.toml b/crates/compat/ente-polkit-compat/Cargo.toml similarity index 80% rename from crates/core/ente-polkit-compat/Cargo.toml rename to crates/compat/ente-polkit-compat/Cargo.toml index 9aa81f5..66fc0b6 100644 --- a/crates/core/ente-polkit-compat/Cargo.toml +++ b/crates/compat/ente-polkit-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-polkit-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-polkit-compat/src/main.rs b/crates/compat/ente-polkit-compat/src/main.rs similarity index 100% rename from crates/core/ente-polkit-compat/src/main.rs rename to crates/compat/ente-polkit-compat/src/main.rs diff --git a/crates/core/ente-resolved-compat/Cargo.toml b/crates/compat/ente-resolved-compat/Cargo.toml similarity index 82% rename from crates/core/ente-resolved-compat/Cargo.toml rename to crates/compat/ente-resolved-compat/Cargo.toml index 4a351de..ca71285 100644 --- a/crates/core/ente-resolved-compat/Cargo.toml +++ b/crates/compat/ente-resolved-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-resolved-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } libc = { workspace = true } anyhow = { workspace = true } tokio = { workspace = true } diff --git a/crates/core/ente-resolved-compat/src/main.rs b/crates/compat/ente-resolved-compat/src/main.rs similarity index 100% rename from crates/core/ente-resolved-compat/src/main.rs rename to crates/compat/ente-resolved-compat/src/main.rs diff --git a/crates/core/ente-systemd1-compat/Cargo.toml b/crates/compat/ente-systemd1-compat/Cargo.toml similarity index 81% rename from crates/core/ente-systemd1-compat/Cargo.toml rename to crates/compat/ente-systemd1-compat/Cargo.toml index 425521e..be7fb94 100644 --- a/crates/core/ente-systemd1-compat/Cargo.toml +++ b/crates/compat/ente-systemd1-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-systemd1-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-systemd1-compat/src/main.rs b/crates/compat/ente-systemd1-compat/src/main.rs similarity index 100% rename from crates/core/ente-systemd1-compat/src/main.rs rename to crates/compat/ente-systemd1-compat/src/main.rs diff --git a/crates/core/ente-timedated-compat/Cargo.toml b/crates/compat/ente-timedated-compat/Cargo.toml similarity index 81% rename from crates/core/ente-timedated-compat/Cargo.toml rename to crates/compat/ente-timedated-compat/Cargo.toml index 4a38e51..f1b61fb 100644 --- a/crates/core/ente-timedated-compat/Cargo.toml +++ b/crates/compat/ente-timedated-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-timedated-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-timedated-compat/src/main.rs b/crates/compat/ente-timedated-compat/src/main.rs similarity index 100% rename from crates/core/ente-timedated-compat/src/main.rs rename to crates/compat/ente-timedated-compat/src/main.rs diff --git a/crates/core/ente-timer-compat/Cargo.toml b/crates/compat/ente-timer-compat/Cargo.toml similarity index 81% rename from crates/core/ente-timer-compat/Cargo.toml rename to crates/compat/ente-timer-compat/Cargo.toml index 835d9b3..f4bcf4b 100644 --- a/crates/core/ente-timer-compat/Cargo.toml +++ b/crates/compat/ente-timer-compat/Cargo.toml @@ -10,8 +10,8 @@ name = "ente-timer-compat" path = "src/main.rs" [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } serde = { workspace = true } serde_json = { workspace = true } ulid = { workspace = true } diff --git a/crates/core/ente-timer-compat/src/main.rs b/crates/compat/ente-timer-compat/src/main.rs similarity index 100% rename from crates/core/ente-timer-compat/src/main.rs rename to crates/compat/ente-timer-compat/src/main.rs diff --git a/crates/core/ente-tmpfiles-compat/Cargo.toml b/crates/compat/ente-tmpfiles-compat/Cargo.toml similarity index 100% rename from crates/core/ente-tmpfiles-compat/Cargo.toml rename to crates/compat/ente-tmpfiles-compat/Cargo.toml diff --git a/crates/core/ente-tmpfiles-compat/src/main.rs b/crates/compat/ente-tmpfiles-compat/src/main.rs similarity index 100% rename from crates/core/ente-tmpfiles-compat/src/main.rs rename to crates/compat/ente-tmpfiles-compat/src/main.rs diff --git a/crates/core/README.md b/crates/core/README.md deleted file mode 100644 index 652fcad..0000000 --- a/crates/core/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# `crates/core/` — Init Arje (absorbido) + Protocolo Brahman - -El directorio agrupa **dos linajes** que se fusionaron al absorberse arje -dentro del workspace de brahman: - -| linaje | prefijo | función | -| ----------- | ------------ | ------------------------------------------------ | -| `arje` | `ente-*` | Init (PID 1), encarnación Linux, compat systemd | -| `brahman` | `brahman-*` | Tarjeta canónica, handshake, broker, admin | - -No están en sub-carpetas físicas porque el workspace declara los paths uno -a uno en `Cargo.toml` raíz y muchos `Cargo.toml` hijos usan `path = -"../ente-X"`. El agrupamiento siguiente es **lógico**: cada crate se -encuentra como `crates/core/`. - ---- - -## 1. Init / PID 1 - -| crate | tipo | resumen | -| --------------- | ---------- | ------------------------------------------------------------------ | -| `ente-zero` | binario | PID 1 del fractal. Bucle primordial (reap + bus + handshake). | -| `ente-kernel` | lib | `bootstrap_kernel_surface()`, subreaper, SIGCHLD/uevent streams. | -| `ente-soma` | lib (shim) | Re-export sobre `crates/shared/ente-incarnate` (clone+ns+cgroup). | -| `ente-snapshot` | lib | `FractalSnapshot` JSON — checkpoint/restore del grafo de Cards. | - -## 2. Contratos canónicos - -| crate | resumen | -| ------------------ | -------------------------------------------------------------------- | -| `brahman-card` | `Card { soma, payload, flow, permissions, supervision, genesis }`. | -| `brahman-card-wit` | Extracción de interfaces WIT de componentes WASM. | -| `brahman-cards` | Helpers para construir Cards típicas (consumer/producer/broker). | -| `ente-card` | Alias histórico — re-export de `brahman-card` con nombres legacy. | - -## 3. Discovery / Routing - -| crate | resumen | -| -------------------- | ------------------------------------------------------------------ | -| `brahman-handshake` | Protocolo Init↔módulo (Hello, Ping, ListSessions) postcard/Unix. | -| `brahman-broker` | Service locator: empareja `flow.input` ↔ `flow.output` por tipo. | -| `brahman-admin` | Socket separado para snapshots de sesiones + matches. | - -## 4. IPC interno + Storage - -| crate | resumen | -| ------------- | ----------------------------------------------------------------------- | -| `ente-bus` | Unix SOCK_STREAM con framing postcard. `Announce`/`Invoke`/`ListEntes`. | -| `ente-cas` | Content-addressed storage SHA-256 (blobs Wasm, audit log). | -| `ente-wasm` | Encarna `Payload::Wasm` vía `wasmi` en thread dedicado. | - -## 5. Cerebro / Observabilidad - -| crate | resumen | -| ------------- | ------------------------------------------------------------------------ | -| `ente-brain` | Rule engine + observer estadístico + audit log con hash chain a CAS. | -| `ente-echo` | Ente de prueba — provee `Capability::Endpoint(echo)` para smoke tests. | - -## 6. Compat systemd (shims D-Bus) - -Cada shim es un binario que se anuncia con un nombre well-known -`org.freedesktop.X1` y traduce las llamadas al bus interno. Esto permite -que GNOME/KDE/aplicaciones legacy arranquen sobre arje sin systemd: - -| binario | reemplaza | nombre D-Bus | -| --------------------------- | --------------------- | ----------------------------------- | -| `ente-logind-compat` | `systemd-logind` | `org.freedesktop.login1` | -| `ente-hostnamed-compat` | `systemd-hostnamed` | `org.freedesktop.hostname1` | -| `ente-timedated-compat` | `systemd-timedated` | `org.freedesktop.timedate1` | -| `ente-localed-compat` | `systemd-localed` | `org.freedesktop.locale1` | -| `ente-journald-compat` | `systemd-journald` | `org.freedesktop.LogControl1` | -| `ente-resolved-compat` | `systemd-resolved` | `org.freedesktop.resolve1` | -| `ente-polkit-compat` | `polkitd` | `org.freedesktop.PolicyKit1` | -| `ente-machined-compat` | `systemd-machined` | `org.freedesktop.machine1` | -| `ente-systemd1-compat` | `systemd` (Manager) | `org.freedesktop.systemd1` | -| `ente-notify-compat` | `sd_notify` socket | `/run/systemd/notify` (datagram) | -| `ente-timer-compat` | `systemd-timer` | (cron-like, sin D-Bus) | -| `ente-tmpfiles-compat` | `systemd-tmpfiles` | (aplica tmpfiles.d al boot) | -| `ente-binfmt-compat` | `systemd-binfmt` | (registra binfmt_misc handlers) | -| `ente-policy-provider` | (interno) | proveedor de decisiones polkit | - ---- - -## Crates relacionados fuera de `core/` - -Dependen del Init pero viven en `crates/shared/`: - -- `ente-incarnate` — rutina pura de `clone(2) + namespaces + cgroup + - rlimits + cpu_affinity`. Reusable por shipote y supervisores no-PID-1. -- `brahman-net` — malla P2P opcional (libp2p) que extiende el handshake. -- `brahman-sidecar` — helper `spawn(card)` para que las apps se presenten - al Init sin reimplementar el cliente del handshake. - -## Convención de uso - -Para arrancar el Init y ejecutar Cards, ver: - -- **Seeds estándar** en `seeds/`. -- **Build de initramfs** con `scripts/build-arje-initrd.sh`. -- **Boot en QEMU / bare metal** documentado en `docs/arje-boot.md`. diff --git a/crates/init/SDD.md b/crates/init/SDD.md new file mode 100644 index 0000000..e029a92 --- /dev/null +++ b/crates/init/SDD.md @@ -0,0 +1,36 @@ +# init/ — Init (PID 1) y encarnación Linux + +**Propósito.** `ente-zero` arranca como PID 1 del fractal. Provee el +bucle primordial (reap + bus + handshake), bootstrap del kernel +surface, encarnación de Cards en procesos aislados con namespaces + +cgroups, y snapshot/restore del grafo. + +## Crates + +| crate | tipo | rol | +| ---------------- | ------- | ----------------------------------------------------- | +| `ente-zero` | binario | PID 1: reap + handshake server + bus dispatcher | +| `ente-kernel` | lib | `bootstrap_kernel_surface`, subreaper, SIGCHLD/uevent | +| `ente-soma` | lib | Shim sobre `ente-incarnate` (compatibilidad legacy) | +| `ente-incarnate` | lib | `clone(2) + namespaces + cgroup + rlimits + cpu` | +| `ente-snapshot` | lib | `FractalSnapshot` JSON: checkpoint del grafo Cards | + +## Dependencias + +- `ente-kernel` ← `nix`, `libc`, syscalls Linux puras. +- `ente-incarnate` reusable: shuma (sandboxes) y supervisores no-PID-1. +- Consume `protocol/` (handshake server, brahman-net opcional). + +## Boot path + +1. Kernel pasa control → `ente-zero` arranca como `/sbin/init`. +2. Levanta sockets: `/run/brahman/bus`, `/run/brahman/handshake`. +3. Lee `Card` semilla (`seeds/arje-{minimal,host,prod}.card.json`). +4. Para cada `genesis` child Card: `incarnate(card)` → spawn aislado. +5. Reap loop atiende SIGCHLD; bus loop atiende anuncios/invokes. + +## Estado + +Funciona bare metal + QEMU + initramfs (ver `docs/arje-boot.md`). LOC +~2.2K en init core. Pendiente: cobertura de tests sobre snapshot +restore en escenarios con stale fds. diff --git a/crates/shared/ente-incarnate/Cargo.toml b/crates/init/ente-incarnate/Cargo.toml similarity index 91% rename from crates/shared/ente-incarnate/Cargo.toml rename to crates/init/ente-incarnate/Cargo.toml index 3e2a00c..1395616 100644 --- a/crates/shared/ente-incarnate/Cargo.toml +++ b/crates/init/ente-incarnate/Cargo.toml @@ -9,7 +9,7 @@ publish.workspace = true description = "Rutina extraída del Init para encarnar Cards en procesos aislados (clone+ns+cgroup+rlimits). Reusable por cualquier supervisor — no implica ser PID 1." [dependencies] -brahman-card = { path = "../../core/brahman-card" } +brahman-card = { path = "../../protocol/brahman-card" } nix = { workspace = true } libc = { workspace = true } anyhow = { workspace = true } diff --git a/crates/shared/ente-incarnate/src/caps.rs b/crates/init/ente-incarnate/src/caps.rs similarity index 100% rename from crates/shared/ente-incarnate/src/caps.rs rename to crates/init/ente-incarnate/src/caps.rs diff --git a/crates/shared/ente-incarnate/src/cgroup.rs b/crates/init/ente-incarnate/src/cgroup.rs similarity index 96% rename from crates/shared/ente-incarnate/src/cgroup.rs rename to crates/init/ente-incarnate/src/cgroup.rs index 59e2680..312bee8 100644 --- a/crates/shared/ente-incarnate/src/cgroup.rs +++ b/crates/init/ente-incarnate/src/cgroup.rs @@ -110,7 +110,7 @@ mod tests { #[test] fn relative_path_prefixed() { - let r = resolve_cgroup_path("shipote/ws-1"); - assert!(r.ends_with("/shipote/ws-1") || r == "/shipote/ws-1"); + let r = resolve_cgroup_path("shuma/ws-1"); + assert!(r.ends_with("/shuma/ws-1") || r == "/shuma/ws-1"); } } diff --git a/crates/shared/ente-incarnate/src/child.rs b/crates/init/ente-incarnate/src/child.rs similarity index 100% rename from crates/shared/ente-incarnate/src/child.rs rename to crates/init/ente-incarnate/src/child.rs diff --git a/crates/shared/ente-incarnate/src/env.rs b/crates/init/ente-incarnate/src/env.rs similarity index 100% rename from crates/shared/ente-incarnate/src/env.rs rename to crates/init/ente-incarnate/src/env.rs diff --git a/crates/shared/ente-incarnate/src/error.rs b/crates/init/ente-incarnate/src/error.rs similarity index 100% rename from crates/shared/ente-incarnate/src/error.rs rename to crates/init/ente-incarnate/src/error.rs diff --git a/crates/shared/ente-incarnate/src/lib.rs b/crates/init/ente-incarnate/src/lib.rs similarity index 98% rename from crates/shared/ente-incarnate/src/lib.rs rename to crates/init/ente-incarnate/src/lib.rs index bcdb8b7..3d68cea 100644 --- a/crates/shared/ente-incarnate/src/lib.rs +++ b/crates/init/ente-incarnate/src/lib.rs @@ -3,7 +3,7 @@ //! //! El núcleo histórico vivía en `ente-soma` con globals dependientes de PID 1. //! Este crate elimina esos globals: se construye un [`Incarnator`] por -//! supervisor (Init, shipote, etc.), cada uno con su propio bus socket y su +//! supervisor (Init, shuma, etc.), cada uno con su propio bus socket y su //! propia política de capacidades. //! //! ## Limitaciones que NO desaparecen al extraer @@ -48,7 +48,7 @@ use std::os::fd::RawFd; /// namespaced) o de dejar que `std::process::Command` los absorba (path /// plain). **No los cierres en el caller** — habría doble-close. /// -/// Útil para conectar pipes entre procesos del pipeline de shipote sin +/// Útil para conectar pipes entre procesos del pipeline de shuma sin /// romper la regla async-signal-safe del callback de clone(2). #[derive(Debug, Clone, Copy, Default)] pub struct ChildStdio { @@ -124,7 +124,7 @@ impl Incarnator { &self.cfg } - /// Valida una Card sin ejecutar nada. Útil para que el caller (shipote, + /// Valida una Card sin ejecutar nada. Útil para que el caller (shuma, /// admin, tests) sepa de antemano si va a poder encarnar tal cual o si /// va a tener que aflojar el SomaSpec. pub fn dry_run(&self, card: &Card) -> ValidationReport { @@ -323,7 +323,7 @@ mod tests { let card = make_card( Payload::Native { exec: "/bin/echo".into(), - argv: vec!["shipote-stdio".into()], + argv: vec!["shuma-stdio".into()], envp: vec![], }, NamespaceSet::default(), @@ -353,7 +353,7 @@ mod tests { let n = read(r_raw, &mut buf).expect("read"); assert!(n > 0); let s = std::str::from_utf8(&buf[..n]).unwrap(); - assert!(s.contains("shipote-stdio"), "got: {s:?}"); + assert!(s.contains("shuma-stdio"), "got: {s:?}"); // r se cierra al drop del OwnedFd. } diff --git a/crates/shared/ente-incarnate/src/namespaced.rs b/crates/init/ente-incarnate/src/namespaced.rs similarity index 100% rename from crates/shared/ente-incarnate/src/namespaced.rs rename to crates/init/ente-incarnate/src/namespaced.rs diff --git a/crates/shared/ente-incarnate/src/plain.rs b/crates/init/ente-incarnate/src/plain.rs similarity index 100% rename from crates/shared/ente-incarnate/src/plain.rs rename to crates/init/ente-incarnate/src/plain.rs diff --git a/crates/shared/ente-incarnate/src/pre_exec.rs b/crates/init/ente-incarnate/src/pre_exec.rs similarity index 100% rename from crates/shared/ente-incarnate/src/pre_exec.rs rename to crates/init/ente-incarnate/src/pre_exec.rs diff --git a/crates/core/ente-kernel/Cargo.toml b/crates/init/ente-kernel/Cargo.toml similarity index 85% rename from crates/core/ente-kernel/Cargo.toml rename to crates/init/ente-kernel/Cargo.toml index 0edbc9a..2ef77f8 100644 --- a/crates/core/ente-kernel/Cargo.toml +++ b/crates/init/ente-kernel/Cargo.toml @@ -6,7 +6,7 @@ license.workspace = true publish.workspace = true [dependencies] -ente-card = { path = "../ente-card" } +ente-card = { path = "../../protocol/ente-card" } nix = { workspace = true } libc = { workspace = true } tokio = { workspace = true } diff --git a/crates/core/ente-kernel/src/lib.rs b/crates/init/ente-kernel/src/lib.rs similarity index 100% rename from crates/core/ente-kernel/src/lib.rs rename to crates/init/ente-kernel/src/lib.rs diff --git a/crates/core/ente-kernel/src/sigchld.rs b/crates/init/ente-kernel/src/sigchld.rs similarity index 100% rename from crates/core/ente-kernel/src/sigchld.rs rename to crates/init/ente-kernel/src/sigchld.rs diff --git a/crates/core/ente-kernel/src/surface.rs b/crates/init/ente-kernel/src/surface.rs similarity index 100% rename from crates/core/ente-kernel/src/surface.rs rename to crates/init/ente-kernel/src/surface.rs diff --git a/crates/core/ente-kernel/src/uevent.rs b/crates/init/ente-kernel/src/uevent.rs similarity index 100% rename from crates/core/ente-kernel/src/uevent.rs rename to crates/init/ente-kernel/src/uevent.rs diff --git a/crates/core/ente-snapshot/Cargo.toml b/crates/init/ente-snapshot/Cargo.toml similarity index 84% rename from crates/core/ente-snapshot/Cargo.toml rename to crates/init/ente-snapshot/Cargo.toml index 19d36b0..38ec4dd 100644 --- a/crates/core/ente-snapshot/Cargo.toml +++ b/crates/init/ente-snapshot/Cargo.toml @@ -6,7 +6,7 @@ license.workspace = true publish.workspace = true [dependencies] -ente-card = { path = "../ente-card" } +ente-card = { path = "../../protocol/ente-card" } serde = { workspace = true } serde_json = { workspace = true } ulid = { workspace = true } diff --git a/crates/core/ente-snapshot/src/lib.rs b/crates/init/ente-snapshot/src/lib.rs similarity index 100% rename from crates/core/ente-snapshot/src/lib.rs rename to crates/init/ente-snapshot/src/lib.rs diff --git a/crates/core/ente-soma/Cargo.toml b/crates/init/ente-soma/Cargo.toml similarity index 72% rename from crates/core/ente-soma/Cargo.toml rename to crates/init/ente-soma/Cargo.toml index 78d24a5..dba4f57 100644 --- a/crates/core/ente-soma/Cargo.toml +++ b/crates/init/ente-soma/Cargo.toml @@ -7,9 +7,9 @@ publish.workspace = true description = "Wrapper histórico sobre ente-incarnate para mantener la API set_bus_sock+incarnate que usa ente-zero. Toda la lógica vive en ente-incarnate." [dependencies] -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } -ente-incarnate = { path = "../../shared/ente-incarnate" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } +ente-incarnate = { path = "../ente-incarnate" } nix = { workspace = true } anyhow = { workspace = true } tracing = { workspace = true } diff --git a/crates/core/ente-soma/src/lib.rs b/crates/init/ente-soma/src/lib.rs similarity index 95% rename from crates/core/ente-soma/src/lib.rs rename to crates/init/ente-soma/src/lib.rs index c94f0d3..e66b32a 100644 --- a/crates/core/ente-soma/src/lib.rs +++ b/crates/init/ente-soma/src/lib.rs @@ -1,7 +1,7 @@ //! `ente-soma` — wrapper histórico sobre [`ente_incarnate`]. //! //! La rutina de namespacing fue extraída a `ente-incarnate` para que -//! shipote, exploradores y cualquier supervisor no-PID-1 puedan reusarla. +//! shuma, exploradores y cualquier supervisor no-PID-1 puedan reusarla. //! Este crate sobrevive como compat para `ente-zero` y otros que importan //! `ente_soma::{set_bus_sock, incarnate}`. //! diff --git a/crates/core/ente-zero/Cargo.toml b/crates/init/ente-zero/Cargo.toml similarity index 59% rename from crates/core/ente-zero/Cargo.toml rename to crates/init/ente-zero/Cargo.toml index b6f4d89..0e8e2f3 100644 --- a/crates/core/ente-zero/Cargo.toml +++ b/crates/init/ente-zero/Cargo.toml @@ -11,21 +11,21 @@ path = "src/main.rs" [dependencies] # Lib crates del fractal — orden: contratos → infra → encarnación → orquestación -ente-card = { path = "../ente-card" } -ente-bus = { path = "../ente-bus" } -ente-cas = { path = "../ente-cas" } +ente-card = { path = "../../protocol/ente-card" } +ente-bus = { path = "../../runtime/ente-bus" } +ente-cas = { path = "../../runtime/ente-cas" } ente-kernel = { path = "../ente-kernel" } ente-soma = { path = "../ente-soma" } -ente-wasm = { path = "../ente-wasm" } +ente-wasm = { path = "../../runtime/ente-wasm" } ente-snapshot = { path = "../ente-snapshot" } -ente-brain = { path = "../ente-brain" } -ente-echo = { path = "../ente-echo" } # solo para constantes del demo +ente-brain = { path = "../../runtime/ente-brain" } +ente-echo = { path = "../../runtime/ente-echo" } # solo para constantes del demo # Brahman protocol — handshake para módulos brahman conscientes -brahman-handshake = { path = "../brahman-handshake" } -brahman-broker = { path = "../brahman-broker" } -brahman-admin = { path = "../brahman-admin" } -brahman-net = { path = "../../shared/brahman-net" } +brahman-handshake = { path = "../../protocol/brahman-handshake" } +brahman-broker = { path = "../../protocol/brahman-broker" } +brahman-admin = { path = "../../protocol/brahman-admin" } +brahman-net = { path = "../../protocol/brahman-net" } # Runtime / utilidades de PID 1 serde = { workspace = true } diff --git a/crates/core/ente-zero/src/brain_glue.rs b/crates/init/ente-zero/src/brain_glue.rs similarity index 100% rename from crates/core/ente-zero/src/brain_glue.rs rename to crates/init/ente-zero/src/brain_glue.rs diff --git a/crates/core/ente-zero/src/bus.rs b/crates/init/ente-zero/src/bus.rs similarity index 100% rename from crates/core/ente-zero/src/bus.rs rename to crates/init/ente-zero/src/bus.rs diff --git a/crates/core/ente-zero/src/events.rs b/crates/init/ente-zero/src/events.rs similarity index 100% rename from crates/core/ente-zero/src/events.rs rename to crates/init/ente-zero/src/events.rs diff --git a/crates/core/ente-zero/src/graph/bus_mediator.rs b/crates/init/ente-zero/src/graph/bus_mediator.rs similarity index 100% rename from crates/core/ente-zero/src/graph/bus_mediator.rs rename to crates/init/ente-zero/src/graph/bus_mediator.rs diff --git a/crates/core/ente-zero/src/graph/capabilities.rs b/crates/init/ente-zero/src/graph/capabilities.rs similarity index 100% rename from crates/core/ente-zero/src/graph/capabilities.rs rename to crates/init/ente-zero/src/graph/capabilities.rs diff --git a/crates/core/ente-zero/src/graph/devices.rs b/crates/init/ente-zero/src/graph/devices.rs similarity index 100% rename from crates/core/ente-zero/src/graph/devices.rs rename to crates/init/ente-zero/src/graph/devices.rs diff --git a/crates/core/ente-zero/src/graph/lifecycle.rs b/crates/init/ente-zero/src/graph/lifecycle.rs similarity index 100% rename from crates/core/ente-zero/src/graph/lifecycle.rs rename to crates/init/ente-zero/src/graph/lifecycle.rs diff --git a/crates/core/ente-zero/src/graph/mod.rs b/crates/init/ente-zero/src/graph/mod.rs similarity index 100% rename from crates/core/ente-zero/src/graph/mod.rs rename to crates/init/ente-zero/src/graph/mod.rs diff --git a/crates/core/ente-zero/src/graph/shutdown.rs b/crates/init/ente-zero/src/graph/shutdown.rs similarity index 100% rename from crates/core/ente-zero/src/graph/shutdown.rs rename to crates/init/ente-zero/src/graph/shutdown.rs diff --git a/crates/core/ente-zero/src/graph/topology.rs b/crates/init/ente-zero/src/graph/topology.rs similarity index 100% rename from crates/core/ente-zero/src/graph/topology.rs rename to crates/init/ente-zero/src/graph/topology.rs diff --git a/crates/core/ente-zero/src/keypair_store.rs b/crates/init/ente-zero/src/keypair_store.rs similarity index 100% rename from crates/core/ente-zero/src/keypair_store.rs rename to crates/init/ente-zero/src/keypair_store.rs diff --git a/crates/core/ente-zero/src/main.rs b/crates/init/ente-zero/src/main.rs similarity index 100% rename from crates/core/ente-zero/src/main.rs rename to crates/init/ente-zero/src/main.rs diff --git a/crates/core/ente-zero/src/seed.rs b/crates/init/ente-zero/src/seed.rs similarity index 100% rename from crates/core/ente-zero/src/seed.rs rename to crates/init/ente-zero/src/seed.rs diff --git a/crates/modules/akasha/SDD.md b/crates/modules/akasha/SDD.md new file mode 100644 index 0000000..546c65c --- /dev/null +++ b/crates/modules/akasha/SDD.md @@ -0,0 +1,29 @@ +# modules/akasha/ — Explorador semántico de Mónadas (era nouser) + +**Propósito.** Daemon que descubre y consulta "Mónadas" (unidades +semánticas auto-descriptivas) vía broker brahman. Provee embeddings +locales (mock o real LLM) y un protocolo `Nous` line-delimited. + +## Crates + +| crate | tipo | rol | +| --------------- | ----- | --------------------------------------------------------- | +| `akasha-card` | lib | Definición Card del daemon + capabilities | +| `akasha-core` | bin | Daemon: scanner FS + DB sled + cluster por embedding | +| `akasha-nous` | lib | Protocolo Nous (JSON line-delimited) | +| `akasha-nous-mock` | bin | Proveedor de embeddings deterministas (testing) | +| `akasha-nous-real` | bin | Proveedor con fastembed/ort (LLM real) | + +## Dependencias + +- `akasha-core` ← `protocol/brahman-card`, `protocol/brahman-sidecar`, + `akasha-card`, `akasha-nous`, `shuma-discern`. +- `akasha-nous-real` ← `fastembed` + `ort` (heavy; profile opt-level=1 + en root Cargo.toml). +- Consumido por: `apps/akasha-explorer` (GPUI dashboard). + +## Estado + +LOC 4,395. Pipeline de scan + embed + cluster funcional. Pendiente: +cobertura de tests sobre el cluster engine (k-means actual es naive). +Ver `docs/changelog/akasha.md`. diff --git a/crates/modules/nouser/card/Cargo.toml b/crates/modules/akasha/card/Cargo.toml similarity index 84% rename from crates/modules/nouser/card/Cargo.toml rename to crates/modules/akasha/card/Cargo.toml index 5fdfb49..2af214c 100644 --- a/crates/modules/nouser/card/Cargo.toml +++ b/crates/modules/akasha/card/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nouser-card" +name = "akasha-card" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,7 +9,7 @@ publish.workspace = true description = "Nouser — manifiesto de Mónada (agrupación semántica de archivos). Espejo de brahman-card pero para datos." [dependencies] -brahman-card = { path = "../../../core/brahman-card" } +brahman-card = { path = "../../../protocol/brahman-card" } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/modules/nouser/card/src/lib.rs b/crates/modules/akasha/card/src/lib.rs similarity index 98% rename from crates/modules/nouser/card/src/lib.rs rename to crates/modules/akasha/card/src/lib.rs index d94fd70..c1c435d 100644 --- a/crates/modules/nouser/card/src/lib.rs +++ b/crates/modules/akasha/card/src/lib.rs @@ -1,4 +1,4 @@ -//! `nouser-card` — manifiesto de Mónada. +//! `akasha-card` — manifiesto de Mónada. //! //! Una **Mónada** es una agrupación semántica de archivos: el archivo //! físico no se mueve, pero su pertenencia se modela por un objeto @@ -10,7 +10,7 @@ //! //! Diferencia con `brahman-card::Card`: //! -//! | brahman::Card | nouser::MonadManifest | +//! | brahman::Card | akasha::MonadManifest | //! |-------------------------------------|-------------------------------| //! | Describe una **entidad runtime** | Describe una **agrupación** | //! | Tiene `payload`/`soma`/`supervision`| No tiene proceso detrás | @@ -18,7 +18,7 @@ //! | Fluye por handshake/postcard | Fluye por queries del backend | //! //! Este crate sólo define los tipos. La lógica de scan, cluster, -//! attraction vive en `nouser-core`. +//! attraction vive en `akasha-core`. #![forbid(unsafe_code)] #![warn(rust_2018_idioms)] @@ -71,7 +71,7 @@ pub struct FileEntry { // Lens — la "vista" preferida de una Mónada // ===================================================================== -/// Lente de visualización dominante. La UI (yahweh) elige cómo renderizar +/// Lente de visualización dominante. La UI (nahual) elige cómo renderizar /// los miembros de una Mónada según este hint. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] diff --git a/crates/modules/nouser/card/src/query.rs b/crates/modules/akasha/card/src/query.rs similarity index 96% rename from crates/modules/nouser/card/src/query.rs rename to crates/modules/akasha/card/src/query.rs index 3d0c432..125ba27 100644 --- a/crates/modules/nouser/card/src/query.rs +++ b/crates/modules/akasha/card/src/query.rs @@ -1,12 +1,12 @@ -//! Wire types para consultar al daemon `nouser` por sus Mónadas. +//! Wire types para consultar al daemon `akasha` por sus Mónadas. //! //! El daemon expone un Unix socket (cuyo path se publica en //! `Card.service_socket` y se descubre vía broker MatchEvent). Cada //! conexión es single-shot: una request JSON terminada en `\n`, //! una response JSON terminada en `\n`, cierre. //! -//! Mismo patrón que `nouser-nous` (mock/real ↔ nouser-core), reusado -//! ahora para que la UI (`nouser-explorer`) descubra y consulte al +//! Mismo patrón que `akasha-nous` (mock/real ↔ akasha-core), reusado +//! ahora para que la UI (`akasha-explorer`) descubra y consulte al //! daemon sin hardcodear sockets ni pasar por brahman-admin. //! //! ## Contrato @@ -118,7 +118,7 @@ impl MonadView { /// Error de protocolo retornado en lugar de la response normal. #[derive(Debug, Clone, Serialize, Deserialize, Error)] -#[error("nouser-engine: {error}")] +#[error("akasha-engine: {error}")] pub struct ErrorResponse { pub error: String, } @@ -135,7 +135,7 @@ pub mod transport { pub const SOCKET_ENV: &str = "NOUSER_ENGINE_SOCKET"; /// Nombre por defecto del socket. - pub const SOCKET_NAME: &str = "nouser-engine.sock"; + pub const SOCKET_NAME: &str = "akasha-engine.sock"; /// Ruta canónica al socket del daemon. Honra `NOUSER_ENGINE_SOCKET` /// si está set, sino arma sobre `$XDG_RUNTIME_DIR` (con fallback @@ -154,7 +154,7 @@ pub mod transport { // ===================================================================== // Cliente blocking — vive con los wire types para que un consumer // (UI, CLI, otro módulo) pueda hablar con el daemon importando sólo -// `nouser-card`, sin arrastrar `nouser-core` (notify/walkdir/sled/blake3). +// `akasha-card`, sin arrastrar `akasha-core` (notify/walkdir/sled/blake3). // ===================================================================== /// Cliente síncrono para el query socket del daemon. Sólo Unix (el diff --git a/crates/modules/nouser/core/Cargo.toml b/crates/modules/akasha/core/Cargo.toml similarity index 63% rename from crates/modules/nouser/core/Cargo.toml rename to crates/modules/akasha/core/Cargo.toml index fe18c1a..4b7f88f 100644 --- a/crates/modules/nouser/core/Cargo.toml +++ b/crates/modules/akasha/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nouser-core" +name = "akasha-core" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,12 +9,12 @@ publish.workspace = true description = "Nouser — explorador de Mónadas: scanner, clustering determinista, DB en memoria." [dependencies] -nouser-card = { path = "../card" } -nouser-nous = { path = "../nous" } -shipote-discern = { path = "../../shipote/shipote-discern" } -brahman-card = { path = "../../../core/brahman-card" } -brahman-handshake = { path = "../../../core/brahman-handshake" } -brahman-sidecar = { path = "../../../shared/brahman-sidecar" } +akasha-card = { path = "../card" } +akasha-nous = { path = "../nous" } +shuma-discern = { path = "../../shuma/shuma-discern" } +brahman-card = { path = "../../../protocol/brahman-card" } +brahman-handshake = { path = "../../../protocol/brahman-handshake" } +brahman-sidecar = { path = "../../../protocol/brahman-sidecar" } blake3 = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -29,5 +29,5 @@ notify = { workspace = true } tempfile = { workspace = true } [[bin]] -name = "nouser" +name = "akasha" path = "src/bin/nouser.rs" diff --git a/crates/modules/nouser/core/src/bin/nouser.rs b/crates/modules/akasha/core/src/bin/nouser.rs similarity index 93% rename from crates/modules/nouser/core/src/bin/nouser.rs rename to crates/modules/akasha/core/src/bin/nouser.rs index 55b13b9..1209993 100644 --- a/crates/modules/nouser/core/src/bin/nouser.rs +++ b/crates/modules/akasha/core/src/bin/nouser.rs @@ -1,4 +1,4 @@ -//! `nouser` CLI — explorador de Mónadas. +//! `akasha` CLI — explorador de Mónadas. //! //! Subcomandos: //! @@ -13,14 +13,14 @@ use std::path::PathBuf; use std::process::ExitCode; -use nouser_core::{ +use akasha_core::{ cluster, db, embed, scanner::{self, ScanConfig}, }; fn main() -> ExitCode { let args: Vec = std::env::args().collect(); - let prog = args.first().cloned().unwrap_or_else(|| "nouser".into()); + let prog = args.first().cloned().unwrap_or_else(|| "akasha".into()); let sub = match args.get(1).map(String::as_str) { Some(s) => s, None => { @@ -41,7 +41,7 @@ fn main() -> ExitCode { return ExitCode::SUCCESS; } other => { - eprintln!("nouser: comando desconocido '{other}'"); + eprintln!("akasha: comando desconocido '{other}'"); print_usage(&prog); return ExitCode::from(2); } @@ -50,7 +50,7 @@ fn main() -> ExitCode { match result { Ok(()) => ExitCode::SUCCESS, Err(e) => { - eprintln!("nouser: {e}"); + eprintln!("akasha: {e}"); ExitCode::from(1) } } @@ -181,7 +181,7 @@ fn cmd_daemon(args: &[String]) -> Cmd { // 1. Decidir el path del query socket ANTES de armar el engine // Card (porque viaja como service_socket en la Card). - let query_socket = nouser_card::query::transport::default_socket_path(); + let query_socket = akasha_card::query::transport::default_socket_path(); // 2. Engine como Ente. Declara service_socket + flow.output para // que el broker pueda emitir MatchEvent::Available a consumers @@ -190,7 +190,7 @@ fn cmd_daemon(args: &[String]) -> Cmd { let engine_id = engine_card.id; let engine_label = engine_card.label.clone(); eprintln!( - "nouser daemon: publicando engine '{}' (kind=Ente, id={}, socket={})", + "akasha daemon: publicando engine '{}' (kind=Ente, id={}, socket={})", engine_label, engine_id, query_socket.display() @@ -227,7 +227,7 @@ fn cmd_daemon(args: &[String]) -> Cmd { hydrated += 1; } eprintln!( - "nouser daemon: hidratadas {} mónadas previas{} en O(1)", + "akasha daemon: hidratadas {} mónadas previas{} en O(1)", hydrated, if skipped_model > 0 { format!(" ({} dropeadas por centroid_model distinto)", skipped_model) @@ -245,7 +245,7 @@ fn cmd_daemon(args: &[String]) -> Cmd { let monads = cluster::by_directory_hydrated(&files, min_files(), Some(&db)); let scanned_count = monads.len(); eprintln!( - "nouser daemon: re-scan {} archivos en {} → {} mónadas", + "akasha daemon: re-scan {} archivos en {} → {} mónadas", n_files, dir.display(), scanned_count @@ -276,7 +276,7 @@ fn cmd_daemon(args: &[String]) -> Cmd { db.replace_monads(monads); eprintln!( - "nouser daemon: 1 ente + {} mónadas vivas ({} nuevas vs hidratación)", + "akasha daemon: 1 ente + {} mónadas vivas ({} nuevas vs hidratación)", scanned_count, newly_spawned ); @@ -285,8 +285,8 @@ fn cmd_daemon(args: &[String]) -> Cmd { // Si el bind falla, seguimos sin él — la UI degrada a "no // alcanzable" pero el daemon sigue procesando cambios. let db_shared = std::sync::Arc::new(std::sync::Mutex::new(db)); - let _query_listener = match nouser_core::engine_socket::spawn_listener( - nouser_core::engine_socket::ListenerConfig { + let _query_listener = match akasha_core::engine_socket::spawn_listener( + akasha_core::engine_socket::ListenerConfig { socket_path: query_socket.clone(), engine_id, engine_label: engine_label.clone(), @@ -296,14 +296,14 @@ fn cmd_daemon(args: &[String]) -> Cmd { ) { Ok(h) => { eprintln!( - "nouser daemon: query socket activo en {} (proto: nouser_card::query)", + "akasha daemon: query socket activo en {} (proto: akasha_card::query)", query_socket.display() ); Some(h) } Err(e) => { eprintln!( - "nouser daemon: query socket NO disponible ({e}) — explorer no podrá consultar" + "akasha daemon: query socket NO disponible ({e}) — explorer no podrá consultar" ); None } @@ -322,14 +322,14 @@ fn cmd_daemon(args: &[String]) -> Cmd { ) { Ok(w) => { eprintln!( - "nouser daemon: watcher activo en {} (debounce 150ms, re-publish on) — Ctrl-C para terminar.", + "akasha daemon: watcher activo en {} (debounce 150ms, re-publish on) — Ctrl-C para terminar.", dir.display() ); Some(w) } Err(e) => { eprintln!( - "nouser daemon: watcher deshabilitado ({e}) — Ctrl-C para terminar." + "akasha daemon: watcher deshabilitado ({e}) — Ctrl-C para terminar." ); None } @@ -385,7 +385,7 @@ fn spawn_fs_watcher( // Dispatcher: notify → filtro → canal de paths. let dispatch_dir = dir.clone(); std::thread::Builder::new() - .name("nouser-watcher-dispatch".into()) + .name("akasha-watcher-dispatch".into()) .spawn(move || { for res in notify_rx { let event = match res { @@ -416,7 +416,7 @@ fn spawn_fs_watcher( // Coordinator: debounce + batch dispatch. let coord_dir = dir; std::thread::Builder::new() - .name("nouser-watcher-coord".into()) + .name("akasha-watcher-coord".into()) .spawn(move || { let debounce = std::time::Duration::from_millis(WATCHER_DEBOUNCE_MS); let mut pending: std::collections::HashMap< @@ -494,7 +494,7 @@ fn process_change_batch( } }; - let prior_monads: Vec = db_lock.monads().cloned().collect(); + let prior_monads: Vec = db_lock.monads().cloned().collect(); let prior_ref: &db::MonadDb = &db_lock; let monads = cluster::by_directory_hydrated(&files, min_files(), Some(prior_ref)); @@ -601,8 +601,8 @@ fn cmd_attract(args: &[String]) -> Cmd { .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) .map(|d| d.as_millis() as u64) .unwrap_or(0); - let target = nouser_card::FileEntry { - id: nouser_card::FileId::from(nouser_card::ulid::Ulid::new()), + let target = akasha_card::FileEntry { + id: akasha_card::FileId::from(akasha_card::ulid::Ulid::new()), path: file_path.clone(), content_hash: None, size: metadata.len(), @@ -630,7 +630,7 @@ fn cmd_attract(args: &[String]) -> Cmd { // Filtramos Mónadas cuyo centroid_model NO matchee. Mezclar // 32-d con 384-d daría scores sin sentido (diferente semántica // y cosine no compara cross-modelo). - let mut ranked: Vec<(&nouser_card::MonadManifest, f32)> = db + let mut ranked: Vec<(&akasha_card::MonadManifest, f32)> = db .monads() .filter(|m| !m.centroid.is_empty()) .filter(|m| match &m.centroid_model { @@ -700,7 +700,7 @@ fn cmd_attract(args: &[String]) -> Cmd { /// Devuelve `(embedding, model_id)` — el caller necesita ambos para /// comparar contra centroides taggeados con su mismo `centroid_model`. fn remote_embed( - file: &nouser_card::FileEntry, + file: &akasha_card::FileEntry, ) -> Result<(Vec, String), Box> { if let Ok(explicit) = std::env::var("NOUSER_NOUS_SOCKET") { let sock = std::path::PathBuf::from(explicit); @@ -708,9 +708,9 @@ fn remote_embed( } let consumer = brahman_sidecar::build_consumer_card( - "nouser.attract-cli", - nouser_nous::FLOW_EMBED_RESULT, - nouser_nous::FLOW_TYPE_NAME, + "akasha.attract-cli", + akasha_nous::FLOW_EMBED_RESULT, + akasha_nous::FLOW_TYPE_NAME, ); let producer_sock = brahman_sidecar::await_provider_blocking( consumer, @@ -719,12 +719,12 @@ fn remote_embed( embed_via(&producer_sock, file) } -/// RPC blocking contra un socket nouser-nous concreto. Devuelve +/// RPC blocking contra un socket akasha-nous concreto. Devuelve /// `(embedding, model_id)` — el `model_id` viaja en la response y /// permite al caller saber qué centroides son comparables. fn embed_via( sock_path: &std::path::Path, - file: &nouser_card::FileEntry, + file: &akasha_card::FileEntry, ) -> Result<(Vec, String), Box> { use std::io::{BufRead, BufReader, Write}; use std::os::unix::net::UnixStream; @@ -734,9 +734,9 @@ fn embed_via( } let mut stream = UnixStream::connect(sock_path)?; - let req = nouser_nous::EmbedRequest { - kind: nouser_nous::RequestKind::EmbedFile, - payload: serde_json::to_value(nouser_nous::EmbedFilePayload { + let req = akasha_nous::EmbedRequest { + kind: akasha_nous::RequestKind::EmbedFile, + payload: serde_json::to_value(akasha_nous::EmbedFilePayload { path: file.path.display().to_string(), extension: file.extension.clone(), size: file.size, @@ -752,14 +752,14 @@ fn embed_via( let mut response = String::new(); reader.read_line(&mut response)?; if response.is_empty() { - return Err("nouser-nous cerró sin respuesta".into()); + return Err("akasha-nous cerró sin respuesta".into()); } - if let Ok(resp) = serde_json::from_str::(&response) { + if let Ok(resp) = serde_json::from_str::(&response) { return Ok((resp.embedding, resp.model)); } - let err: nouser_nous::ErrorResponse = serde_json::from_str(&response)?; - Err(format!("nouser-nous: {}", err.error).into()) + let err: akasha_nous::ErrorResponse = serde_json::from_str(&response)?; + Err(format!("akasha-nous: {}", err.error).into()) } /// Card del propio engine (kind=Ente). Es el "ser" que produce y @@ -771,7 +771,7 @@ fn embed_via( /// brahman-admin. fn build_engine_card(service_socket: std::path::PathBuf) -> brahman_card::Card { use brahman_card::{Card, CardKind, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef}; - use nouser_card::query::{FLOW_MONAD_LIST, FLOW_TYPE_NAME}; + use akasha_card::query::{FLOW_MONAD_LIST, FLOW_TYPE_NAME}; Card { payload: Payload::Virtual, diff --git a/crates/modules/nouser/core/src/cluster.rs b/crates/modules/akasha/core/src/cluster.rs similarity index 97% rename from crates/modules/nouser/core/src/cluster.rs rename to crates/modules/akasha/core/src/cluster.rs index 5ea49c4..2aad513 100644 --- a/crates/modules/nouser/core/src/cluster.rs +++ b/crates/modules/akasha/core/src/cluster.rs @@ -15,7 +15,7 @@ use std::collections::BTreeMap; use std::path::PathBuf; -use nouser_card::{FileEntry, Lens, MonadManifest}; +use akasha_card::{FileEntry, Lens, MonadManifest}; use crate::embed; @@ -153,7 +153,7 @@ fn top_extensions(files: &[&FileEntry], n: usize) -> Vec { } /// Elige el lente dominante según la extensión más frecuente, con -/// fallback a `shipote-discern` sobre el head del archivo más +/// fallback a `shuma-discern` sobre el head del archivo más /// representativo cuando la extensión no da hint claro (Lens::Grid). fn pick_lens(files: &[&FileEntry]) -> Lens { let dominant = top_extensions(files, 1).into_iter().next(); @@ -170,7 +170,7 @@ fn pick_lens(files: &[&FileEntry]) -> Lens { if by_ext != Lens::Grid { return by_ext; } - // Fallback: samplear el primer archivo del grupo con shipote-discern. + // Fallback: samplear el primer archivo del grupo con shuma-discern. // Sólo si tiene path real (FileEntry con path absoluto/relativo). if let Some(first) = files.first() { if let Some(lens) = discern_lens(&first.path) { @@ -186,11 +186,11 @@ fn discern_lens(path: &std::path::Path) -> Option { let mut f = std::fs::File::open(path).ok()?; let n = f.read(&mut buf).ok()?; buf.truncate(n); - let pipeline = shipote_discern::DiscernPipeline::default_pipeline(); + let pipeline = shuma_discern::DiscernPipeline::default_pipeline(); let path_str = path.to_str(); let d = pipeline.discern( &buf, - &shipote_discern::Hint { + &shuma_discern::Hint { path: path_str, size_total: None, }, @@ -236,7 +236,7 @@ fn shannon_entropy_normalized(files: &[&FileEntry]) -> f32 { #[cfg(test)] mod tests { use super::*; - use nouser_card::FileId; + use akasha_card::FileId; use std::path::PathBuf; use ulid::Ulid; diff --git a/crates/modules/nouser/core/src/db.rs b/crates/modules/akasha/core/src/db.rs similarity index 99% rename from crates/modules/nouser/core/src/db.rs rename to crates/modules/akasha/core/src/db.rs index f1997aa..0045a86 100644 --- a/crates/modules/nouser/core/src/db.rs +++ b/crates/modules/akasha/core/src/db.rs @@ -12,7 +12,7 @@ use std::collections::BTreeMap; use std::path::Path; -use nouser_card::{FileEntry, FileId, MonadId, MonadManifest}; +use akasha_card::{FileEntry, FileId, MonadId, MonadManifest}; use thiserror::Error; #[derive(Debug, Error)] @@ -188,7 +188,7 @@ fn decode_key(k: &[u8]) -> Result { #[cfg(test)] mod tests { use super::*; - use nouser_card::Lens; + use akasha_card::Lens; use ulid::Ulid; fn mk_file(path: &str) -> FileEntry { diff --git a/crates/modules/nouser/core/src/embed.rs b/crates/modules/akasha/core/src/embed.rs similarity index 98% rename from crates/modules/nouser/core/src/embed.rs rename to crates/modules/akasha/core/src/embed.rs index 19bfb13..245ebbf 100644 --- a/crates/modules/nouser/core/src/embed.rs +++ b/crates/modules/akasha/core/src/embed.rs @@ -27,7 +27,7 @@ //! - Dirs distintos + misma ext → similitud ~ 0.5. //! - Sin parecido → similitud < 0.3. -use nouser_card::{FileEntry, MonadId, MonadManifest}; +use akasha_card::{FileEntry, MonadId, MonadManifest}; /// Dimensión del vector embedding. pub const EMBED_DIM: usize = 32; @@ -37,7 +37,7 @@ pub const EMBED_DIM: usize = 32; /// este string contra el suyo antes de hacer cosine similarity. /// Mezclar centroides de distinto MODEL_ID corrompe scores /// silenciosamente (dimensiones distintas, semántica distinta). -pub const MODEL_ID: &str = "nouser-pseudo-32d"; +pub const MODEL_ID: &str = "akasha-pseudo-32d"; /// Computa el embedding de un archivo. Determinístico: misma input /// → mismo vector. El vector queda L2-normalizado. @@ -185,7 +185,7 @@ pub const DEFAULT_ATTRACTION_THRESHOLD: f32 = 0.7; #[cfg(test)] mod tests { use super::*; - use nouser_card::FileId; + use akasha_card::FileId; use std::path::PathBuf; use ulid::Ulid; diff --git a/crates/modules/nouser/core/src/engine_socket.rs b/crates/modules/akasha/core/src/engine_socket.rs similarity index 91% rename from crates/modules/nouser/core/src/engine_socket.rs rename to crates/modules/akasha/core/src/engine_socket.rs index 495639e..2b81de1 100644 --- a/crates/modules/nouser/core/src/engine_socket.rs +++ b/crates/modules/akasha/core/src/engine_socket.rs @@ -1,13 +1,13 @@ -//! Listener Unix-socket que sirve [`nouser_card::query::QueryRequest`]. +//! Listener Unix-socket que sirve [`akasha_card::query::QueryRequest`]. //! -//! El daemon `nouser` lo monta para que cualquier consumer (UI, CLI, +//! El daemon `akasha` lo monta para que cualquier consumer (UI, CLI, //! otro módulo) pueda preguntarle por sus Mónadas sin pasar por //! brahman-admin. El path del socket viaja en el `Card.service_socket` //! del engine; el broker brahman lo enseña vía MatchEvent::Available //! cuando un consumer declara `flow.input = monad-list:json`. //! //! Wire: line-delimited JSON, single-shot por conexión. Mismo patrón -//! que `nouser-nous` (mock/real ↔ nouser-core), reutilizado. +//! que `akasha-nous` (mock/real ↔ akasha-core), reutilizado. //! //! Threading: un thread dedicado, blocking I/O. No vale la pena traer //! tokio acá — la frecuencia esperada es muy baja (UI poll cada 2s) @@ -18,10 +18,10 @@ use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use nouser_card::query::{ +use akasha_card::query::{ EngineInfo, ErrorResponse, ListMonadsResponse, MonadView, QueryRequest, }; -use nouser_card::ulid::Ulid; +use akasha_card::ulid::Ulid; use crate::db::MonadDb; @@ -54,7 +54,7 @@ pub fn spawn_listener( let listener = UnixListener::bind(&config.socket_path)?; let handle = std::thread::Builder::new() - .name("nouser-engine-listener".into()) + .name("akasha-engine-listener".into()) .spawn(move || { for conn in listener.incoming() { match conn { @@ -126,17 +126,17 @@ fn encode_error(msg: String) -> String { serde_json::to_string(&err).unwrap_or_else(|_| "{\"error\":\"encode\"}".into()) } -// El cliente blocking vive en `nouser_card::query::client` — junto a +// El cliente blocking vive en `akasha_card::query::client` — junto a // los wire types — para que un consumer pueda hablar con el daemon -// importando sólo `nouser-card`, sin arrastrar el peso de -// `nouser-core` (scanner / db / sled / notify / walkdir / blake3). +// importando sólo `akasha-card`, sin arrastrar el peso de +// `akasha-core` (scanner / db / sled / notify / walkdir / blake3). #[cfg(test)] mod tests { use super::*; use crate::db::MonadDb; - use nouser_card::query::client as query_client; - use nouser_card::MonadManifest; + use akasha_card::query::client as query_client; + use akasha_card::MonadManifest; use std::time::Duration; fn fresh_socket_path(name: &str) -> PathBuf { @@ -147,7 +147,7 @@ mod tests { #[test] fn list_monads_roundtrip_empty() { - let socket = fresh_socket_path("nouser-engine-test"); + let socket = fresh_socket_path("akasha-engine-test"); let db = Arc::new(Mutex::new(MonadDb::new())); let engine_id = Ulid::new(); let _h = spawn_listener( @@ -178,7 +178,7 @@ mod tests { #[test] fn list_monads_returns_views() { - let socket = fresh_socket_path("nouser-engine-test-views"); + let socket = fresh_socket_path("akasha-engine-test-views"); let db = Arc::new(Mutex::new(MonadDb::new())); let m1 = MonadManifest::new("alpha"); let m2 = MonadManifest::new("beta"); @@ -209,7 +209,7 @@ mod tests { #[test] fn invalid_request_returns_error_response() { - let socket = fresh_socket_path("nouser-engine-test-bad"); + let socket = fresh_socket_path("akasha-engine-test-bad"); let db = Arc::new(Mutex::new(MonadDb::new())); let _h = spawn_listener( ListenerConfig { diff --git a/crates/modules/nouser/core/src/lib.rs b/crates/modules/akasha/core/src/lib.rs similarity index 93% rename from crates/modules/nouser/core/src/lib.rs rename to crates/modules/akasha/core/src/lib.rs index 0bf2e19..8572c84 100644 --- a/crates/modules/nouser/core/src/lib.rs +++ b/crates/modules/akasha/core/src/lib.rs @@ -1,4 +1,4 @@ -//! `nouser-core` — el explorador de Mónadas. +//! `akasha-core` — el explorador de Mónadas. //! //! Implementa la pipeline determinista descrita en el diseño de Kairos: //! @@ -31,4 +31,4 @@ pub mod embed; pub mod engine_socket; pub mod scanner; -pub use nouser_card::*; +pub use akasha_card::*; diff --git a/crates/modules/nouser/core/src/scanner.rs b/crates/modules/akasha/core/src/scanner.rs similarity index 99% rename from crates/modules/nouser/core/src/scanner.rs rename to crates/modules/akasha/core/src/scanner.rs index ed4581e..3c12cf5 100644 --- a/crates/modules/nouser/core/src/scanner.rs +++ b/crates/modules/akasha/core/src/scanner.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; -use nouser_card::{FileEntry, FileId}; +use akasha_card::{FileEntry, FileId}; use thiserror::Error; use ulid::Ulid; use walkdir::WalkDir; diff --git a/crates/modules/nouser/nous-mock/Cargo.toml b/crates/modules/akasha/nous-mock/Cargo.toml similarity index 66% rename from crates/modules/nouser/nous-mock/Cargo.toml rename to crates/modules/akasha/nous-mock/Cargo.toml index e9b7518..ed40eb8 100644 --- a/crates/modules/nouser/nous-mock/Cargo.toml +++ b/crates/modules/akasha/nous-mock/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nouser-nous-mock" +name = "akasha-nous-mock" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -9,11 +9,11 @@ publish.workspace = true description = "Nouser — Nous mock determinístico: implementa el contrato nouser-nous con pseudo-embeddings de Phase C. Stand-in para tests y para `BRAHMAN_BROKER_CONTEXT=test`." [dependencies] -brahman-card = { path = "../../../core/brahman-card" } -brahman-sidecar = { path = "../../../shared/brahman-sidecar" } -nouser-card = { path = "../card" } -nouser-core = { path = "../core" } -nouser-nous = { path = "../nous" } +brahman-card = { path = "../../../protocol/brahman-card" } +brahman-sidecar = { path = "../../../protocol/brahman-sidecar" } +akasha-card = { path = "../card" } +akasha-core = { path = "../core" } +akasha-nous = { path = "../nous" } serde_json = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } @@ -21,5 +21,5 @@ tracing-subscriber = { workspace = true } ulid = { workspace = true } [[bin]] -name = "nouser-nous-mock" +name = "akasha-nous-mock" path = "src/main.rs" diff --git a/crates/modules/nouser/nous-mock/src/main.rs b/crates/modules/akasha/nous-mock/src/main.rs similarity index 92% rename from crates/modules/nouser/nous-mock/src/main.rs rename to crates/modules/akasha/nous-mock/src/main.rs index 4d1e3a5..6d2d2ed 100644 --- a/crates/modules/nouser/nous-mock/src/main.rs +++ b/crates/modules/akasha/nous-mock/src/main.rs @@ -1,7 +1,7 @@ -//! `nouser-nous-mock` — proveedor de embeddings determinista (sin LLM). +//! `akasha-nous-mock` — proveedor de embeddings determinista (sin LLM). //! -//! Implementa el contrato `nouser-nous` usando los pseudo-embeddings -//! de Phase C (`nouser_core::embed`). Sirve como: +//! Implementa el contrato `akasha-nous` usando los pseudo-embeddings +//! de Phase C (`akasha_core::embed`). Sirve como: //! //! - **Mock para tests**: en `BRAHMAN_BROKER_CONTEXT=test`, el //! `priority_offset` per-contexto declarado en su Card lo prioriza @@ -16,7 +16,7 @@ //! `priority_contexts.test = { priority_offset: +1 }` lo prioriza //! cuando el broker corre bajo contexto test. //! 2. Bind del Unix socket en `$NOUSER_NOUS_SOCKET` (default -//! `$XDG_RUNTIME_DIR/nouser-nous.sock`). +//! `$XDG_RUNTIME_DIR/akasha-nous.sock`). //! 3. Loop: accept → read line JSON → process → write line JSON → close. //! 4. Cada request se loggea (info) — útil para verificar que el //! consumidor está usando este proveedor. @@ -29,9 +29,9 @@ use brahman_card::{ ulid::Ulid, Card, CardKind, ContextBias, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef, }; -use nouser_card::FileEntry; -use nouser_core::embed; -use nouser_nous::{ +use akasha_card::FileEntry; +use akasha_core::embed; +use akasha_nous::{ transport, EmbedFilePayload, EmbedRequest, EmbedResponse, EmbedTextPayload, ErrorResponse, PingResponse, RequestKind, FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME, }; @@ -39,11 +39,11 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{UnixListener, UnixStream}; use tracing::{info, warn}; -/// El mock implementa el MISMO algoritmo que `nouser_core::embed`, +/// El mock implementa el MISMO algoritmo que `akasha_core::embed`, /// así que reportamos el mismo `MODEL_ID` que él. De otro modo el /// consumer filtraría las Mónadas como "modelo distinto" y los /// scores quedarían vacíos. -const MODEL_ID: &str = nouser_core::embed::MODEL_ID; +const MODEL_ID: &str = akasha_core::embed::MODEL_ID; #[tokio::main(flavor = "current_thread")] async fn main() -> std::io::Result<()> { @@ -60,7 +60,7 @@ async fn main() -> std::io::Result<()> { std::fs::create_dir_all(parent)?; } let listener = UnixListener::bind(&sock_path)?; - info!(socket = %sock_path.display(), "nouser-nous-mock escuchando"); + info!(socket = %sock_path.display(), "akasha-nous-mock escuchando"); // 2. Sidecar al brahman-init con la Card que declara el socket. let card = build_card(sock_path.clone()); @@ -107,7 +107,7 @@ fn build_card(service_socket: std::path::PathBuf) -> Card { Card { schema_version: brahman_card::CARD_SCHEMA_VERSION, id: Ulid::new(), - label: "nouser.nous_mock".into(), + label: "akasha.nous_mock".into(), payload: Payload::Virtual, supervision: Supervision::Delegate, lifecycle: Lifecycle::Daemon, @@ -178,7 +178,7 @@ fn handle_embed_file(payload: serde_json::Value, started: Instant) -> Result Result PathBuf { .map(|h| PathBuf::from(h).join(".cache")) }) .unwrap_or_else(std::env::temp_dir); - base.join("brahman").join("nouser-nous-real-embed-cache.sled") + base.join("brahman").join("akasha-nous-real-embed-cache.sled") } fn build_key(file_sha: &[u8; 32], model_id: &str) -> Vec { diff --git a/crates/modules/nouser/nous-real/src/embeddings.rs b/crates/modules/akasha/nous-real/src/embeddings.rs similarity index 99% rename from crates/modules/nouser/nous-real/src/embeddings.rs rename to crates/modules/akasha/nous-real/src/embeddings.rs index 72a8987..abd9e85 100644 --- a/crates/modules/nouser/nous-real/src/embeddings.rs +++ b/crates/modules/akasha/nous-real/src/embeddings.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use std::time::Instant; use fastembed::{EmbeddingModel, InitOptions, TextEmbedding}; -use nouser_nous::{ +use akasha_nous::{ EmbedFilePayload, EmbedRequest, EmbedResponse, EmbedTextPayload, ErrorResponse, PingResponse, RequestKind, }; diff --git a/crates/modules/nouser/nous-real/src/main.rs b/crates/modules/akasha/nous-real/src/main.rs similarity index 91% rename from crates/modules/nouser/nous-real/src/main.rs rename to crates/modules/akasha/nous-real/src/main.rs index 3bd8f0b..0a4ea99 100644 --- a/crates/modules/nouser/nous-real/src/main.rs +++ b/crates/modules/akasha/nous-real/src/main.rs @@ -1,21 +1,21 @@ -//! `nouser-nous-real` — proveedor Nous con LLM real (gated por feature). +//! `akasha-nous-real` — proveedor Nous con LLM real (gated por feature). //! //! ## Build modes //! -//! - `cargo build -p nouser-nous-real` +//! - `cargo build -p akasha-nous-real` //! Compila como **stub**: bin que arranca, sidecarea al brahman-init //! pero rechaza toda request con un error explicando que falta la //! feature. Útil para que `cargo build --workspace` no requiera ML //! deps. //! -//! - `cargo build -p nouser-nous-real --features embeddings` +//! - `cargo build -p akasha-nous-real --features embeddings` //! Compila con `fastembed` + ONNX Runtime descargado por Cargo. //! Modelo default: `all-MiniLM-L6-v2` (384-d, ~80 MB descargado al //! primer run y cacheado en `~/.cache/fastembed`). //! //! ## Diseño //! -//! Mismo contrato wire que `nouser-nous-mock` (`nouser-nous` crate). La +//! Mismo contrato wire que `akasha-nous-mock` (`akasha-nous` crate). La //! diferencia operativa: real produce 384-d con semantic content //! (text-embedding del modelo); mock produce 32-d con metadata-hashing. //! No son intercambiables a media-deployment — los centroides de @@ -36,7 +36,7 @@ use brahman_card::{ ulid::Ulid, Card, CardKind, ContextBias, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef, }; -use nouser_nous::{transport, FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME}; +use akasha_nous::{transport, FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME}; use tokio::net::UnixListener; use tracing::info; @@ -63,11 +63,11 @@ async fn main() -> std::io::Result<()> { #[cfg(not(feature = "embeddings"))] info!( - "nouser-nous-real corriendo en modo STUB (compilá con \ + "akasha-nous-real corriendo en modo STUB (compilá con \ --features embeddings para activar el modelo)" ); - // 1. Resolver socket del data-plane (default `nouser-nous-real.sock`, + // 1. Resolver socket del data-plane (default `akasha-nous-real.sock`, // distinto del mock para coexistir). let sock_path = transport::provider_socket_path("real"); if sock_path.exists() { @@ -77,7 +77,7 @@ async fn main() -> std::io::Result<()> { std::fs::create_dir_all(parent)?; } let listener = UnixListener::bind(&sock_path)?; - info!(socket = %sock_path.display(), "nouser-nous-real escuchando"); + info!(socket = %sock_path.display(), "akasha-nous-real escuchando"); // 2. Sidecar al brahman-init con Card declarando el socket. let card = build_card(sock_path.clone()); @@ -144,7 +144,7 @@ fn init_tracing() { } /// Card que real-nous anuncia. Idéntica al mock excepto por: -/// - label distinto (`nouser.nous_real`) para que coexistan en el broker. +/// - label distinto (`akasha.nous_real`) para que coexistan en el broker. /// - `priority_contexts.prod = +1` (gana en contexto prod). /// - `service_socket` propio para que clientes lo descubran directo. fn build_card(service_socket: std::path::PathBuf) -> Card { @@ -160,7 +160,7 @@ fn build_card(service_socket: std::path::PathBuf) -> Card { Card { schema_version: brahman_card::CARD_SCHEMA_VERSION, id: Ulid::new(), - label: "nouser.nous_real".into(), + label: "akasha.nous_real".into(), payload: Payload::Virtual, supervision: Supervision::Delegate, lifecycle: Lifecycle::Daemon, diff --git a/crates/modules/nouser/nous-real/src/stub.rs b/crates/modules/akasha/nous-real/src/stub.rs similarity index 86% rename from crates/modules/nouser/nous-real/src/stub.rs rename to crates/modules/akasha/nous-real/src/stub.rs index 6a51b5b..8665960 100644 --- a/crates/modules/nouser/nous-real/src/stub.rs +++ b/crates/modules/akasha/nous-real/src/stub.rs @@ -1,7 +1,7 @@ //! Modo stub: arranca el bin pero rechaza las requests con un error //! que explica que falta la feature `embeddings`. -use nouser_nous::{EmbedRequest, ErrorResponse}; +use akasha_nous::{EmbedRequest, ErrorResponse}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::UnixStream; use tracing::warn; @@ -21,8 +21,8 @@ pub async fn handle_conn(stream: UnixStream) -> std::io::Result<()> { let resp = ErrorResponse { error: format!( - "nouser-nous-real compilado sin la feature `embeddings`. \ - Rebuild con: cargo build -p nouser-nous-real --features embeddings" + "akasha-nous-real compilado sin la feature `embeddings`. \ + Rebuild con: cargo build -p akasha-nous-real --features embeddings" ), }; let mut stream = reader.into_inner(); diff --git a/crates/modules/nouser/nous/Cargo.toml b/crates/modules/akasha/nous/Cargo.toml similarity index 95% rename from crates/modules/nouser/nous/Cargo.toml rename to crates/modules/akasha/nous/Cargo.toml index 3e5d2ce..29d0bf4 100644 --- a/crates/modules/nouser/nous/Cargo.toml +++ b/crates/modules/akasha/nous/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nouser-nous" +name = "akasha-nous" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/modules/nouser/nous/src/lib.rs b/crates/modules/akasha/nous/src/lib.rs similarity index 93% rename from crates/modules/nouser/nous/src/lib.rs rename to crates/modules/akasha/nous/src/lib.rs index 839bf5a..4a6b957 100644 --- a/crates/modules/nouser/nous/src/lib.rs +++ b/crates/modules/akasha/nous/src/lib.rs @@ -1,6 +1,6 @@ -//! `nouser-nous` — el contrato del proveedor de embeddings. +//! `akasha-nous` — el contrato del proveedor de embeddings. //! -//! Define el wire-format compartido entre `nouser-core` (consumidor) y +//! Define el wire-format compartido entre `akasha-core` (consumidor) y //! cualquier implementación de Nous (mock determinista o LLM real). El //! protocolo es **line-delimited JSON** sobre Unix socket: cada conexión //! envía una request, recibe una response, y cierra. Single-shot por @@ -21,8 +21,8 @@ //! //! ## Por qué un crate aparte //! -//! El consumidor (nouser-core) y el proveedor (nouser-nous-mock, -//! nouser-nous-real) deben acordar en types EXACTOS. Tener el contrato +//! El consumidor (akasha-core) y el proveedor (akasha-nous-mock, +//! akasha-nous-real) deben acordar en types EXACTOS. Tener el contrato //! en su crate evita que cada lado declare structs paralelos que se //! desincronizan. Si bumpeás el wire, bumpeás aquí. //! @@ -32,7 +32,7 @@ //! el mismo `flow.output: { name: "embed-result", type: ... }` y //! `flow.input: "embed-request"`. El broker brahman los matchea contra //! los consumidores; el `priority_offset` per-contexto del Card hace que -//! mock-nous gane en `test` y real-nous en `prod`. nouser-core sólo +//! mock-nous gane en `test` y real-nous en `prod`. akasha-core sólo //! consume el flow, sin saber cuál implementación corre. #![forbid(unsafe_code)] @@ -118,7 +118,7 @@ pub mod transport { pub const SOCKET_ENV: &str = "NOUSER_NOUS_SOCKET"; /// Nombre genérico del socket cuando hay un solo proveedor. - pub const SOCKET_NAME: &str = "nouser-nous.sock"; + pub const SOCKET_NAME: &str = "akasha-nous.sock"; /// Ruta canónica al socket cuando un único proveedor está activo /// (consumidores que no quieren elegir). @@ -136,7 +136,7 @@ pub mod transport { if let Ok(p) = std::env::var(SOCKET_ENV) { return PathBuf::from(p); } - runtime_base().join(format!("nouser-nous-{}.sock", provider)) + runtime_base().join(format!("akasha-nous-{}.sock", provider)) } fn runtime_base() -> PathBuf { diff --git a/crates/modules/barra/SDD.md b/crates/modules/barra/SDD.md new file mode 100644 index 0000000..dc8f860 --- /dev/null +++ b/crates/modules/barra/SDD.md @@ -0,0 +1,41 @@ +# modules/barra/ — Taskbar agnóstica (estilo Windows) + +**Propósito.** Lista de tareas (cajitas, una por ventana abierta) que +se renderiza dentro de un `
    ` provisto por el host. Modelo + render +puros viven en `barra-core`; el binding click+DOM en `barra-web`. + +## Crates + +| crate | tipo | rol | +| ------------- | ---- | --------------------------------------------------------- | +| `barra-core` | lib | `Task` + `render_html(&[Task]) -> String` + sanitizadores | +| `barra-web` | lib | Mount sobre `
      ` + listener de click + lookup centers | + +## Dependencias + +- `barra-core`: sin deps. +- `barra-web` ← `barra-core`, `wasm-bindgen`, `web-sys`. + +## Contrato HTML + +```html + +``` + +Clases generadas (host estiliza): +- `.taskbar-item`, `.taskbar-item.active`, `.taskbar-item-dot` +- atributo `data-task=""` para theming CSS por tarea + +## API + +```rust +let bar = TaskList::mount(list_el)?; +bar.set_tasks(&[Task::new("aire", "AIRE"), + Task::new("fuego", "FUEGO").active()]); +bar.on_click(|id, cx, cy| { /* center en CSS pixels */ }); +``` + +## Estado + +barra-core: 5 tests verdes (sanitize + escape + render). barra-web: +binding mínimo (mount + click + center lookup). LOC ~280. diff --git a/crates/modules/barra/barra-core/Cargo.toml b/crates/modules/barra/barra-core/Cargo.toml new file mode 100644 index 0000000..6c11636 --- /dev/null +++ b/crates/modules/barra/barra-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "barra-core" +description = "Barra — modelo de taskbar agnóstico: Task + render-to-html + sanitizadores. Sin dependencias web/DOM." +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true diff --git a/crates/modules/barra/barra-core/src/lib.rs b/crates/modules/barra/barra-core/src/lib.rs new file mode 100644 index 0000000..ef4afc7 --- /dev/null +++ b/crates/modules/barra/barra-core/src/lib.rs @@ -0,0 +1,108 @@ +//! Barra core — modelo agnóstico de taskbar. +//! +//! Provee la lista de `Task`, los helpers de sanitización para atributos +//! HTML, y `render_html` puro. El binding DOM vive en `barra-web`. + +/// Una tarea (cajita) en la barra. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Task { + pub id: String, + pub label: String, + pub active: bool, +} + +impl Task { + pub fn new(id: impl Into, label: impl Into) -> Self { + Self { id: id.into(), label: label.into(), active: false } + } + pub fn active(mut self) -> Self { + self.active = true; + self + } +} + +/// Renderiza un slice de tareas a markup HTML. Sanitiza IDs y escapa +/// labels. La salida es la lista de `
    • ` que el host inyecta en su `
        `. +pub fn render_html(tasks: &[Task]) -> String { + let mut html = String::new(); + for t in tasks { + let id_safe = sanitize_attr(&t.id); + let label_safe = escape_text(&t.label); + let active_cls = if t.active { " active" } else { "" }; + html.push_str(&format!( + "
      • " + )); + } + html +} + +/// Filtra a `[a-zA-Z0-9_-]` para uso seguro en atributos HTML. +pub fn sanitize_attr(s: &str) -> String { + s.chars() + .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_') + .collect() +} + +/// HTML-escape de texto para insertarlo en posiciones de contenido. +pub fn escape_text(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + match c { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + c => out.push(c), + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn task_builder_defaults_inactive() { + let t = Task::new("aire", "AIRE"); + assert!(!t.active); + assert!(Task::new("f", "F").active().active); + } + + #[test] + fn sanitize_attr_strips_unsafe() { + assert_eq!(sanitize_attr("aire"), "aire"); + assert_eq!(sanitize_attr("a-b_c"), "a-b_c"); + assert_eq!(sanitize_attr("ai"), "aire"); + assert_eq!(sanitize_attr("a\"b"), "ab"); + } + + #[test] + fn escape_text_escapes_html() { + assert_eq!(escape_text("AIRE"), "AIRE"); + assert_eq!(escape_text("