3f8a3ea4b6d3de529e42ae418d486bc81cca308e
235 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
df089f0585 |
feat(brahman-cards): templates Nickel canónicos para cada body kind
Materializa el patrón "import + override" del brazo. Hasta ahora BRAHMAN_CARDS_TEMPLATES_DIR existía como mecanismo pero el repo no shippeaba ningún template. 3 templates basic bajo crates/core/brahman-cards/templates/: - ente_basic.ncl: Card runtime mínima (Virtual + OneShot). - monad_basic.ncl: Mónada con metadata vacía. - ui_module_basic.ncl: descriptor UI con entities/menu/views vacíos. Cada field override-able marcada `| default` (sin eso Nickel rebota merge de strings/numbers no-iguales). Nuevo `pub fn canonical_templates_dir() -> PathBuf` resuelve el dir via CARGO_MANIFEST_DIR. Para distribución del binary standalone queda como pending (include_dir! o convención de install path). 5 tests E2E que cubren los 3 templates con import+override, sanity del default sin override, y existencia física del dir. Tests brahman-cards: 26 → 31 (+5). Workspace 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> |
||
|
|
f6361bbdca |
feat(nakui-ui): migrar consumer al brazo brahman_cards::load_cards_from_dir
Primera consumer migration del brazo. nakui-ui ya no llama a nakui_ui_schema::load_modules_from_dir directamente; pasa por brahman_cards::load_cards_from_dir y extrae UiModule del CardBody. Beneficios concretos: - Soporta .ncl además de .json (templates Nickel + merge funcionan en cualquier subdir de modules). - Cards de otros body kinds (Ente/Monad) se skipean limpio con toast informativo, no rompen la carga. Cambios en brahman-cards: - Nuevo load_cards_from_dir(dir) + variante con readers/filenames custom. DEFAULT_CARD_FILENAMES = [card.ncl, card.json, module.ncl, module.json] (orden de prioridad). - 4 tests nuevos del helper. Cambios en nakui-ui: - Nueva dep brahman-cards. - Helper load_ui_modules(dir) -> (Vec<Module>, Vec<String>) envuelve el brazo, filtra a UiModule, aplica Module::validate(), detecta duplicate ids. - MetaUi::new usa el helper, emite toast con cards skipped si las hay. - 3 tests e2e nuevos. 26/26 brahman-cards verdes (+4). 48/48 nakui-ui verdes (+3). Workspace build verde. nakui_ui_schema::load_modules_from_dir queda intacto (sus tests lo usan + otros consumers futuros pueden preferirlo). Migración opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2a4443790c |
feat(brahman-cards): Nickel reader + templates con merge nativo (V2)
El brazo ahora acepta `.ncl`: evalúa via nickel-lang 2.0, exporta a JSON, dispatcha por los readers JSON estándar. Templates funcionan con import + & merge nativos de Nickel — el brazo no inventa mecánica paralela. - Dep nickel-lang = "2.0.0" (interfaz estable). - Nuevo módulo nickel_eval con eval_nickel_file(path) -> Value y errores tipados (Io/Eval/Export/JsonReparse). Mensaje de Nickel como texto plano sin ANSI. - load_card_with añade arm "ncl" simétrico al "json". - CardLoadError::Nickel propaga el error limpio. - Imports resueltos: parent dir del input + env BRAHMAN_CARDS_TEMPLATES_DIR (registry global, opcional). - Convención obligatoria documentada: fields override-ables del template usan `| default` (sin eso Nickel rechaza el merge). 9 tests nuevos: eval directo, dispatch a UiModule/Ente, template merge con id+label override, registry via env, error wrapping, contract violation en eval-time (`id | String = 42`), shape desconocida. 22 tests totales en brahman-cards (13 JSON V1 + 9 Nickel V2). Workspace build verde. NO hace: migrar consumers, set canonical de templates, KCL→Nickel — todos para commits siguientes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
09501911b7 |
feat(brahman-cards): brazo unificado V1 — readers JSON + estructura canónica
Pivote arquitectónico: Brahman maneja varios formatos legítimos de
"Card" (cada uno en su crate origen, shape preservado), y un único
brazo los lee y proyecta a UNA estructura interna canónica que
consumen UI runtime / storage / DHT / wire. Agregar formato nuevo
= agregar reader, sin tocar consumers.
Crate nuevo `crates/core/brahman-cards/`:
- Card { id, schema_version, lineage, label, extensions, body }:
wrapper común con identidad legible. PartialEq omitido porque
MonadManifest y nakui_ui_schema::Module no lo implementan.
- CardBody enum tagged: Ente(brahman_card::Card), Monad(MonadManifest),
UiModule(nakui_ui_schema::Module). Convención: agregar variant +
reader; consumers hacen `match { Ente(..) => ..., _ => skip }`.
- trait CardReader { name, can_read(&Value), read(Value) }.
- 3 readers: EnteJsonReader (payload+supervision), MonadJsonReader
(members+cardinality), UiModuleJsonReader (entities+views+menu).
- Entry points load_card / load_card_with. Errores tipados.
13 tests integration: detection x3, dispatch+projection x3,
negative cases x2, sanity de orden, e2e desde disco, unsupported
extension, custom reader set, documented invariant.
13/13 verdes. Workspace build verde.
V1 NO hace (explícito): Nickel reader, templates, migración de
consumers, yahweh refactor, KCL→Nickel — todos en commits siguientes
para mantener este aislado.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ffdfa6f8d7 |
feat(nakui-ui): validación cross-field del EntityRef (existence en store)
parse_field_value(EntityRef) sólo validaba forma (UUID parseable). Un UUID válido pero inexistente pasaba al log/store, dejando dangling references. Ahora validamos también existencia contra la entity declarada en FieldSpec.ref_entity. - Nuevo helper validate_entity_refs<S: Store>(store, refs): fail-fast loop sobre (label, target_entity, uuid) tuples; primer record ausente → error con label legible + UUID corto. - commit_seed: durante el parse loop encolamos cada EntityRef + ref_entity + UUID parseado. Después del loop, una sola toma del store lock valida todos. Falla early: ningún log entry. - Cobertura: SEED + EDIT. Morphism inputs ya cubierto por Executor::compute (load + EntityMissing) — documentado en el doc del helper. 5 tests nuevos del helper: happy path, fail-fast con detalles, label en msg, lista vacía, distingue target entity. 45 tests verdes en nakui-ui. 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>
|
||
|
|
613f4f299e |
feat(nakui-ui): snapshot/compaction durante runtime cada N writes
El compact ya no es sólo en boot: en runtime, después de cada write efectivo (commit_seed Created/Updated, commit_morphism, commit_delete), incrementamos un contador en memoria y disparamos maybe_compact_log cuando alcanza el threshold (mismo NAKUI_SNAPSHOT_THRESHOLD del startup). El log no crece sin tope en sesiones largas. - Nuevos fields en MetaUi: snap_path, snapshot_threshold, writes_since_compact (los dos primeros cacheados en new()). - Nuevo método tick_runtime_compact: increment + check + maybe compact + reset. Si compact falla, counter NO se resetea (próximo write reintenta). Threshold 0 desactiva. - Helper append_compact_msg concatena el msg de compact al toast del op original con "; " separator. - Wireado en los 3 callsites de write. NoChange (edit sin cambios) no cuenta — preserva "1 write = 1 log entry = 1 tick". 2 tests nuevos: format del helper, ciclo de 7 writes con threshold=3 verifica 2 compacts + counter residual + log final con 1 anchor + 1 write. 37 tests verdes (+2). Workspace build verde. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9951307fd9 |
feat(nakui-ui): atajo Esc para cancelar el modal de delete
`capture_key_down` en el root div: si event.keystroke.key=="escape" y hay pending_delete, lo limpia y emite toast "delete cancelado (X) [esc]". Capture phase (no bubble) intercepta el Esc antes de que cualquier TextInput descendiente lo consuma. Sin pending el handler es no-op, el evento sigue su flujo. Hint visual en el banner: subtítulo amber tenue "Esc para cancelar · click [Confirmar] para borrar" para que el usuario descubra el atajo sin RTFM. 35 tests verdes. El handler son 8 líneas no-testeables sin GPUI cx; type-check garantiza wireup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bcccf3b953 |
feat(nakui-ui): EntityRef validation en parse_field_value (UUID al submit)
Antes: parse_field_value(EntityRef, raw) devolvía Ok(json!(raw)) blindly. Garbage entraba al log/store si el field se usaba como seed o param (sólo el path de morphism inputs validaba UUID). Ahora: Uuid::parse_str(raw.trim()) → error claro si falla, value trimmed si pasa. El selector clickable garantiza happy path; este check es defensivo contra paste manual o garbage tipeado. 5 tests nuevos: happy path, trim de whitespace, rechazo de garbage, rechazo de empty, propagación de error a resolve_param_value con label del FieldSpec en el mensaje. 35 tests verdes. E2E del morphism real intacto (sus inputs van por path dedicado, no por parse_field_value). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7e2be96a57 |
feat(nakui-ui): snapshot/compaction automático del event log al startup
Wirea el snapshot machinery existente en nakui-core (Snapshot, replay_with_snapshot_into, EventLog::compact_through) al runtime de la UI. Reduce el costo de boot de O(events) a O(events_post_snapshot). - Path: sibling del log, extensión `.snap.json`. - Threshold via `NAKUI_SNAPSHOT_THRESHOLD` env (default 50; 0 = off). - Helper `maybe_compact_log` captura snapshot + compacta dejando la última entry como anchor del cursor (sin anchor, EventLog::open vería log vacío y perdería next_seq). - WAL order: write snap antes de compactar; un crash entre los dos da el mismo outcome al próximo boot (replay skipea entries cubiertas por snap). - Fail-soft: snap load/compact errors no son fatales, sólo banner. 5 tests nuevos: derivación del path, dos no-ops bajo threshold, E2E 60 entries → snapshot+compact → reopen+replay → 60 records intactos. 31 tests verdes en nakui-ui. Workspace build verde. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
70f8c66548 |
feat(nakui-ui): edit delta-only — sólo campos modificados al log/store
Antes: editar un record emitía Set por *todos* los fields del form,
incluso los no tocados. Bloataba el log y oscurecía el intent. Ahora:
- Nuevo helper `compute_field_delta(current, proposed)` — devuelve
sólo las entries que difieren (PartialEq de Value).
- Nuevo enum `CommitOutcome { Created, Updated{changed}, NoChange }`
para que el toast sea preciso ("actualizado X (2 campo(s))" vs
"sin cambios — no log entry").
- `commit_seed` en path EDIT carga current del store, calcula delta,
return early si vacío (no log entry, no apply). Si no vacío emite
`Morphism{ ui.edit_record, params.fields=delta }` con sólo los
campos modificados.
5 tests nuevos del helper: delta vacío, sólo campo cambiado, current
Null = todo nuevo, int vs string, ignora fields ausentes del proposed.
27 tests verdes. SEED path inalterado, E2E del morphism real verde.
Limitación: edit no puede *clearear* un value vaciando el input
(empty optional fields ya hacían `continue` antes del delta). Para
soportar eso haría falta `FieldOp::Clear`, no necesario hoy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fdb9bbe058 |
feat(nakui-ui): confirmación de delete vía banner modal antes de borrar
El click en `✕` ya no borra inmediatamente: marca el record como
pendiente y abre un banner amber al tope con [Cancelar] / [Confirmar].
Sólo [Confirmar] llama `commit_delete` (la lógica del store/log no
cambia).
Cambios:
- Nuevo `MetaUi.pending_delete: Option<(String, Uuid)>`.
- Click en ✕ → set pending_delete + clear toast.
- Banner renderea como sibling del row sidebar+main en `flex_col`
raíz; None cuando no hay pending. Texto: "¿Borrar {Entity} {id}?".
- [Cancelar] → toast informativo, sin tocar el store.
- [Confirmar] → limpia pending primero (evita banner colgado en
caso de error) y llama `commit_delete`.
- `select_view` también limpia pending (record podría no ser visible
en la nueva view).
22 tests verdes — la lógica del store/log no cambió, sólo el state
machine de UI. La compilación garantiza el wireup de las closures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
46185951d0 |
feat(nakui-ui): validación estricta de params del morphism vía FieldKind
Reemplaza la heurística `infer_param_value` por parseo estricto basado en el `FieldKind` declarado en el `FieldSpec` correspondiente. Un Boolean con value "abc" ahora rebota en la UI con mensaje claro en lugar de fallar opacamente dentro del morphism Rhai. Cambios: - Nuevo helper `resolve_param_value(field_name, raw, spec)`: - Required + empty → error con label legible. - Optional + empty → Value::Null. - Spec presente → parse_field_value(spec.kind, raw) estricto. - Spec ausente (módulo mal-formado) → fallback a infer_param_value. - `commit_morphism` simplificado: el loop de params ahora delega al helper, que es testable sin GPUI. - 6 tests nuevos cubriendo: número estricto, boolean rechaza "abc", required vacío, optional vacío → null, fallback a infer sin spec. 22 tests verdes (+6 nuevos). E2E del morphism real `morphism_pipeline_executes_real_sales_vender` sigue verde — la validación estricta no afecta el path correcto, sólo agrega rebotes tempranos a values mal-tipados. 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.
|
||
|
|
170d1f890a |
feat(nakui-ui): edit + delete de records (ciclo CRUD completo)
Cierra "no hay UI para editar/borrar" del commit anterior. Cada fila
de la lista gana dos botones (edit, delete); el form view se reusa
para alta y para edit; el delete es inline. Las mutaciones pasan por
LogEntry::Morphism con sus ops, asi el replay restaura el estado
correcto.
Cambios:
- MetaUi.editing: Option<(String, Uuid)> nuevo. Set al click ✎,
cleared al cambiar de view o tras submit.
- open_edit(mod_idx, entity, id, cx): setea editing, busca primer
Form view del modulo cuya entity matchee, navega ahi.
- select_view extendido: cuando carga un Form, si editing matchea y
el record existe, pre-llena cada input con value_to_input_text del
record (inverso de parse_field_value).
- commit_seed ramifica:
- Edit path: emite LogEntry::Morphism { name: "ui.edit_record",
ops: [Set per field] }. Aplica via store.apply.
- Seed path: alta nueva (comportamiento previo).
- commit_delete(entity, id): emite LogEntry::Morphism { name:
"ui.delete_record", ops: [Delete] } + apply.
- Render del form: titulo y submit label cambian segun editing
("Editar customer abc..." / "Guardar cambios").
- Render de la lista: dos columnas nuevas — id, acciones. Cada fila
con ✎ (edit, accent) y ✕ (delete, rojo) + hover states.
Coherencia con el modelo: todo cambio post-seed pasa por ops dentro
de Morphism. nakui-explorer muestra estos morphisms con sus ops en
la timeline.
Trade-offs:
- schema_hash: None sigue (legacy path) hasta Action::Morphism
wireé Manifest.
- Delete sin confirmacion (1 click).
- Edit sobreescribe todos los campos del form (no delta-only).
Tests: 3 nuevos. 10 totales:
- value_to_input_text_inverse_of_parse + round_trip — la propiedad
del pre-llenado.
- event_log_replay_handles_full_crud_cycle — E2E: seed + edit +
delete via log, replay desde cero deja store vacio. Replay parcial
deja valores editados.
Activacion:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
|
||
|
|
d60ee5eab2 |
feat(nakui-ui): persistencia con event log + replay al startup
Cierra "sin persistencia entre runs" del commit anterior. Cada
SeedEntity se appendea al nakui_core::event_log::EventLog con WAL
semantics (log antes que store) y al re-abrir el binario el replay
reconstruye el MemoryStore desde cero. Cerrar y volver a abrir ya
no borra el data.
Cambios:
- MetaUi.event_log: Option<Arc<Mutex<EventLog>>> nuevo. Compartido
bajo Mutex para que commit_seed pueda mutar.
- Apertura + replay al startup: path por env NAKUI_EVENT_LOG, default
./nakui-ui-state.jsonl. EventLog::open + replay_into reconstruyen
el store. Toast: "log nuevo" o "log X cargado: N evento(s)
replayed".
- WAL en commit_seed: log.append(LogEntry::Seed { ..., schema_hash:
None }) antes de store.seed. Si append falla, cancela operacion.
- schema_hash: None es el path "legacy / pre-versioning" documentado
para seeds que no pasan por Manifest+Executor. Correcto para alta
via metainterfaz hasta que Action::Morphism wire el Manifest.
- Degradacion gracil: si abrir log falla -> toast error + sigue
in-memory.
Tests: 1 nuevo E2E event_log_replay_restores_memory_store que escribe
2 seeds via EventLog::append, re-abre + replay_into store fresh,
verifica records con values correctos. 7 tests verdes en nakui-ui.
Activacion con persistencia:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
Pendientes:
- Action::Morphism (cargar Manifest junto a Module).
- Snapshot/compaction para logs grandes.
- UI para editar/borrar records existentes (hoy solo alta).
- Widget input simple sin selection/IME/clipboard.
|
||
|
|
5d584ff815 |
feat(nakui-ui): inputs reales + click handlers funcionales
Cierra dos limitaciones documentadas del commit anterior: los
formularios ahora aceptan teclado real, y los clicks en menus +
botones mutan estado correctamente.
Cambios:
- Cada FieldSpec del Form materializa un Entity<TextInput> de
yahweh-widget-text-input al entrar a la vista. Los entities se
reemplazan al cambiar (drop limpio). Soporta: escribir caracteres,
Backspace, Enter (Confirmed event no usado todavia), Escape.
Cursor renderea como "|" al final.
- Click handlers wired via cx.listener: menus invocan select_view,
botones invocan apply_action. Tienen acceso real al
Context<MetaUi> y mutan el modelo + cx.notify.
- commit_seed reemplaza el buffer ad-hoc por
input.read(cx).text() por cada field. El value parseado va al
MemoryStore con tipo correcto.
- Reset de inputs tras submit (set_text("")) si no hay next_view —
flujo de alta consecutiva sin re-tipear.
- Hover states en sidebar y botones.
- Theme::install_default(cx) al inicio (requerido por text_input).
Wire: deps nuevas yahweh-widget-text-input + yahweh-theme.
Limitaciones que siguen:
- Action::Morphism: requiere cargar Manifest de nakui-core.
- Sin persistencia entre runs (wire con EventLog cuando daemon Nakui
exista).
- Widget input es simple (sin cursor positioning, selection, IME,
multilinea, copy/paste).
- Enter no envia (TextInputEvent::Confirmed no suscrito; submit va
por click). Trivial de wirear si se necesita.
Tests: 6 unit verdes. Visual requiere cargo run + manual.
Activacion:
NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui
|
||
|
|
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.
|
||
|
|
5b8f71e0de |
feat(nakui-explorer): nuevo binario GPUI — Nakui visible en la interfaz
Cierra "nakui no tiene UI propia" del audit. Nuevo binario standalone nakui-explorer (paralelo a nouser-explorer) que renderea el event log de un repo Nakui: timeline scrollable de seeds + morphisms con sus parametros, breakdown por entity type, polling cada 2s para detectar nuevos eventos appended sin restart. Diseno: lee directamente el archivo .jsonl del nakui_core::event_log:: EventLog. Path por env NAKUI_EVENT_LOG, default "nakui.jsonl" en pwd. Sin discovery via broker brahman porque nakui hoy es CLI/library/demos, no daemon. Cuando se daemonice, sustituir el lector de archivo por un sidecar consumer (mismo patron que nouser-explorer). UI: - Header: path, count total + breakdown seeds/morphisms, reload time. - Breakdown line: top 5 buckets por frecuencia (entities + morphisms). - Timeline: tarjetas color-coded por kind (azul=seed, verde=morphism) con #seq, kind, entity/morphism, id corto, preview de data/params, schema hash o "(legacy)". Mas-recientes-primero, hasta 200 visibles. - Error banner si lectura falla; el explorer no crashea, sigue intentando cada 2s. Wire: nuevo crates/apps/nakui-explorer/ agregado al workspace. Deps minimas: nakui-core, gpui, serde_json, uuid (feature serde). Sin deps de brahman (Nakui standalone). Tests: 7 unitarios cubriendo load_log (in-order, missing-file), breakdown (counts + buckets), preview_value (truncate + intact), short_uuid + short_hash. Activacion: NAKUI_EVENT_LOG=/tmp/nakui_inv_xxx.jsonl cargo run -p nakui-explorer Estado del CHANGELOG global tras este commit: cero pendientes fundamentados activos. Lo unico que queda es minga-vfs (FUSE, explicitamente diferido) y mejoras nice-to-have (cobertura adicional per-lenguaje, daemon-izacion de nakui para sidecar discovery). |
||
|
|
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).
|
||
|
|
f9a3c33586 |
feat(brahman-handshake): multi-key identity — rotacion de session sin perder peer_id logico
Cierra el ultimo pendiente del plan de red P2P. Hasta ahora, rotar la keypair libp2p de un nodo cambiaba su peer_id, lo que invalidaba todas las allowlists/denylists remotas que lo referenciaban. Imposible rotar sin coordinar con todos los pares. Solucion: separar identity master (Ed25519 persistente forever, identifica al nodo como entidad logica) de session libp2p (Ed25519 efimera, rotable). El master firma certs de session con expiracion. La politica de admision se evalua contra el master_peer_id del cert — el session peer_id puede cambiar libremente sin tocar allowlists. API nueva en brahman_handshake::identity: - Identity::from_keypair / master_peer_id / issue_session_cert. - SessionCert::verify devuelve (master_peer_id, session_peer_id). - SessionCert::verify_against_session(expected_session_pk) verify + exige que el cert vincule esa session pubkey (previene reuso de certs ajenos). - CertError tipado: UnknownVersion, DecodeMaster, DecodeSession, InvalidSignature, Expired, SessionMismatch, Sign. - DEFAULT_SESSION_TTL = 24h. SESSION_CERT_VERSION = 1 documenta esquema; bump al cambiar canonicalizacion. Wire: - Hello.identity_cert: Option<SessionCert> agregado (default None, back-compat). - Client::connect_with_stream_signed_with_cert variante que adjunta cert. - network::connect_libp2p_with_cert paralelo a connect_libp2p. Server (do_handshake): nuevo paso ANTES del policy gate. Si el Hello trae cert, verify_against_session(&hello.signature.public_key) y el logical_peer = master_peer_id derivado. Sin cert (path Fase 3), logical_peer = expected_peer (compat). Cert invalido -> Unauthorized antes de evaluar policy. Migracion gradual: clientes sin cert siguen funcionando contra servers con policy basada en session peer_ids. Tests: 8 unit en identity::tests (issue+verify, mismatch, expired, tampered sig/expires_at, unknown version, rotated_session_with_same_ master_yields_same_master_peer_id — la propiedad fundamental). E2E definitivo identity_cert_allows_session_rotation_without_policy_ change: A allowlist[master_peer]; B conecta con session1+cert -> admitido; B rota a session2!=session1 con cert nuevo del MISMO master -> admitido SIN tocar la allowlist; sanity: session sin cert es rechazada. 40 tests verdes en brahman-handshake + brahman-net. Wire en Arje queda como follow-up: ente-zero es server-only y no necesita identity (su keypair libp2p ya es estable). La API esta lista para cuando algun modulo haga conexiones salientes con cert. |
||
|
|
7a0481962e |
feat(brahman-net+handshake): swarm-level deny via libp2p block_list
Optimizacion de seguridad: la denylist ya no espera al handshake brahman para rechazar — ahora se proyecta al block_list behaviour del swarm libp2p. Conexiones desde peers baneados son rechazadas ANTES del Noise handshake, ahorrando el round-trip TCP+Noise por cada intento denegado. brahman-net: - Nuevo behaviour block_list: allow_block_list::Behaviour<BlockedPeers> añadido al BrahmanBehaviour derivado. Default vacio. - Nuevos comandos BlockPeer / UnblockPeer en el enum interno. - API publica: BrahmanNet::block_peer / unblock_peer. Idempotentes. - Dep nueva: libp2p-allow-block-list 0.6 (sub-crate, no es feature de libp2p en 0.56). brahman_handshake::peer_policy: - PeerPolicy gana net: Arc<RwLock<Option<Arc<BrahmanNet>>>>. Default None preserva callers existentes. - Nuevo attach_to_net(net): sync inicial (block_peer por cada en deny) + guarda net para diff-sync en cada reload. - reload extendido: snapshot prev_deny ANTES de mutar inner. Tras mutar, sync_deny_to_swarm aplica block/unblock por cada added/removed. - Atomicidad preservada: si parse falla, sync no ocurre y la version anterior persiste tanto en policy como en block_list. ente-zero: tras setup_brahman_net + setup_brahman_policy, si AMBOS estan presentes -> policy.attach_to_net(net.clone()) con log informativo. Tests: 1 nuevo E2E swarm_level_deny_blocks_before_noise. A configura policy con deny + attach_to_net. Cliente baneado intenta connect_libp2p; en lugar del Unauthorized del handshake, ahora falla con error de transporte/stream o timeout — el dial nunca completa porque el swarm rechaza la conexion. 5 tests verdes en network_libp2p.rs. 31 tests totales en brahman- handshake + brahman-net. Trade-offs documentados: - Mas eficiente contra DoS (no consume CPU del Noise por peer baneado). - Misma fuente de verdad: PeerPolicy. Swarm es cache derivado, sync via diff en cada reload, sin drift posible. - El handshake-level gate sigue activo como segunda linea (defensa en profundidad si por bug/race un peer baneado pasa el block_list). |
||
|
|
d98a2b6b7c |
feat(brahman-handshake+ente-zero): denylist + hot reload de policy de peers
Consolida PeerAllowlist + nueva denylist en un unico PeerPolicy con
allow + deny + hot reload via notify. Cubre los dos pendientes
documentados en el commit anterior y simplifica la API hacia un solo
punto de entrada.
API consolidada en brahman_handshake::peer_policy:
- PeerPolicy::open() — todo permitido (default).
- PeerPolicy::from_sets(allow, deny) — politica inline para tests.
- PeerPolicy::from_files(allow_path?, deny_path?) — carga ambos
archivos opcionales.
- PeerPolicy::evaluate(peer) -> Decision { Admit | DeniedByDenylist
| NotInAllowlist }. Decision lleva reason() para logging.
- PeerPolicy::reload() — recarga atomica desde paths asociados.
Si un archivo falla, conserva la version anterior (un typo no
baja la politica activa).
- PeerPolicy::spawn_watcher() -> JoinHandle — vigila los archivos
via notify, debounce 250ms (coalesce de eventos por save), recarga
atomica al detectar cambio.
Orden de evaluacion: deny-first.
1. peer in denylist -> DeniedByDenylist.
2. allowlist set y peer no in allowlist -> NotInAllowlist.
3. resto -> Admit.
Deny gana sobre allow (un peer en ambas es rechazado): la denylist
es la primitiva de "kill switch".
Watcher: vigila el directorio padre del archivo, no el archivo
mismo. Razon: editores tipicos hacen rename-and-replace que rompe
el watch del archivo pero no del dir. Filtra eventos por path al
procesar.
Wire en server: ServerConfig.allowlist -> ServerConfig.policy:
Option<PeerPolicy> (rename, scope local).
Wire en Arje (ente-zero): nueva env BRAHMAN_PEER_DENYLIST complementa
BRAHMAN_PEER_ALLOWLIST. setup_brahman_policy carga + spawn watcher
y devuelve (policy, JoinHandle) — el handle se conserva en main
para que el thread no aborte.
Activacion completa con todas las capas:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 \\
BRAHMAN_PEER_ALLOWLIST=/etc/brahman/allow.txt \\
BRAHMAN_PEER_DENYLIST=/etc/brahman/deny.txt \\
ente-zero
# Editar deny.txt en caliente entra en efecto en ~250ms sin restart.
Tests: 10 unit en peer_policy (incluido watcher_reloads_on_file_change
con notify real) + 1 E2E nuevo libp2p_handshake_denylist_blocks_
listed_peer. 30 tests verdes en brahman-handshake. Sin regresion en
ente-zero.
Lo que cierra: politica completa (open/allow/deny/both), hot reload
sin restart, atomicidad de la recarga, resiliencia ante typos.
Pendientes futuros: aplicar policy a nivel de swarm via
libp2p_allow_block_list::Behaviour (rechazar antes del Noise
handshake), rotacion de keypair sin perder peer_id.
|
||
|
|
505748dd41 |
feat(brahman-handshake+ente-zero): allowlist explicita de peers libp2p
Capa de politica sobre el trust criptografico de Fase 3. Hasta ahora
cualquier peer Ed25519-valido pasaba el handshake remoto; con
allowlist activa, solo los peers explicitamente listados. Aplica
unicamente al path libp2p — el path Unix sigue usando SO_PEERCRED
del kernel.
API nueva en brahman_handshake::peer_allowlist:
- PeerAllowlist::from_iter / from_file con AllowlistError tipado.
- Formato del archivo: PeerId base58 por linea, # comentarios (linea
entera o inline), lineas vacias ignoradas. Errores de parseo
reportan numero de linea.
- is_allowed, len, is_empty, iter.
Wire en el server:
- ServerConfig.allowlist: Option<PeerAllowlist>. None = modo abierto
(compat). Some = solo los listados.
- Gate en do_handshake ANTES de la verificacion de firma — la
comparacion BTreeSet O(log n) es mas barata que crypto, asi que
rechazamos peers invalidos antes de gastar ciclos.
- HandshakeError::Unauthorized("peer X no esta en la allowlist").
Wire en Arje (ente-zero):
- Env var BRAHMAN_PEER_ALLOWLIST apuntando a un archivo.
- setup_brahman_allowlist carga al startup; degrada a None si el
archivo falla (doctrina PID 1: no romper por subsistemas
opcionales).
Activacion end-to-end:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 \\
BRAHMAN_PEER_ALLOWLIST=/etc/brahman/allowlist.txt \\
ente-zero
Tests: 6 unit en peer_allowlist + 1 E2E en network_libp2p
(libp2p_handshake_allowlist_admits_listed_rejects_others). 25 tests
verdes en brahman-handshake. Sin regresion en ente-zero.
Pendientes: denylist explicita, hot reload via SIGHUP/watch, aplicar
politica a nivel de swarm via libp2p_allow_block_list::Behaviour
para rechazar ANTES del Noise handshake.
|
||
|
|
2e6afd0973 |
feat(brahman-net+handshake): stop_providing automatico en cleanup
Cierra el pendiente conocido del DHT: hasta ahora cuando una sesion con outputs cerraba (Farewell, EOF, error), el record que la anunciaba en el DHT seguia vivo hasta su TTL natural (~24h en kad default). Consumers remotos podian descubrir un peer "vivo" que ya no servia nada. Cambios: - BrahmanNet::stop_providing(key) (nuevo): contraparte simetrica de start_providing. Manda Command::StopProviding al swarm que llama kad.stop_providing(&key). Borra el record local al instante; replicas remotas siguen expirando por TTL (kad no expone retraccion cross-peer, simetrico al hecho de que start_providing tambien propaga eventualmente). - brahman_handshake::network::withdraw_outputs(net, card) (nuevo): contraparte de announce_outputs. Itera card.flow.output y llama net.stop_providing(flow_dht_key(...)) por cada uno. - server::cleanup: extrae la ResolvedCard removida del registro de sesiones (en lugar de descartarla) y, si config.net esta set, llama withdraw_outputs(net, &card) antes de broadcast_match_diffs. Tests: nuevo E2E dht_discovery_withdraws_on_session_cleanup: 1. A registra Card con flow.output = monad-list:json. 2. B descubre a A via find_remote_providers (assert before contains a_peer). 3. Cliente local de A hace farewell -> cleanup -> withdraw_outputs. 4. Espera a que la sesion salga del registro + 100ms para que el swarm procese el Command. 5. Nueva query desde B: after NO debe contener a_peer. 3 tests verdes en network_discovery.rs (positivo, negativo, withdraw). 18 tests totales en handshake + net. |
||
|
|
fa0ed98880 |
feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + keypair persistente)
Cierra el ultimo pendiente del plan de red: Arje ahora puede arrancar
opcionalmente con BrahmanNet configurado, persistir su identidad
libp2p entre reboots, y participar en la malla brahman como nodo
publico. Sin breaking changes: usuarios actuales (sin env vars) siguen
viendo el comportamiento Unix-only de antes.
Activacion por env vars:
- BRAHMAN_LISTEN_MULTIADDR — si set, activa la red P2P. Ej:
/ip4/0.0.0.0/tcp/4101 (publico), /ip4/127.0.0.1/tcp/0 (loopback).
- BRAHMAN_KEYPAIR_PATH — override del path donde se persiste la
keypair Ed25519. Defaults: PID 1 -> /var/lib/brahman/init-keypair.bin;
dev mode -> $XDG_DATA_HOME/brahman/init-keypair.bin con fallbacks.
- BRAHMAN_BOOTSTRAP_PEERS — multiaddrs coma-separados a dial-ear al
arranque para entrar al DHT.
Comportamiento al activarse:
1. keypair_store::load_or_generate carga keypair de disco o genera y
persiste una nueva (32 bytes raw, 0o600, atomic rename). peer_id
estable across reboots.
2. BrahmanNet::with_keypair arma el swarm con esa identidad.
3. net.listen() resuelve la addr y se loggea.
4. BRAHMAN_BOOTSTRAP_PEERS si set -> dial cada multiaddr.
5. ServerConfig.net = Some(net), activando announce_outputs automatico
en el DHT por cada Card con outputs.
6. Ademas del Unix accept loop, se monta libp2p accept loop sobre el
mismo Server compartido (Arc<Server>). Sesiones locales y remotas
conviven en las mismas tablas.
Refactor del Unix accept loop: antes consumia server via run().await;
ahora usa Arc<Server>::accept_one().await en loop para coexistir con
el libp2p accept loop sin moverse el server.
Degradacion gracil: si la keypair no carga, multiaddr invalido,
listen falla, bootstrap dial revienta -> log + continuamos en
Unix-only. Doctrina PID 1 ("ningun subsistema opcional rompe el
bucle primordial") preservada.
Tests: 4 unit en keypair_store: generate_persist_and_reload_yields_
same_peer_id (la property fundamental), rejects_corrupted_file,
persisted_file_is_owner_only (0o600 verificados), default_path_
honors_env.
Activacion en una linea:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero
Pendientes: stop_providing en cleanup, allowlist/denylist (PKI),
rotacion de keypair sin perder peer_id.
|
||
|
|
c164e9f422 |
feat(brahman-handshake): Fase 3 — trust remoto via firma Ed25519
Cuarto paso del plan "el encuentro entre Entes no se restringe a
local". Cierra la falla de seguridad que dejaba la red P2P abierta:
hasta ahora un atacante que pudiera dial-ar al multiaddr del Init
podia registrar cualquier Card con cualquier label/flow. Fase 3
exige que el Hello via libp2p venga firmado con la misma keypair
Ed25519 que produce el peer_id autenticado por Noise.
Modelo de trust:
- Local Unix: SO_PEERCRED del kernel autentica. Firma opcional pero
verificada si presente (defensa en profundidad).
- Remoto libp2p: firma obligatoria; public key del Hello debe derivar
al peer_id autenticado por Noise. Si falta o no coincide ->
HandshakeError::Unauthorized.
Wire:
- Hello.signature: Option<HelloSignature> (default None, backwards
compat para path Unix).
- HelloSignature { public_key: Vec<u8>, signature: Vec<u8> } —
public_key en formato canonico libp2p (encode_protobuf), firma
Ed25519 sobre (SIGNATURE_VERSION, WireCard, Option<WitInterface>)
serializado postcard.
Nuevo modulo signature:
- sign_hello / verify_hello con SignatureError tipado.
- 4 unit tests: roundtrip, peer mismatch, card tampered, sig flipped.
Server:
- Session<S> gana expected_peer: Option<PeerId>.
- session_from_libp2p_stream(stream, peer) para path remoto;
session_from_stream sin peer para Unix.
- do_handshake exige firma + verifica peer match si expected_peer.
Client:
- connect_with_stream_signed(stream, card, wit, &Keypair) (nuevo).
- connect_libp2p ahora requiere &Keypair (breaking change).
BrahmanNet:
- Almacena Arc<Keypair> internamente; expose keypair() accessor.
Truco: ed25519::Keypair SI es Clone, se duplica para que swarm
(Noise) y caller (signing) compartan identidad sin copiar bytes.
- with_keypair rechaza no-Ed25519.
Tests: 4 unit signature + 1 E2E negativo nuevo
(libp2p_handshake_rejects_mismatched_signing_key) + E2E positivo
ya pasaba con keypair correcta. 90+ tests verdes en
brahman-handshake/brahman-net/brahman-card/minga-p2p.
Lo que cierra: la cadena completa de discovery + connect + trust
funciona cross-machine sin paths hardcodeados ni confianza
implicita. Brahman-net es una malla publicamente dial-able CON
autenticacion criptografica end-to-end.
Pendientes futuros: stop_providing en cleanup, wire de Arje con
ServerConfig.net configurado, allowlist/denylist de peers,
persistencia de la keypair entre reboots.
|
||
|
|
2059af4fb9 |
feat(brahman-handshake): Fase 2 — discovery remoto via DHT por flow type
Tercer paso del plan "el encuentro entre Entes no se restringe a
local". Cuando un Init local acepta una sesion cuya Card declara
outputs, anuncia al DHT (Kademlia, via brahman-net) que el provee
esos flow types. Cualquier nodo conectado al mismo DHT puede
consultar y obtener la lista de PeerId's que sirven el flow.
API nueva en brahman_handshake::network:
- flow_dht_key(flow_name, type_ref) -> [u8; 32]: blake3 hash de
"brahman-flow|v1|{flow}|{type_canon}". Determinista cross-host.
Cambiar la canonicalizacion rompe compatibilidad — el prefijo v1
documenta version del esquema y obliga a bump al modificar.
- announce_outputs(net, card): start_providing por cada flow.output.
Idempotente, fire-and-forget.
- find_remote_providers(net, flow_name, type_ref) -> Vec<PeerId>:
query DHT. Lista vacia si nadie anuncia.
Wire en el server:
- ServerConfig gana pub net: Option<Arc<BrahmanNet>>. Si esta set,
cada Card registrada con outputs se anuncia automaticamente al DHT
desde register_session. None = server "ciego al DHT".
- Debug manual de ServerConfig (BrahmanNet no es Debug).
Canonicalizacion del TypeRef:
- Primitive { name } -> "prim:{name}"
- Wit { package, interface, name } -> "wit:{package}#{interface_or_empty}#{name}"
Tests: 2 nuevos en tests/network_discovery.rs:
- dht_discovery_finds_remote_provider: 2 nodos, A registra Card con
flow.output = monad-list:json, B dial-ea a A, B llama
find_remote_providers y descubre el peer_id de A.
- dht_discovery_negative_unknown_flow: B busca flow inexistente,
devuelve [] sin colgarse.
Callers actualizados con net: None: tests existentes + ente-zero
(arje aun no expone red; pasar Some(Arc<BrahmanNet>) cuando quiera
publicar al DHT remoto).
Lo que esto desbloquea: un nouser daemon en maquina A puede ser
descubierto por nouser-explorer en maquina B sin conocimiento previo
del peer — solo necesitan compartir DHT (via bootstrap inicial).
Pendiente para Fase 3: trust (firma Ed25519 en Cards remotas) +
stop_providing al cleanup de sesion.
|
||
|
|
73dadbb166 |
feat(brahman-handshake): Fase 1 — handshake brahman sobre stream libp2p
Segundo paso del plan "el encuentro entre Entes no se restringe a
local". El protocolo brahman (Hello / HelloAck / Ping / Pong /
MatchEvent / Farewell, frames postcard length-prefixed) ahora tambien
viaja sobre streams libp2p de la malla brahman-net — el mismo Init
acepta sesiones por Unix socket Y por libp2p indistintamente, y un
consumer remoto puede dial-ar al multiaddr y completar handshake.
Cambios:
- Session<S> y Client<S> genericos: ambos dejan de estar atados a
UnixStream y pasan a ser genericos sobre S: AsyncRead + AsyncWrite
+ Unpin + Send + 'static. El path Unix queda como
Client = Client<UnixStream> (default generico). Constructores
nuevos: Server::session_from_stream(stream),
Client::connect_with_stream(stream, card, wit).
- Refactor del post-handshake con split: tokio::select! sobre
&mut self.stream requeria S: Sync indirectamente, y libp2p::Stream
no es Sync. Reemplazado por tokio::io::split(stream) -> reader loop
principal + writer task separada que drena el push channel. Writer
compartido bajo Arc<Mutex<WriteHalf<S>>> para serializar Pong/Error
inline con los MatchEvents pusheados. Cleanup garantizado en todas
las ramas. La logica del post-handshake migra a funciones libres
(run_post_handshake, handle_inbound_frame, cleanup,
broadcast_match_diffs, do_handshake, register_session,
validate_hello).
- Nuevo modulo brahman-handshake::network:
- BRAHMAN_HANDSHAKE_PROTOCOL = "/brahman/handshake/1.0.0"
- LibP2pHandshakeStream = Compat<libp2p::Stream>
- run_libp2p_accept_loop(server, net): accept loop que delega cada
stream entrante a session_from_stream(stream.compat()). Sesiones
libp2p y Unix conviven en el mismo Server — comparten broker,
push table, last_matches.
- connect_libp2p(net, peer, card, wit): abre stream libp2p al peer
y arranca handshake.
- NetworkError tipado.
Deps: brahman-handshake gana brahman-net, futures, tokio-util.
brahman-net re-exporta Multiaddr, PeerId, Stream, StreamProtocol,
Protocol, OpenStreamError para que callers no necesiten dep directa
a libp2p.
Tests: 9 verdes en el path Unix (sin regresion). Nuevo
tests/network_libp2p.rs E2E que arma server con BrahmanNet, hace
listen TCP, monta accept loop; cliente con su propio BrahmanNet
dial-ea al peer_id, completa handshake remoto, ping, farewell.
Verifica que la sesion se registro durante la conversacion y se
removio tras farewell.
|
||
|
|
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.
|