# 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-08 ### 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).