2f426b017111ccd82343188daa813e598fbe8509
47 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea074d7d57 |
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) text-input caret blinking: caret_visible bool toggea cada 500ms via cx.spawn loop. _blink_task se mantiene en self para que drop cancele. render dibuja | sólo si focused && caret_visible. 2) yahweh-theme: 5 slots ornament secundario como methods (no fields) derivados de is_dark via ornament_slots() helper: bg_input/bg_button/bg_button_hover/accent_destructive/ bg_destructive_hover. No requiere modificar los 6 presets. 3) MetaApp ornament cleanup: 11 rgb(0x...) hardcoded → slots del theme. Sidebar menu items, list row separator/buttons, icon ✕ delete y su hover, EntityRef selector hovers, form submit button + fallback input bg, confirm modal hint y hovers. Pattern: let X = theme.slot() antes de las closures + move |d| d.bg(X) en hover/when para tomar ownership. Antes MetaApp tenía la paleta principal themed (iter 5) pero el ornament secundario seguía hardcoded. Ahora el theme switcher cambia absolutamente todo el chrome. Tests: 117 verdes. Downstream compila. Smoke nakui-ui: bootstrap OK. Limitación: nouser-explorer todavía hardcoded — próxima iter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fc4da751ca |
feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus
Iter 7 (mini-iter — el text-input ya estaba themed, faltaba el polish de focus visibility). Antes el border era siempre accent_strong y el caret | siempre presente — imposible distinguir cuál input está activo en un form con varios fields. - Border focus-aware: focused → accent_strong; blur → border (slot tenue del theme). - Caret | sólo on focus; sin focus se muestra texto plano (reduce ruido visual). - render usa window.is_focused(focus_handle) para chequear. Sin cambios en API pública. Tests downstream verdes. Limitación: caret estático (no parpadea). Iter futura si emerge la necesidad de animation timer via cx.spawn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8fdef818cc |
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. crates/modules/ui_engine/widgets/theme-switcher/: - pub fn theme_switcher(cx: &mut App) -> impl IntoElement: botón clickable con bg=panel_alt, hover=row_hover, label "Tema: <name> ▸". Al click hace Theme::set(cx, Theme::next_after(current.name)). - 2 tests #[gpui::test]: smoke + verificación de cambio de global. - Dev-dep gpui con test-support. Migración: - nakui-explorer: header pasa a flex_row con label flex_grow + switcher alineado derecha. - yahweh-widget-meta-form (sidebar): mismo pattern en el header "Nakui" del sidebar. Tests stack: 115 → 117. Beneficio: click cambia toda la paleta en vivo. 6 presets disponibles (Nebula, Aurora, Sunset, Flat Dark, Solarized Light, High Contrast) ciclables circularmente. Limitación: TextInput entities tienen colors hardcoded; migrar text_input al theme es iter futura. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5885457628 |
feat(yahweh-widget-meta-form): paleta del chrome migrada a Theme::global(cx)
Iter 5 de integración. MetaApp::render tenía 7 vars con colors hardcoded (bg/panel/border/text/text_dim/accent/accent_active); ahora salen de Theme::global(cx) que el shell instala al boot. confirm_delete_banner usa themed_colors(Warning/Error) para sus colors base. render: - 7 let X = rgb(0x...) → theme.bg_app/bg_panel/border/fg_text/ fg_muted/accent/accent_strong. - toast_div / error_banner: banner() → banner_themed(cx). Firmas internas: - render_sidebar/main/list/entity_ref_selector/form cambian Rgba → Hsla (Background donde aplica para panel). gpui::Div acepta ambos via Into, uso interno no cambia. confirm_delete_banner: - 6 colors hardcoded → themed_colors(Warning) para banner base, themed_colors(Error) para Confirm, theme.bg_panel_alt+fg_text para Cancel. NO migra esta iter (ornament hardcoded para una pasada futura): row hovers, bordes sutiles entre filas, bg de inputs custom, bg de botones del EntityRef selector, color del icon ✕ de delete. Smoke test del binario verificado: bootstrap completo OK, panic esperado en open_window sin display. 115 tests verdes (sin cambio: los tests del widget no acceden al render). Beneficio: theme switcher (cuando llegue) cambia toda la paleta con 1 sola llamada Theme::set(cx, ...). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9632d8c4a7 |
feat(yahweh): theme integration en banner + card + nakui-explorer consume themed
Iter 4 de integración nakui↔yahweh. Los widgets banner y card ofrecen variants _themed(cx, ...) que leen Theme::global(cx). Versiones sin theme preservadas para apps sin theme global. yahweh-widget-card: - Nueva dep yahweh-theme. - pub fn card_themed(cx: &App) -> Div: card() + bg(theme.bg_panel). yahweh-widget-banner: - Nueva dep yahweh-theme. - pub fn banner_themed(cx, kind, msg) -> Div: deriva (bg, fg) según kind + theme.is_dark. Info usa accent del theme; Success/Warning/ Error usan hue fijo (verde/amber/rojo) + lightness flippeada. - pub fn themed_colors(kind, theme) -> (Background, Hsla) helper. - 3 tests nuevos del derivation. nakui-explorer: - Nueva dep yahweh-theme. - main() instala Theme::install_default antes de open_window. - render: 5 vars rgb() locales → theme slots (bg_app/fg_text/etc). - card() → card_themed(cx). banner() → banner_themed(cx). - Accents semánticos del log (seed azul, morphism verde) quedan locales: son señales del dominio, no chrome. Tests: 112 → 115 (+3). Stack intacto. Beneficio: cambiar de Theme refleja en nakui-explorer automático. Próximo candidato: migrar MetaApp (paleta hardcoded de 6 colors). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1be7bf9b1e |
feat(yahweh-widget-card): container card-shape compartido para timeline entries
Iter 3 de 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. crates/modules/ui_engine/widgets/card/: - pub fn card() -> Div: flex_col + px(12) + py(8) + mb(4) + rounded(4) + gap(2). Sin colores (caller decide via builder). - 1 test smoke. nakui-explorer: - Los 2 timeline entry patterns (Seed/Morphism) pasan de ~7 calls a ~3, intención "card with accent" emerge del nombre. Tests stack: 111 → 112. App-agnostic — el widget no impone paleta, permite themes diversos. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
715cf6be03 |
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): div con bg + text
colored según severidad. Antes duplicado con colores hardcoded en
cada consumer.
Crate nuevo crates/modules/ui_engine/widgets/banner:
- pub enum Banner { Info, Success, Warning, Error } con bg()/fg()
hardcoded por variant.
- pub fn banner(kind, message) -> Div: padding/text_size defaults;
caller compone con .child()/.px()/.on_click()/etc.
- 2 tests sanity (no color collisions).
Migración:
- yahweh-widget-meta-form: 12 líneas hardcoded → 2 llamadas a
banner().
- nakui-explorer: error banner usa banner() + override de
padding custom del header.
Tests stack: 109 → 111 (+2). Cada crate compila individualmente.
Próximo natural: confirm_delete_banner (Warning + botones) puede
extraerse como modal-banner cuando emerja segundo consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3e4278d766 |
feat(yahweh): MockBackend público + tests E2E del widget con TestAppContext
Cierra el ciclo de testabilidad del widget metainterfaz. Hasta ahora los tests del MetaBackend trait vivían como impl privada en backend.rs; el widget no podía testear handlers sin levantar NakuiBackend (que depende de event log + Rhai). yahweh-meta-runtime: - Nuevo `pub mod testing` con MockBackend (renombre del MemBackend privado, ahora público). Constructores: new(), with_records(iter), with_morphism(name, handler) builder. Métodos de inspección total_records / records_for. Bajo `pub mod testing` (no cfg test) para que crates downstream lo usen en sus dev tests. - Tests del trait en backend.rs simplificados: usan MockBackend en vez del MemBackend duplicado. 8 backend.rs + 9 nuevos del mock. yahweh-widget-meta-form: - Dev-dep nueva: gpui con feature "test-support" (TestAppContext). - MetaApp::apply_action ahora pub (era privado). Necesario para invocar handlers desde tests E2E. - Nuevo tests/widget_with_mock_backend.rs con 4 tests #[gpui::test]: meta_app_constructs, open_view_action_does_not_panic, backend_state_visible_from_widget_perspective, morphism_handler_can_be_registered_and_called_via_widget. Tests: 47→56 yahweh-meta-runtime, 3→7 yahweh-widget-meta-form. Total stack 109 verdes. Limitación: render() no se invoca (requiere window context más rico). Tests verifican state machine, no pixels. Snapshot tests serían scope futuro. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d5ef7144b5 |
feat(yahweh-meta-runtime): promover short_hash y preview_value desde nakui-explorer
Continúa la integración de apps nakui al stack yahweh. Los helpers visuales que nakui-explorer tenía locales y son reusables suben a yahweh-meta-runtime/format. yahweh-meta-runtime: - short_hash(h: &[u8; 32]) -> String: hex de los primeros 4 bytes. - preview_value(v: &Value, max: usize) -> String: JSON one-liner truncado con "..." (edge case max < 3 sin panic). - 5 tests nuevos. nakui-explorer: - Nueva dep yahweh-meta-runtime. - Borrado helpers locales (short_uuid + short_hash + preview_value) + 4 tests duplicados. - Imports desde yahweh-meta-runtime. Tests: 42→47 yahweh-meta-runtime, 7→3 nakui-explorer (los 3 que quedan son específicos del explorer: load_log, breakdown, missing_file). Resto intacto. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b05de24c24 |
refactor(nakui-core): KCL → Nickel — kcl_wrapper reemplazado por evaluación in-process
Cierra el plan original. 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 usa el brazo de cards). Los 3 schemas de sales/inventory/treasury migran de .k a .ncl. nakui-core: - Nueva dep nickel-lang = "2.0.0". - Borrado kcl_wrapper.rs. - Nuevo nickel_validator.rs con vet(schema_path, state, entity) que evalúa `let bundle = (import "<schema>") in (std.deserialize 'Json m%%"<json>"%%) | bundle.<entity>`. - executor.rs: KclError → NickelError, KclPre/Post/PostCreate → SchemaPre/Post/PostCreate, kcl_check → validate_entity. build_schema_bundle ahora emite `(import "X") & (import "Y") & ...` en lugar de concatenar bytes (cada .ncl es expresión completa). - manifest.rs: default schema "schema.ncl", extract_schema_names reescrito para sintaxis Nickel record (CapitalCase keys con 2-space indent). Schemas migrados: - sales/schema.ncl: Venta con std.contract.Sequence [record, 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 antes de que el value populate el record; documentado en cada schema. - inventory/schema.ncl, treasury/schema.ncl: idem. - 3 schema.k viejos borrados; sales/nsmc.json paths actualizados. Tests: refs Kcl* renombradas; paths .k → .ncl; tests inline que escribían schema.k cambian a schema.ncl con sintaxis Nickel. 84 tests verdes en nakui-core. Doc-only borrados: - crates/core/ente-card/schema/card.k (REFERENCE ONLY). - crates/core/ente-brain/schema/rule.k (REFERENCE ONLY). Beneficios: sin dep externa al binario `kcl` (build CI limpio), errores Nickel en línea con caret pointing al field, mismo motor que cards (una dep para todo el repo), sin tempfile JSON intermedio. Cierra el plan original yahweh + KCL + card.k. Pendientes salen de nuevo trabajo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b3a99f38dc |
refactor(yahweh): Fase 2c — widget render extraído a 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 nakui-ui y pasa a un crate yahweh nuevo, genérico sobre MetaBackend. crates/modules/ui_engine/widgets/meta-form/ (yahweh-widget-meta-form): - pub MetaApp<B: MetaBackend> con todo el state UI + impl Render + handlers + render_*. El bound `B: MetaBackend` se propaga. - pub fn new(modules, backend, initial_toast, initial_error, cx): constructor sin bootstrap. Caller pre-construye todo. - Helpers locales del widget: lookup_field, append_compact_msg, format_seed_toast. - Cero deps a nakui o brahman-cards. Reusable por cualquier app. - 3 tests funcionales puros (lookup_field, append_compact_msg, format_seed_toast). nakui-ui (shell): - main.rs: 1959 → 424 líneas (78% reducción). Sólo bootstrap: load_ui_modules + load executors + NakuiBackend::open + cx.open_window con MetaApp::<NakuiBackend>::new como root view. - Dep nueva yahweh-widget-meta-form. - Tests E2E quedan: event_log_replay, morphism_pipeline real sales, load_ui_modules x3 (4 shell). NakuiBackend tests siguen en backend.rs (8). Widget tests en su crate. Distribución total tests refactor yahweh: - yahweh-meta-schema: 8 - yahweh-meta-runtime: 42 - yahweh-widget-meta-form: 3 - brahman-cards: 26 - nakui-ui: 12 (4 shell + 8 backend) Total: 91 tests cubriendo el área. Cada crate compila individualmente. Fase 3 (shell mínimo) era implícita en F2c — el shell ya quedó en 424 líneas. Pendientes restantes: KCL → Nickel, eliminar card.k. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2462aca444 |
refactor(yahweh): Fase 2b — MetaBackend trait + NakuiBackend + MetaUi consume el backend
3 steps en un commit:
A) yahweh-meta-runtime/backend.rs: trait MetaBackend con 6 métodos
(list_records, load_record, seed, update, delete, morphism) +
WriteOutcome { id, changed, post_status }. 9 tests con MemBackend.
B) nakui-ui/backend.rs: NakuiBackend struct con store/log/executors/
compaction. NakuiBackend::open() compone log+snapshot+replay+tick;
impl MetaBackend mapea cada método al pipeline nakui-core.
snapshot_path_for / maybe_compact_log se mueven acá. 7 tests del
impl.
C) MetaUi consume el backend:
- 6 fields colapsan en `backend: NakuiBackend`.
- MetaUi::new pasa de ~150 líneas a ~10 (delega a NakuiBackend::open).
- commit_seed / commit_morphism / commit_delete delegan al trait;
CommitOutcome enum eliminado, reemplazado por WriteOutcome.
- tick_runtime_compact eliminado (interno al backend; el msg sale
por WriteOutcome.post_status).
- validate_entity_refs callsite usa cierre sobre backend.load_record.
- Imports nakui_core::delta y event_log salen de main.rs (sólo
quedan en tests E2E).
Tests: 33→42 yahweh-meta-runtime (+9 trait), 14→21 nakui-ui (+7
backend impl). 97 totales en el área. Cada crate compila individualmente.
Pendiente Fase 2c: extraer widget render (form/list/modal/EntityRef)
al crate yahweh — ahora trivial porque el render solo consume
&self.modules + self.backend (via trait).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6104484498 |
refactor(yahweh): Fase 2 — extraer helpers puros a yahweh-meta-runtime
Sigue de la Fase 1 (lift del schema). Ahora los helpers puros que cualquier widget renderer o backend ejecutor consume sobre el schema viven en yahweh-meta-runtime. Sin GPUI, sin nakui — usa cierres en lugar de traits para decoupling máximo. Crate nuevo crates/modules/ui_engine/libs/meta-runtime: - parse.rs: parse_field_value, infer_param_value, resolve_param_value. - delta.rs: compute_field_delta, compute_clear_fields. - refs.rs: validate_entity_refs(load: F, refs) con cierre Fn(&str, Uuid) -> Option<Value> en vez de trait Store. - format.rs: human_label_for_record, render_value, value_to_input_text, short_uuid. - 33 tests propios. nakui-ui: - Nueva dep yahweh-meta-runtime. - Borrado código local equivalente (~200 líneas) + 34 tests duplicados. - validate_entity_refs callsite usa cierre: validate_entity_refs(|e, id| store.load(e, id), &refs). - 14 tests runtime-específicos quedan (compact/snapshot/event-log/ morphism pipeline/load_ui_modules). Distribución tests: 48 → 14 nakui-ui; +33 yahweh-meta-runtime. Cada crate afectado builds + tests limpio individualmente. Workspace build full no completó esta corrida por OOM al compilar surrealdb-core (ambiental, no relacionado). Fase 2b pendiente: extraer render widgets (form/list/modal/ EntityRef selector) a yahweh — requiere diseñar MetaBackend trait. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f5987d9cfc |
refactor(yahweh): Fase 1 — nakui-ui-schema → yahweh-meta-schema
Primer paso del refactor yahweh. El schema de UI declarativa no tiene acoplamiento real con Nakui (sólo dep en serde/thiserror) — movemos a yahweh para que cualquier app metadata-driven lo use sin pasar por nakui. Mecánico: - git mv crates/modules/nakui/ui-schema → crates/modules/ui_engine/libs/meta-schema. - Crate name: nakui-ui-schema → yahweh-meta-schema. - Workspace members[] actualizado (sección yahweh, no nakui). - Consumers actualizados: brahman-cards (Cargo.toml + lib.rs + readers.rs), nakui-ui (Cargo.toml + main.rs). - Self-test (example_modules.rs): import + path rebase (5 niveles arriba ahora). Documental: - Doc del crate ahora dice "metainterfaz (yahweh meta-schema)" + "backend-agnostic" en filosofía. - Module.nakui_module_dir documentado como "path opaco al backend"; se conserva el nombre por compat con módulos ya escritos + serde alias "backend_module_dir" para futuro rename suave. Tests: 13 yahweh-meta-schema + 26 brahman-cards + 48 nakui-ui verdes. Workspace build verde. NO hace Fase 1: mover widgets a yahweh (Fase 2), trait MetaBackend (Fase 3), renombrar nakui_module_dir. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f0c0a71860 |
feat(nakui-core,nakui-ui): FieldOp::Clear — borrar values vía form vacío
Nueva variante semántica del kernel: Clear { path } remueve la key
del record, distinta de Set { value: Null } (que deja la key con
valor literal null). Habilita "limpiar" un field optional vaciando
el input en la UI.
nakui-core:
- delta::FieldOp::Clear + simulate_on + capability_token (mismo
shape que Set: "entity.field").
- MemoryStore::apply_dry_run y apply: Set/Clear comparten
pre-condition (record existe + es objeto). Clear de field
ausente = no-op silencioso.
- SurrealStore: equivalente con `UPDATE ... UNSET <field>`.
- Executor capability check: Set/Clear comparten match.
- Conservation rules NO consideran Clear (sólo Set) — documentado
como morphism-author responsibility.
nakui-ui:
- commit_seed acumula `to_clear: Vec<String>` con optional empties
en lugar de `continue` silencioso.
- EDIT branch: nuevo helper compute_clear_fields filtra a sólo los
fields con current value non-null. Combina Set + Clear ops.
NoChange ahora requiere ambos vacíos. Log entry incluye
`cleared: [...]` sólo si non-empty. Updated.changed cuenta
sets+clears.
Tests: +7 en nakui-core (4 store + 3 delta), +3 en nakui-ui.
Suites: 34/34 nakui-core, 40/40 nakui-ui. Workspace build verde.
E2E del morphism real intacto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|