529287f01d5f658eacabfeb450cd11b7cf7d694c
282 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fc72726666 |
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:
- Nueva variante FieldKind::EntityRef (serializa como "entity_ref").
- FieldSpec.ref_entity: Option<String> nuevo. validate() chequea que
cualquier field con kind=entity_ref tenga ref_entity set.
- Nuevo SchemaError::EntityRefMissingTarget.
Runtime:
- render_entity_ref_selector helper: lista clickable debajo del input,
cada item con etiqueta humana (heuristica: name > label > title >
sku > sku_id > UUID corto) y click handler via cx.listener que
setea el TextInput con el UUID completo. Highlight en accent color
para el seleccionado.
- parse_field_value(EntityRef) devuelve string raw — validacion como
Uuid es responsabilidad de commit_morphism downstream.
- Mensaje "(sin {entity}: crea uno antes...)" cuando lista vacia —
el user sabe que hacer.
Demo actualizado sales_engine: vender_form.stock_id_input y
caja_id_input cambian a kind=entity_ref. Flujo nuevo: click en Stock
listado bajo input, click en Caja, escribir venta_id/cantidad/precio/
timestamp, submit. Sin copiar UUIDs.
Tests: 2 nuevos schema (validate detecta EntityRef sin ref_entity y
acepta con ref_entity) + 4 nuevos runtime (parse, human_label cubre
todos los key fallbacks). 29 tests totales (16 + 8 + 5).
Pendientes: confirmacion de delete, snapshot/compaction del log,
edit delta-only, validacion estricta de params del morphism via
FieldKind del FieldSpec en lugar de infer_param_value.
|
||
|
|
932e7464d7 |
feat(nakui-ui): Action::Morphism wired al pipeline real (compute -> log -> apply)
Cierra el ultimo 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: compute (con
dry-run + KCL post-checks) -> log append -> store apply.
Schema nakui-ui-schema extendido:
- Module.nakui_module_dir: Option<String> nuevo. Path al modulo
nakui-core. Sin esto, Action::Morphism quedan no-op con toast.
SeedEntity sigue funcionando (alta administrativa sin manifest).
- Action::Morphism gano dos campos opcionales:
- inputs: BTreeMap<String, String> — mapeo role -> field_name.
- params: Vec<String> — fields cuyos values van al params JSON.
Si vacio, todos los fields no-input van a params.
Runtime nakui-ui:
- MetaUi.executors: BTreeMap<String, Arc<Executor>> nuevo. Carga
Executor::load_module(nakui_module_dir) en MetaUi::new.
- commit_morphism: resuelve inputs (parsea UUIDs), arma params
(Value object con tipos inferidos), llama
execute_and_log_with_recovery. Toast con count de ops o error.
- infer_param_value: heuristica i64 -> f64 -> bool -> string.
Tests: 2 nuevos. E2E morphism_pipeline_executes_real_sales_vender
carga el modulo real crates/modules/nakui/modules/sales, ejecuta
"vender" con inputs Stock+Caja y params (cantidad=5, precio=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacio).
- stock.cantidad: 100 -> 95.
- caja.saldo: 1_000_000 -> 1_001_000.
12 tests verdes en nakui-ui (+1). Schema extension no rompio nada
(6 unit + 5 integration siguen verdes).
Demo nuevo: examples/nakui-modules/sales_engine/module.json apunta
al sales real via nakui_module_dir. 6 vistas (list+form para Stock/
Caja/Venta + "Vender" con Action::Morphism). El user crea Stocks +
Cajas con seed_entity, copia los UUIDs a los inputs de "Vender", y
ejecuta el morphism real con KCL post-checks.
Activacion:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
Trade-offs:
- Inputs UUID a mano (no dropdown). Nice-to-have: FieldKind::EntityRef
que renderee selector.
- Inferencia de tipo en params es heuristica.
|
||
|
|
06c4fb9130 |
feat(nakui): metainterfaz declarativa + 6 modulos ERP estandar
Salto cualitativo: Nakui pasa de "library + demos + read-only viewer
del event log" a plataforma ERP con UI dirigida por datos. Cada
modulo de negocio se declara como un module.json (sin codigo Rust
nuevo) y el runtime GPUI lo carga dinamicamente: sidebar de menus,
listas con columnas configurables, formularios de alta.
3 entregables:
1. Crate nakui-ui-schema (datos puros): Module, View::List/Form,
FieldSpec con FieldKind {Text|Multiline|Number|Boolean|Date},
Action {OpenView|SeedEntity|Morphism}. Module::from_path,
Module::validate, load_modules_from_dir(dir). 6 tests unit + 4
integration.
2. Crate nakui-ui (binario GPUI): carga modulos desde
NAKUI_MODULES_DIR. Sidebar + main panel. List view con tabla
weighted; form view con campos labeled + submit que ejecuta
SeedEntity contra MemoryStore in-process compartido. Toast +
error banner. 6 tests unit.
3. 6 modulos demo en examples/nakui-modules/:
- customers (nombre, email, telefono, credito, notas)
- products (SKU, nombre, categoria, precio, stock)
- suppliers (razon social, ID fiscal, contacto, terminos pago)
- inventory_movements (fecha, tipo, SKU, cantidad, costo, motivo)
- sales_orders (numero, cliente, fechas, estado, totales)
- invoices (numero, cliente, fechas, totales, pagado, moneda)
Filosofia: UI como datos. Persistencia universal (MemoryStore hoy,
SurrealStore manana, sin tocar module.json). Schema primero, semantica
despues.
Activacion:
NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui
Limitaciones conocidas (proximos iters):
- Inputs sin teclado (GPUI no lo trae nativo; integrar
yahweh-widget-text-input).
- Click handlers no propagan mutacion al estado (refactor con
cx.listener pendiente).
- Action::Morphism queda como TODO hasta cargar Manifest junto al
Module.
- Sin persistencia entre runs (wire con EventLog/SurrealStore para
cuando el daemon Nakui exista).
Tests: 16 totales nuevos. Lo que esto desbloquea: cualquiera puede
escribir un module.json para su dominio (pacientes, alumnos,
reservaciones) y aparece en la UI sin recompilar.
|
||
|
|
6be50c5b73 |
feat(minga-core): alpha-hashing per-language para Python, TS, JS, Go
Cierra el ultimo pendiente fundamentado del CHANGELOG. Cada lenguaje soportado por minga tiene ahora su propio profile alpha-equivalente — refactorings tipo "rename variable" no inflan el storage del repo en ningun dialecto. Refactor de alpha.rs (639 LOC) a modulo alpha/: - alpha/common.rs: primitives compartidos (TAG_*, write_kind_and_field, emit_*, push_identifier_name). Garantiza wire bit-equivalente. - alpha/rust.rs: logica Rust movida sin cambios funcionales. - alpha/python.rs, alpha/ecmascript.rs, alpha/go.rs: nuevos. - alpha/mod.rs: re-exporta hash_node_alpha (Rust legacy) + expone hash_alpha_with(dialect, node) que despacha al profile correcto. Cobertura per-language: Python: function_definition, lambda, for_statement, list/set/dict comprehensions, generator_expression (con scope incremental: binders del for_in_clause viven en clauses siguientes + body), with_statement (recursando en as_pattern_target). ECMAScript (TS+JS): function_declaration, function_expression, method_definition, generator_function_*, arrow_function (paren y shorthand), statement_block (con lexical_declaration y variable_declaration introduciendo binders al resto), for_in_statement (cubre for-of/for-in), for_statement (initializer C-style), catch_clause, TS typed/optional parameters. Go: function_declaration, method_declaration, func_literal (closure), parameter_declaration con multi-name agrupados, block (con short_var_declaration), for_statement con range_clause y for_clause, if_statement con initializer. Tests: 26 nuevos en alpha_polyglot.rs cubriendo rename invariants + sanity negatives (function name matters, type matters, operation matters) por cada lenguaje + cross-language sanity (mismo source en distintos lenguajes -> hashes distintos). 141 tests verdes en minga-core (115 antes; +26 polyglot). 36 alpha tests Rust intactos (sin regresion). Pendientes Minga: minga-vfs (FUSE, proyecto independiente). Cobertura adicional por-lenguaje (Python class, JS destructuring, Go type_switch) queda como nice-to-have. |
||
|
|
d1888e0901 |
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
alpha-equivalente ahora es estable bajo renombre de TODOS los binders
de Rust, no solo los del MVP (params, let, for, match arms).
Pendientes cerrados:
- if let X = expr { ... }: if_expression detecta let_condition en
condition, recolecta binders del pattern, los propaga al
consequence. Alternative (else) no los ve.
- while let X = expr { ... }: simetrico al if-let, propaga al body.
- let-else: ya funcionaba por construccion (alternative procesado en
scope antes que feed_block extienda con los binders).
- or_pattern: ambos lados introducen los mismos binders (Rust
enforcement). Emit recorre todos, collect solo el primero para no
duplicar.
- let-chains (if let X = a && let Y = b): collect_let_condition_binders
recursa en el arbol del condition capturando todos los let_condition
vivan donde vivan (binary_expression u otros).
Helper nuevo: feed_let_condition para que el pattern del let_condition
pase por feed_pattern (que distingue binders de constructors). Sin
esto, los identifiers del pattern se hasheaban como variables libres
y Some(x) != Some(y) aun teniendo el mismo significado.
Tests: 6 nuevos en alpha_invariants:
- alpha_if_let_binder_rename_invariant
- alpha_if_let_else_does_not_see_binder
- 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)
36 tests alpha verdes. 115 totales en minga-core.
Refactorings del tipo "rename variable" no inflan el storage del
repo. Pendiente futuro: alpha-hashing per-language (Python, TS, JS,
Go) — cada uno requiere conocimiento profundo de su gramatica.
|
||
|
|
4db168253c |
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-deteccion por extension hace que minga ingest archivo.{py,ts,js,go}
"simplemente funcione".
API nueva en minga_core::parse:
- Funciones por dialecto: python, typescript, javascript, go (~6 LOC
c/u sobre el parse_with comun). Mas la rust existente.
- Enum Dialect con parse(source) y name() para logging.
- detect_by_extension(ext) -> Option<Dialect>: rs/py/pyi/ts/js/mjs/
cjs/go (case-insensitive). None para extensiones desconocidas.
Wire en minga-cli:
- cmd_ingest deja de hardcodear parse::rust — usa
detect_dialect(file)?.parse(...).
- initial_scan + cmd_watch cambian is_rs_file -> is_supported_source.
- CliError::UnsupportedLanguage { path, extension } nuevo, lista las
extensiones reconocidas en el mensaje.
Notas sobre hashing:
- Hashing estructural (cas::hash_node) funciona para todos. NO es
alpha-equivalente.
- Hashing alpha-equivalente (alpha::hash_node_alpha) sigue siendo
Rust-only — cada lenguaje tiene reglas distintas para binder vs
constructor; implementacion per-language queda como work futuro
(requiere conocimiento profundo de cada gramatica).
- Sanity test structural_hash_distinguishes_languages verifica que
"x = 1" parseado como Python != JS — las gramaticas no comparten
kinds, hashes salen distintos. Importante para evitar colisiones.
Deps nuevas (workspace + minga-core):
- tree-sitter-python 0.23, tree-sitter-typescript 0.23 (modo
LANGUAGE_TYPESCRIPT, no TSX), tree-sitter-javascript 0.23,
tree-sitter-go 0.23.
Tests: 9 nuevos en parse::tests (parse basico para 5 dialectos +
detect_by_extension canonical/case-insensitive + name() +
structural_hash_distinguishes_languages). 108 verdes en minga-core,
10 en minga-cli, sin regresion.
Pendientes: alpha-hashing per-language; alpha-Rust documentados en
alpha.rs (if let, while let, let-else, let-chains, or_pattern con
bindings).
|
||
|
|
ad0d475a2e |
feat(brahman-net): capa P2P compartida — Fase 0 (extracción del swarm)
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. BrahmanNet expone: - new() / with_keypair() para identidad efimera o persistente - API de comandos uniforme: dial, listen, add_dht_peer, find_closest_peers, start_providing, find_providers - Publica peer_id (libp2p) y control (stream::Control) — cada protocolo registra su StreamProtocol sin acoplarse al swarm - Re-exporta Stream y StreamProtocol para evitar dep directa a libp2p minga-p2p::network reduce de 282 LOC a 22: re-export del nuevo BrahmanNet bajo el alias historico LibP2pNode (zero churn en MingaPeer) y la const SYNC_PROTOCOL = "/minga/sync/1.0.0" especifica del sub-protocolo de sync Minga. Aclaracion semantica anclada por el usuario: Arje es el init (PID 1), Brahman es el encuentro entre Entes. El nombre brahman-net refleja que la malla pertenece al encuentro, no al runtime — Minga es un cliente de la malla, no su dueño. Tests: minga-p2p completo verde (58 tests, sin regresion). Behavior identico — solo se movio codigo, ningun cambio funcional. |
||
|
|
6f993f4268 |
refactor(explorer+card): independencia jerarquica enforced
Cierra el unico debt estructural detectado en el audit de independencia: nouser-explorer ya no arrastra nouser-core (que aportaba notify/walkdir/sled/blake3 al grafo de compilacion de una UI que solo habla JSON contra un socket). - Cliente movido: engine_socket::client::list_monads (~60 LOC, std + serde_json puros) emigra de nouser_core::engine_socket a nouser_card::query::client. Vive donde viven los wire types, consistente con el principio "un consumer importa el contrato, no el runtime del productor". - Drop dep: nouser-explorer deja de depender de nouser-core. Verificado con cargo tree: notify, sled, blake3 desaparecen del grafo del binario. - Fallback "falla hacia la simplicidad": nueva resolve_socket() en el explorer intenta primero broker discovery; si el broker no responde / no hay init vivo, fallback directo al default_socket_path. El explorer queda funcional contra un daemon huerfano (standalone sin init) — completa "consciente cuando hay ecosistema, soberano cuando esta solo". - socket_source gana tercer estado "default-path" para visibilidad. Audit estructural confirmo que el resto del ecosistema ya respeta el principio. Brahman es pegamento opcional, no chasis obligatorio — y ahora el grafo de Cargo lo enforcea, no solo la convencion. Tests: 4 + 10 + 27 verdes. Cliente movido ejercitado end-to-end por los 3 tests integracion de engine_socket. |
||
|
|
2ae888bc8f |
feat(explorer+daemon): discovery dinamico via broker + query socket
Cierra el "explorer encuentra al daemon de forma totalmente dinamica"
del meta-plan. La UI deja de hardcodear el socket admin: descubre al
daemon nouser via MatchEvent::Available del broker y le consulta sus
Monadas directo.
Pipeline end-to-end:
- Daemon publica engine Card con service_socket = $XDG_RUNTIME_DIR/
nouser-engine.sock y flow.output = monad-list:json.
- Daemon binda Unix socket en ese path con listener blocking que
sirve nouser_card::query::QueryRequest::ListMonads, responde
ListMonadsResponse { engine, monads: Vec<MonadView> }.
- Explorer construye consumer Card con flow.input matched,
brahman_sidecar::await_provider_blocking le devuelve el socket,
y nouser_core::engine_socket::client::list_monads lo consulta.
- Cachea el socket; cualquier fallo de query lo invalida y la
proxima iteracion re-descubre.
Wire types nuevos en nouser_card::query:
- QueryRequest::ListMonads
- ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }
- MonadView: proyeccion slim de MonadManifest SIN centroid ni
members (KB que no tienen por que viajar cada poll).
- transport::default_socket_path() con env override.
Listener en nouser_core::engine_socket: spawn_listener + client
blocking con QueryError tipado. 3 tests integracion verdes.
Refactor explorer:
- Drop dep brahman-admin, add brahman-sidecar/nouser-card/nouser-core.
- State: socket cache + snapshot + socket_source informativo.
- TickOutcome enum desacopla la I/O del UI.
Trade-offs: polling 2s (no streaming — broker no empuja Data cards
hoy), re-discovery full en error (discovery es barato).
Tests: 10 (nouser-card +3 query) + 27 (nouser-core +3 engine_socket)
+ 4 (sidecar) verdes. Explorer compila clean.
|
||
|
|
b23ddf2980 |
feat(nous-real): cache de embeddings + write-through al CAS de arje
Cierra el ciclo del feedback: el modelo real (fastembed-allMiniLML6V2, ~1-50ms por archivo) era invocado ciegamente en cada re-cluster del watcher. Ahora se cachea por sha256(bytes-vistos) + model_id, con write-through al CAS de arje. Pipeline en handle_file: 1. Lee primeros 8 KiB del archivo (igual que antes). 2. file_sha = ente_cas::sha256_of(buf) — hash de los bytes que el modelo *realmente* verá. Garantiza que un archivo creciendo mas alla de la ventana sin tocar la cabeza siga sirviendo cache hits. 3. Cache lookup -> HIT: respuesta en us, sin invocar fastembed. 4. MISS: ente_cas::store(&buf) (write-through, 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, asi que cambiar de modelo invalida el cache implicitamente. Override por env NOUSER_NOUS_REAL_CACHE. Encoding compacto: cada Vec<f32> se serializa como bytes little-endian (4B por f32, sin overhead). Para 384-d son 1.5 KiB por entry. Decode tolera bytes corruptos (longitud no-multiplo de 4 -> None, no panic). Por que 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. Mock NO se modifica — su embedding pseudo-32d es metadata-hashing puro, sin costo. Cachearlo seria overhead. Tests: 5 unitarios verdes (roundtrip, miss, model collision, content collision, corrupted value). Stub mode (sin feature) sigue compilando sin tocar cache. |
||
|
|
79d42aba28 |
chore(nakui): alinear nakui-core con [workspace.package] y deps compartidas
Cleanup de drift de convenciones: nakui-core era el unico crate del
monorepo que manteia version, edition y thiserror hardcoded, mientras
el resto heredaba del workspace y usaba thiserror v2. Eso significaba
que un bump global de version o edition se olvidaba sistematicamente
de nakui.
Cambios:
- [package]: version, edition, rust-version, license, authors, publish
-> todos *.workspace = true. Agregado description (convencion).
- 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 esta en el workspace dep porque nakui es el unico user;
queda local opt-in en lugar de inflar el dep comun.
- Deps especificas de nakui (sin comparticion posible): rhai, petgraph,
surrealdb permanecen inline con version local.
Verificacion: cargo build -p nakui-core verde tras el bump thiserror
v1->v2 — los 14+ enums de error de nakui no requirieron ajustes
(derive backwards-compat para patrones simples). cargo test -p
nakui-core --lib: 27/27 verdes.
Bonus en este commit: discovery.rs movio el import Ulid a #[cfg(test)]
porque el refactor a Card::new lo dejo unused en module-scope.
|
||
|
|
4c9e4c3962 |
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 00000...
La fix no es romper Default (sigue siendo determinista, requerido por
callers que lo usan como template para deserializacion y patterns de
busqueda) sino agregar un constructor explicito Card::new(label) que
asigna id = Ulid::new() + label provisto, manteniendo defaults seguros
en todo lo demas.
Pensado para struct-literals con override parcial:
let card = Card {
kind: CardKind::Data,
payload: Payload::Embedded(json),
..Card::new("mi-modulo.algo")
};
Refactor de call sites en codigo de produccion:
- brahman_sidecar::discovery::build_consumer_card
- nouser daemon::build_engine_card
Default queda con docstring expandida que apunta a Card::new para uso
"vivo". to_brahman_card en nouser-card NO se modifica porque asigna
el id estable de la Monada, no uno fresco.
Tests: 3 unitarios nuevos en brahman-card. 15 tests verdes (era 12).
|
||
|
|
006640057a |
feat(sidecar): API reusable de discovery via broker
Promueve el patron ad-hoc discover_producer_socket que vivia inline en
'nouser attract --remote' a un modulo publico brahman_sidecar::discovery.
Cualquier consumer ahora puede preguntar al broker "quien provee este
TypeRef?" sin reimplementar el patron a mano.
API:
- build_consumer_card(label, flow_name, type_name) construye una Card
minima (Ente, Oneshot, Virtual) con un input flow. Asigna Ulid::new()
real (no nil), evitando colisiones en el broker.
- await_provider(card, timeout) async: conecta al init, espera
MatchEvent::Available, devuelve producer_service_socket, manda
Farewell. Ignora eventos Lost durante el await.
- await_provider_blocking(card, timeout) wrapper para mundos no-async
(CLIs, std-thread loops). Crea su propio runtime current_thread.
- ConsumerError tipado: Connect{socket,source}, NoProvider{flow,type_ref,
timeout}, Client(ClientError), Runtime(String). Adios al Box<dyn Error>.
Refactor en nouser daemon: discover_producer_socket inline (60 LOC) ->
5 LOC delegando en el helper. remote_embed ya no construye su propio
runtime.
Tests: 4 unitarios (id no-nil, id unico por llamada, formateo de Wit
TypeRef, fallback sin input). Build verde para sidecar y nouser-core.
|
||
|
|
2725d6a297 |
feat(nouser+sidecar): watcher con debounce 150ms + re-publish al broker
Cierra los dos pendientes documentados en
|
||
|
|
487c457e5b |
feat(nouser): notify watcher — el sistema reacciona en tiempo real
El daemon monta notify::recommended_watcher recursivo sobre el dir escaneado. Cada Create/Modify de archivo regular dispara: embedding → filtro por centroid_model → ranking contra centroides → log con 🧲 / · según supere DEFAULT_ATTRACTION_THRESHOLD. $ nouser daemon /tmp/x & $ vim /tmp/x/src/nuevo.rs [watcher] 🧲 /tmp/x/src/nuevo.rs → x/src (0.7470) $ echo edit >> /tmp/x/docs/n1.md [watcher] 🧲 /tmp/x/docs/n1.md → x/docs (0.8169) Mecánica: - DB pasa a Arc<Mutex<MonadDb>> para sharing con thread 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 + diff publish) queda para futuro. Limitación conocida: notify emite múltiples eventos por edición (Create + Modify, etc.). Sin debounce el watcher reporta varias veces. Aceptable para demo; producción conviene debounce ~100ms por path. Esto cierra la Fase C del plan post-reporte: el sistema "se siente" vivo. Tocar un archivo en vim y ver inmediatamente la atracción calculada cumple el meta-mensaje "Mónada Viva". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
65af98da13 |
feat(nouser): hidratación del daemon vía sled + path_hint
El daemon ya no recomputa ciegamente al arrancar. Si la DB tiene Mónadas previas con centroid_model válido, las publica instantáneo y el re-scan reusa sus IDs vía path_hint. Schema: - MonadManifest.path_hint: Option<String> — identidad estable derivada del origen (para by_directory, el parent dir canónico). Permite reusar ULID across re-scans. Cluster: - Nueva fn cluster::by_directory_hydrated(files, min_files, prior). Con 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 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 NUEVO. Los path_hints existentes preservan identidad, evitando duplicados en el broker. 5. Persiste el set actualizado. Validación: $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core # arranque 1: re-scan 102 archivos → 5 mónadas (5 nuevas) $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core # arranque 2: hidratadas 5 mónadas en O(1) # re-scan → 5 mónadas (0 nuevas vs hidratación) Costo del arranque 2: ~0.06s user CPU. Tests: 7 (card) + 24 (core) verdes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
820a1a33bf |
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 daría scores sin sentido.
- MonadManifest.centroid_model: Option<String>. None = legacy.
- nouser_core::embed::MODEL_ID = "nouser-pseudo-32d". 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, reportar el mismo ID es honesto.
- nouser-nous-real ya reportaba "real-fastembed-allMiniLML6V2-384d";
el filter ahora lo descarta automáticamente cuando los centroides
cacheados son del mock.
- cmd_attract:
- Captura el model_id del embedding del target.
- Filtra Mónadas cuyo centroid_model no matchee.
- Reporta "embed: <source> (<model>)" y "skipped: N" cuando
descarta.
Resultado: 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.
Tests: 7 (card) + 24 (core) verdes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9c371ee43e |
feat: profile.dev slim + dynamic binding del consumer Nous
Dos piezas del plan post-reporte, en un commit por estar acopladas
(ambas tocan cómo se construye y conecta el sistema):
profile.dev slim:
- debug = "line-tables-only" + split-debuginfo unpacked +
codegen-units 256 en [profile.dev].
- Override [profile.dev.package.{gpui,ort,fastembed,tokenizers,image}]
con opt-level=1, debug=false para los pesados que no debuggeamos.
- Resultado: binarios ~3× más livianos. ente-zero 125→47 MB;
mock-nous ~50→22 MB. target/ futuro mucho más manejable.
dynamic binding (cierra priority_contexts):
- nouser-core Cargo.toml: deps directas brahman-handshake + tokio.
- cmd_attract refactor:
- Si NOUSER_NOUS_SOCKET está set, atajo explícito (compat).
- Si no, abre Client al brahman-init, anuncia consumer Card con
flow.input = embed-result:json, espera 3s por MatchEvent::Available,
usa producer_service_socket del evento.
- discover_producer_socket() es async; cmd_attract usa runtime tokio
current_thread inline (block_on).
- embed_via(path, file) se separa como helper sync para la RPC.
Validación end-to-end:
$ ente-zero & nouser-nous-mock &
$ nouser attract --remote crates/core archivo.rs
🧲 0.9058 ente-brain/src ...
(mock log: "embed_file path=archivo.rs" — discovery activo)
Con esto BRAHMAN_BROKER_CONTEXT=test/prod swappea el provider sin que
el consumer toque nada — la promesa de priority_contexts es real.
Bug colateral resuelto: la "flakiness" del cargo test --workspace era
disco lleno (24 GB en target/), no condición de carrera. Con
cargo clean + profile slim, tests deterministas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7831c0c827 |
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; si no, in-memory: $ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core scan: 102 archivos, 5 mónadas $ ls /tmp/monads.sled blobs conf Tests nuevos en db.rs: - persistence_roundtrip — escribe, cierra, reabre, datos están. - replace_monads_purges_persistent_tree — replace limpia tree. 24 tests en nouser-core (era 22, +2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d7b4886164 |
feat(sidecar): Phase B-3 — SidecarPool consolida sidecars en un runtime
Antes: cada spawn(card) creaba un thread + tokio runtime propio.
Para módulos con 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_with_wit, wit);
pool.spawn_with_config(custom_config);
// 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.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b3feaf667c |
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.
- brahman-card:
- RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }
- CardReference { kind, target_id: Ulid, target_label: String }.
target_label es cache para que la UI renderee sin resolver.
- Card.references: Vec<CardReference> + espejo en WireCard.
Conversiones From propagan.
- brahman-broker::BrokeredCard propaga references.
- brahman-status imprime "ref OwnedBy → label (id)" por sesión.
- nouser daemon: cada Mónada publicada añade OwnedBy apuntando al
engine. Declaración unilateral — el engine no necesita conocer
Mónada 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...)
[data] ente-brain/src
ref OwnedBy → brahman.nouser_engine (01K...)
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5edc912ed8 |
feat: Phase D-3 + D-4 — service_socket en Card, providers coexisten
Cierra el ciclo del swap automático Nous mock↔real:
- brahman-card: Card.service_socket: Option<PathBuf> y espejo en
WireCard. Path del data plane (distinto al Init). Cualquier
consumer que matchee con esta Card conecta directo, sin discovery
extra.
- brahman-broker: BrokeredCard propaga service_socket. Sin
participación en matching — sólo metadata.
- brahman-handshake::MatchEvent: nuevo campo
producer_service_socket. Server lo busca en BrokeredCard al emitir
Available.
- nouser-nous::transport: 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.
- Mock declara nouser-nous-mock.sock; real declara
nouser-nous-real.sock. La Card se construye DESPUÉS del bind.
- 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
[ente] nouser.nous_mock
socket: /run/user/1001/nouser-nous-mock.sock
Pendiente (no crítico): nouser-core attract --remote usa todavía
NOUSER_NOUS_SOCKET hardcoded. Siguiente paso: subscribirse al
MatchEvent del broker y usar producer_service_socket directo, así
BRAHMAN_BROKER_CONTEXT=test/prod swapea provider sin tocar al
consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
794884a90f |
refactor(nouser): labels de Mónada con 2 componentes del path
Resuelve la fricción visual de monorepos donde múltiples Mónadas
quedaban con label "src" (ambiguo). Nueva función label_from_path
toma los últimos hasta 2 componentes normales del path:
$ 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 (proj/src en lugar de src).
22 tests en nouser-core (era 20, +2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
11fc95629c |
feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature
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 con dos modos según feature:
- Sin feature (default): stub.
cargo build -p nouser-nous-real (~10s, sin ML deps).
Bin arranca, sidecarea a brahman-init declarando la Card,
escucha en el socket Nous, rechaza requests con un ErrorResponse
explicativo: "compilado sin la feature embeddings, rebuild con
cargo build -p nouser-nous-real --features embeddings".
cargo build --workspace SIGUE siendo limpio.
- Con --features embeddings: real.
Pulls fastembed = "4" → ort 2.0.0-rc.9 (ONNX Runtime con binarios
descargados por Cargo) + tokenizers 0.21 + ~30 transitive deps.
Compila en ~50s.
Modelo default: all-MiniLM-L6-v2 (384-d, descargado a
~/.cache/fastembed la primera vez).
EmbedText: pasa el texto al modelo → vector 384-d.
EmbedFile: lee primeros 8KiB UTF-8 lossy, embed como texto.
Ping: devuelve model_id + embed_dim reales.
Card declara label "nouser.nous_real" + priority_contexts.prod = +1.
En contexto prod gana sobre el mock; en test el mock gana por su +1
en test. Sin contexto, empate alfabético.
Validación end-to-end con modelo real:
$ ente-zero & nouser-nous-real &
$ python3 socket-probe '{"kind":"embed_text","payload":{"text":"..."}}'
model: real-fastembed-allMiniLML6V2-384d
elapsed_ms: 8
embed_dim: 384
Tradeoff: dim mock (32) vs real (384) son incompatibles. Cambiar
proveedor invalida centroides cacheados — documentar "limpiar DB al
swap".
Workspace state:
- cargo build --workspace limpio sin features (no ML deps pulled).
- cargo build -p nouser-nous-real --features embeddings funciona.
- 0 errores, 0 warnings en ambos modos.
Pendientes para D-3 / futuro:
- Discovery de socket: el consumer hoy usa NOUSER_NOUS_SOCKET hardcoded.
Para que el broker elija real vs mock per-contexto, falta o un campo
socket en el MatchEvent o un broker query "dame socket de session X".
- Coexistencia: ambos providers compiten por el mismo socket path por
default. Parametrizarlos cuando se quiera correrlos juntos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b3c3c00cf2 |
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 mock↔real (futuro) será vía
priority_contexts en el broker.
Crates nuevos:
- crates/modules/nouser/nous: contrato compartido.
- EmbedRequest { kind: { EmbedFile | EmbedText | Ping }, payload }.
- EmbedFilePayload (path, ext, size, mtime), EmbedTextPayload.
- EmbedResponse (embedding, model, elapsed_ms), PingResponse,
ErrorResponse.
- Wire: line-delimited JSON sobre Unix socket, single-shot.
- Constants FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME.
- transport::default_socket_path con env 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/embed-result + priority_contexts.test = +1.
- Bind del socket Nous + accept loop tokio.
- EmbedFile delega a nouser_core::embed::embed (Phase C).
- Modelo: "mock-pseudo-32d".
Cambios:
- nouser-core: dep nueva nouser-nous. Subcomando attract --remote
abre un UnixStream blocking, envía EmbedRequest, lee response.
Imprime "embed: local|remote" para ver cuál ruta corrió.
Bug encontrado y corregido:
- ContextBias tenía #[serde(skip_serializing_if = ...)] en sus campos.
Postcard NO soporta skip-condicional en formatos no self-describing:
el serializer omitía bytes que el deserializer esperaba, rompiendo
la wire de cualquier Card con priority_contexts poblada.
Síntoma: "postcard decode: Hit the end of buffer" en el server,
"early eof" en el cliente.
- Fix: removidos los skip_serializing_if de ContextBias. JSON pretty
ahora emite {"pin_to": null, "priority_offset": 0} pero el wire
funciona. Trade-off aceptado.
- Test wirecard_postcard_with_priority_contexts en brahman-card que
ejercita el roundtrip postcard con biases poblados.
Validación end-to-end:
$ ente-zero & nouser-nous-mock & nouser daemon crates/core
$ brahman-status
Sessions (7):
[ente] nouser.nous_mock flows: embed-request, embed-result
[ente] brahman.nouser_engine
[data] src summary: 6 archivos en crates/core/brahman-handshake/src
[data] graph summary: 7 archivos en crates/core/ente-zero/src/graph
...
$ nouser attract --remote crates/core <archivo>.rs
embed: remote
🧲 0.9058 src ...
(mock log: embed_file path=...)
Tests: 75. cargo check --workspace: 0 errores, 0 warnings.
Próximo natural: Phase D-2 — real-nous con ONNX/Llama text-embedding.
Declara la misma Card con priority_contexts.prod = +1 y el swap es
transparente para el consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
77faf12e82 |
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 determinista 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 nuevo:
- EMBED_DIM = 32. Vector:
* dims 0..8: blake3(extension)
* dims 8..16: blake3(parent_dir)
* dims 16..24: blake3(file_stem)
* dims 24..28: tamaño (log + flags)
* dims 28..32: mtime (escala día + features cíclicas)
- Tip clave: hash bytes se centran a [-1, 1] (no [0, 1]). Sin
centrar, dos hashes random tendrían cosine ~0.75 espurio.
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
y lo guarda en MonadManifest.centroid. El centroide viaja al
brahman-status vía DataFacet.centroid.
- bin nouser nuevo subcomando: attract <dir> <file>.
- Scan del dir, embedding del archivo objetivo, ranking de afinidad
contra Mónadas con centroide.
- 🧲 si la mejor supera umbral, · si es mejor pero debajo.
Validación end-to-end:
$ nouser attract crates/core crates/modules/nouser/core/src/embed.rs
🧲 0.9058 [01K..] src (ente-brain/src)
0.8984 [01K..] src (brahman-handshake/src)
...
$ nouser attract crates/core crates/modules/nouser/core/Cargo.toml
0.3427 [01K..] graph (ente-zero/src/graph)
(mejor score 0.3427 < umbral 0.7000 — no se 'pega')
7 tests nuevos en embed (determinismo, normalización, similitud
mismo-dir/mismo-ext, baja entre no-relacionados, centroide
unidad+coherente, attraction picks correctly, vacío skipeado).
Tests acumulados: 73. 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.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
85886b7a3c |
feat(nouser): Phase B-2 — daemon que publica Mónadas al Init brahman
Cierra la unificación ontológica de B-1: 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.
Cambios:
- nouser-core gana deps brahman-card + brahman-sidecar.
- bin nouser nuevo subcomando: daemon <dir>.
1. Spawna sidecar para el engine (brahman.nouser_engine, kind=Ente):
el "ser" que produce y administra Mónadas.
2. Scan + cluster del directorio.
3. Para cada Mónada, monad.to_brahman_card() + sidecar (kind=Data).
Cada Mónada es una sesión brahman propia, con su ULID estable.
4. Park del main thread; 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
...
La función de presentarse es la misma para procesos y datos. UI ve
una lista uniforme y discrimina por `kind` cuando le importa.
Costo conocido: cada Mónada consume thread + tokio runtime
current_thread (legacy del sidecar API). Para escalar a >50 Mónadas
conviene consolidar en un único runtime con N tasks. Defer a B-3.
Pendientes propuestos (en CHANGELOG):
- B-3: consolidar sidecars en un solo runtime.
- C: pseudo-embeddings + atracción por centroide.
- D: módulo nouser-nous para LLM, swappable por priority_contexts.
- Polish: labels con 2-3 componentes de path.
- Crossreferencia: Ente anuncia "procesando Mónada X", Mónada anuncia
"siendo procesada por Ente Y".
cargo check --workspace: 0 errores, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b85700c538 |
feat: Phase B-1 — unificación ontológica de Cards (Ente ↔ Data)
La Card pasa a ser EL protocolo de presentación del ecosistema. 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.
brahman-card:
- CardKind { Ente (default), Data }. Backward-compat: Cards existentes
quedan Ente.
- DataFacet { summary, keywords, centroid, member_count, dispersion,
presentation_hint } — vista liviana para el wire. Listas grandes
(members reales, embeddings completos) se consultan al daemon dueño
bajo demanda.
- Card.kind y Card.data agregados. WireCard espeja, conversiones From
propagan ambos campos.
- Default impl actualizado.
brahman-broker:
- BrokeredCard propaga kind y data desde la Card registrada. No afecta
el matching (sigue por TypeRef + priority + pin_to); permite a
observadores discriminar sin re-query.
nouser-card:
- Depende ahora de brahman-card.
- MonadManifest::to_brahman_card() proyecta una Mónada a Card brahman:
- id, label, lineage directos.
- payload Virtual, supervision Delegate, lifecycle Daemon
(placeholder — la Mónada no se ejecuta).
- kind = Data.
- data = Some(DataFacet { summary, keywords, centroide,
member_count, entropy → dispersion, presentation_hint del Lens }).
- Test nuevo projects_to_brahman_card.
brahman-status:
- Prefijo [ente] o [data] por sesión.
- Sesiones data renderean también summary, members + dispersion,
keywords y lens hint.
Resultado: la UI ve una sola lista uniforme — no necesita saber si
mira procesos o cúmulos de datos, sólo lee el Card y se adapta por
kind. La función de presentarse es la misma para todos.
Tests: 59 (card 11, broker 15, handshake codec+tr 2 + integ 7,
card-wit 4, admin 0, nouser-card 7 +1, nouser-core 13).
cargo check --workspace: 0 errores, 0 warnings.
Próximo: Phase B-2 — bin nouser daemon que sidecarea cada Mónada como
sesión brahman, mezclándolas con los entes en brahman-status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7bdc26e61a |
feat(nouser): Phase A — mecanismo determinista de Mónadas
Primer trozo de Nouser/Kairos: explorador de Mónadas como agrupaciones
semánticas sobre el filesystem, sin tocar IA todavía. Cubre el 90% de
los casos con heurísticas puras.
Crates nuevos:
crates/modules/nouser/card:
- MonadManifest: la Tarjeta de Presentación de una Mónada. Espejo
conceptual de brahman::Card pero para datos: id (Ulid), label,
summary, centroid (vacío en Phase A), keywords, cardinality, entropy
[0,1], dominant_lens (Grid|Code|Gallery|Database|Markdown|Tree),
pins, members, timestamps, extensions (forward-compat).
- Diferencia explícita en docs: brahman::Card describe entidades
runtime con payload/soma/supervision; MonadManifest describe una
agrupación de datos sin proceso atrás.
- Validación: schema_version, label no vacío, entropy en rango,
cardinality consistente con members.len().
- 6 tests (validación + JSON roundtrip).
crates/modules/nouser/core:
- scanner::scan_directory: walkdir → Vec<FileEntry> con metadatos.
Skipea hidden por default; configurable max_depth y follow_links.
- cluster::by_directory: agrupa archivos por parent dir, mínimo 3
para promover a Mónada (configurable). Computa keywords (top-N
extensiones por freq + alfabético), elige Lens dominante por
extensión más frecuente, entropía de Shannon normalizada.
- db::MonadDb: store en memoria con índices BTreeMap.
resolve_members filtra IDs huérfanos.
- bin nouser con subcomandos scan, show, json. Env var
NOUSER_MIN_FILES para 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
Pendientes (anotados en CHANGELOG, no urgentes):
- Phase B: bin nouser daemon que sidecarea a brahman-init.
- Phase C: pseudo-embeddings de metadatos + atracción por centroide.
- Phase D: módulo nouser-nous para el LLM real, swappable por
priority_contexts (mock-nous en test, real-nous en prod).
- Polish: labels con 2-3 componentes del path.
cargo check --workspace: 0 errores, 0 warnings.
Tests acumulados: 58.
CHANGELOG.md actualizado.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
70a7a0d46d |
feat: segundo módulo (nakui) + admin API + brahman-status
Dos cosas en una sesión, en el orden discutido:
(1) Segundo módulo brahman vivo: nakui-core
- crates/modules/nakui/core/Cargo.toml: deps brahman-card,
brahman-sidecar, ulid.
- crates/modules/nakui/core/src/bin/nakui.rs: brahman_card_for_nakui()
construye una Card como Lifecycle::Daemon, Supervision::Restart,
flow.input "command" (json) + flow.output "report" (json). El
cmd_run llama brahman_sidecar::spawn antes de levantar el server
de nakui.
(2) crates/shared/brahman-sidecar (estrena crates/shared/)
Boilerplate del sidecar extraído (DRY): el thread con tokio current
thread runtime, conexión vía Client::connect, ping loop. Yahweh y
nakui ahora consumen este crate. API:
- spawn(card) fire-and-forget
- spawn_with_handle(config) con JoinHandle
Example "presence" útil para demos: módulo dummy con label tomado
del primer arg que se queda vivo hasta SIGTERM.
(3) crates/core/brahman-admin: observabilidad del broker
Socket Unix paralelo en \$BRAHMAN_ADMIN_SOCKET (default
\$XDG_RUNTIME_DIR/brahman-admin.sock). Cada conexión recibe un
StatusSnapshot JSON line-delimited y se cierra. Compatible con nc/socat.
- StatusSnapshot { server, protocol, init_attached, sessions, matches }
- server::AdminServer
- client::query(path)
- example "brahman-status" CLI
(4) Wiring de ente-zero
En primordial_loop, junto al handshake server, ahora también levanta
AdminServer con misma política de degradación grácil.
(5) brahman-broker: BrokeredCard ahora incluye lifecycle. Endpoint y
Match derivan Serialize/Deserialize. Nuevo método cards() expone
iterador de BrokeredCard para que el admin pueda construir snapshots.
(6) brahman-card: re-export pub use ulid::* para que módulos no
necesiten depender de ulid directamente.
(7) yahweh-shell migrado al sidecar compartido. Su brahman_client.rs
pasa de 96 a 53 líneas: sólo declara la Card, delega el spawn.
Demo end-to-end:
$ ente-zero &
$ presence demo.producer &
$ presence demo.consumer &
$ brahman-status
Init: server=0.1.0 protocol=0.1.0 attached=true
Sessions (2):
01KR42TY1J... demo.producer lifecycle=Daemon priority=Normal
01KR42TY1K... demo.consumer lifecycle=Daemon priority=Normal
Matches (2):
demo.producer.in ← demo.consumer.out via Exact
demo.consumer.in ← demo.producer.out via Exact
El broker matchea bidireccional por tipo. El admin lo expone.
Tests: 27/27. cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4d50bfc587 |
chore: absorbe nakui (ERP matemático) en modules/nakui
- crates/modules/nakui/core/: el crate nakui-core (4 bins, tests).
Deps directas (serde, rhai, surrealdb, petgraph, sha2, uuid, tokio,
thiserror v1) — no convertidas a workspace = true en esta pasada.
- crates/modules/nakui/modules/{inventory,sales,treasury}/: datos
declarativos del dominio (nsmc.json, schema.k, morphisms/) que el
crate consume — no son crates.
cargo check -p nakui-core: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
53dbdf0f1d |
chore: monorepo inicial con arje + minga + yahweh absorbidos
Workspace en 4 ejes (core/modules/apps/shared):
- 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 crates *-compat)
- modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST,
minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli)
- modules/ui_engine/: 11 crates de yahweh (libs/{core,theme,bus,providers},
widgets/{tree,splitter,tabs,tiled,container_core,text_input})
- apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer,
image_viewer, yahweh-shell)
- shared_wit/protocol.wit: handshake/lifecycle inicial
Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio
"full", paths intra-workspace de yahweh redirigidos a su nueva ubicación.
cargo check --workspace: 0 errores, 17 warnings (dead code preexistente).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|