Files
brahman/docs/changelog/minga.md
T
sergio e77a32f4d6 feat(minga): minga-vfs — proyecta el repo como filesystem FUSE
minga-vfs deja de ser un stub: monta el repositorio direccionado por
contenido como un filesystem FUSE de sólo lectura. roots/<hash> da el
código fuente reconstruido (formato normalizado) de cada raíz del MST;
cas/<hash> resuelve cualquier hash bajo demanda como S-expression.

Capas separadas: render (SemanticNode→texto, puro) + source (contrato
NodeSource, backends sled/memoria) + fs (único módulo con fuser).
Nuevo subcomando `minga mount <punto>`. Dep fuser 0.15 sin libfuse-dev
(default-features = false). 14 tests nuevos, sin regresión en minga-cli.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:23:44 +00:00

18 KiB
Raw Blame History

Changelog — minga (semantic_dht)

feat(minga-vfs): VFS FUSE — el repo como filesystem de sólo lectura

minga-vfs deja de ser un stub de 2 LOC: ahora monta el repositorio direccionado por contenido como un filesystem FUSE navegable con ls/cat/grep/un editor, sin exponer sled.

Layout del montaje:

  • README — explicación del propio VFS.
  • roots/<hash> — un archivo por raíz del MST (cada archivo ingerido), con el código fuente reconstruido en formato normalizado. ls roots/ las enumera todas.
  • cas/<hash> — la S-expression del subárbol de cualquier hash. Este directorio NO se enumera (decenas de miles de nodos) pero cat cas/<hash> resuelve cualquier hash conocido: es el "blob por hash bajo demanda" que la SDD pedía.

Arquitectura, separando por capas (ver feedback de separabilidad):

  • renderSemanticNode → texto. Lógica pura, sin IO ni FUSE. render_source re-imprime los tokens hoja con un pretty-printer mínimo consciente de llaves (indenta tras {, corta tras ;); render_sexp vuelca el árbol literal. El render de fuente es normalizado, no byte-exacto: el AST descartó whitespace y comentarios al ingerir. Python, cuya estructura vive en la indentación, sale como secuencia de tokens — esperado.
  • source — el contrato NodeSource (roots() + get()) y reconstruct(). Backends: RepoSource sobre el PersistentRepo de sled, MemSource en RAM (tests / índices efímeros).
  • fs — único módulo acoplado a fuser: implementa la Filesystem trait. Inodos estáticos reservados (1-4), dinámicos desde 16 con tabla (padre, nombre) → inodo estable. Cada archivo se renderiza y cachea en el primer lookup.

Dep nueva (workspace + minga-vfs):

  • fuser = "0.15" con default-features = false: prescinde de pkg-config/libfuse-dev en build; el montaje pasa a Rust puro (helper fusermount3 en runtime).

CLI: nuevo subcomando minga mount <punto> (cmd_mount), que abre el repo, lo envuelve en RepoSource y bloquea hasta fusermount -u <punto>. Pide la passphrase igual que status.

Tests: 14 nuevos en minga-vfs (9 unit: render con llaves, S-expression con campos/escape, parseo de hash de 64 hex, roundtrip de MemSource; 5 integration: ingest→reconstruct lossless a nivel AST, roots/ lista todo, vistas source/sexp, deduplicación de subárboles). Los 10 tests de minga-cli intactos (sin regresión).

Pendientes:

  • cas/ cachea contenido sin tope: navegar un repo gigante crece en RAM. Un LRU sería el siguiente paso si molesta.
  • Sin extensión en los nombres (roots/<hash>): no guardamos el lenguaje original, así que el editor no autodetecta sintaxis.

feat(minga-explorer): listings de items recientes en cada stat card

Iter 12. Hasta ahora minga-explorer mostraba sólo counts (3 números). Ahora cada stat card muestra también un sample de los items dentro: hashes truncados de los 5 primeros nodos AST (con su kind), atestaciones (content_hash ← did_short) y claves MST. Mucho más útil para debugging que el "tengo N items".

Cambios en minga-explorer:

  • RepoSnapshot extendido con 3 nuevos Vec<...>:
    • recent_nodes: Vec<(String, String)>(hash_short, kind).
    • recent_attestations: Vec<(String, String)>(content_hash_short, did_short).
    • recent_mst_keys: Vec<String>hash_short.
    • Cap de 5 items por sección via RECENT_LIMIT const.
  • load_snapshot itera los stores y toma los primeros 5 items via iter().filter_map(Result::ok).take(RECENT_LIMIT). Errores per-item se silencian (filter_map) — el dashboard muestra lo que pueda; un par de items corruptos no debería tirar el panel.
  • short_hash(&str) helper local: trunca un hex a sus primeros 12 chars (48 bits, distintivo dentro de un repo single-machine).
  • stat_card extendido: nuevo arg recent_items: &[String]. Si no está vacío, agrega un sub-header "recent (N de TOTAL):"
    • una linea por item. Cada line es texto pequeño (px(11)) con el color principal del theme — visualmente queda como monospace listing aunque no use mono font (no hay todavía en el theme).

Tests: 2 → 4 (+2 sanity de los nuevos defaults + del short_hash).

Beneficio operativo:

  • Después de minga ingest archivo.rs, el explorer muestra inmediatamente los hashes de los nodos AST creados, qué kind tienen, y las atestaciones firmadas — sin necesitar minga status o queries SQL.
  • "5 de 247" da contexto del crecimiento sin overwhelm de listing completo.

Limitación documentada: los "recent" no son cronológicos — sled ordena lexicográfico por hash. Para timeline real, agregar timestamp al schema (cambio breaking del store, scope futuro).

feat(minga-explorer): nueva app dashboard del repo Minga sobre stack yahweh

Iter 11. Cierra el último frente identificado: integración del módulo Minga (VCS semántico P2P) al ecosistema GUI. Antes Minga sólo tenía CLI (minga init/status/ingest/listen/sync/watch). Ahora hay un dashboard GPUI que muestra los counts del repo en vivo, sobre el mismo stack themed que las otras dos apps explorer.

Crate nuevo crates/apps/minga-explorer/:

  • Deps: minga-store (para PersistentRepo::open) + yahweh-theme + yahweh-widget-{banner,card,theme-switcher}. Sin minga-cli (no necesita prompts de passphrase) ni minga-core (counts no requieren parsear AST).
  • Lectura sin passphrase: el PersistentRepo se abre directo desde <repo>/repo sled. Los counts (nodes.len(), attestations.len(), mst.len()) son lectura pública. Para el DID se sigue necesitando minga status (CLI con passphrase).
  • Refresh por polling cada 2s: mismo pattern que nakui-explorer/nouser-explorer.
  • 3 stat cards una por dimensión:
    • Nodos AST (cyan) — fragments parseados del código.
    • Atestaciones (verde) — firmas Ed25519 sobre los nodos.
    • Claves MST (purple) — entradas del Merkle Search Tree.
  • Helper stat_card(cx, label, value, description, accent, ...): fabrica una card con border-l colored + label tenue + número grande (px(28)) + descripción. Reutilizable.
  • Header: título dinámico (Repo: <path> · reload <ms> ms)
    • theme switcher derecha.
  • Error banner: themed Banner::Error si el repo no abre.
  • 2 tests: load_snapshot_errors_on_missing_dir (msg claro cuando el dir no existe) + sanity del RepoSnapshot::default.

Workspace: nueva entry en members[].

Smoke run del binario verificado: bootstrap completo OK, panic esperado en open_window por falta de display.

Beneficio operativo:

  • Un usuario corre minga init + minga ingest archivo.rs desde CLI, después abre minga-explorer y ve los counts crecer en vivo cuando ingiere más archivos.
  • Comparte theme switcher con nakui-explorer y nouser-explorer — cualquier preset elegido se aplica visualmente igual cross-app.
  • minga deja de ser sólo CLI; gana presencia GUI sin tocar el resto del módulo.

Apps GUI integradas al stack themed: 4 (nakui-ui, nakui-explorer, nouser-explorer, minga-explorer).

feat(minga-core): α-hashing per-language para Python, TypeScript, JavaScript, Go

Cierra el último pendiente fundamentado del CHANGELOG. Cada lenguaje soportado por minga tiene ahora su propio profile α-equivalente — dos versiones del mismo programa que difieren sólo en nombres de variables ligadas producen el mismo hash, no importa el lenguaje. Refactorings tipo "rename variable" no inflan el storage del repo en ningún dialecto.

Refactor de alpha.rs (639 LOC) a módulo alpha/:

  • alpha/common.rs: primitives compartidos (TAG_*, write_kind_and_field, emit_leaf_marker, emit_binder_body, emit_identifier_ref, push_identifier_name). Garantiza que el formato wire del hash sea bit-equivalente entre todos los profiles.
  • alpha/rust.rs: la lógica de Rust (movida desde alpha.rs sin cambios funcionales).
  • alpha/python.rs: nuevo.
  • alpha/ecmascript.rs: nuevo (cubre TypeScript + JavaScript; comparten la mayoría de los kinds).
  • alpha/go.rs: nuevo.
  • alpha/mod.rs: re-exporta hash_node_alpha (Rust legacy) + expone hash_alpha_with(dialect, node) que despacha al profile correspondiente.

Cobertura per-language:

Python (def, lambda, for, comprehensions, with):

  • function_definition y lambda: parámetros (incluyendo typed_parameter, default_parameter, *args, **kwargs) introducen binders al body. El nombre de la función NO es α-anónimo.
  • for_statement: el left (identifier o tuple) introduce binder(es) al body.
  • list_comprehension, set_comprehension, dictionary_comprehension, generator_expression: cada for_in_clause añade binders que viven en el body + clauses siguientes (semántica de scope incremental de Python).
  • with_statement: as introduce binder al body (recursando en as_pattern_target para llegar al identifier).

ECMAScript (TS + JS):

  • function_declaration, function_expression, method_definition, generator_function_*: parameters → body. Soporta TS required_parameter y optional_parameter (x: number, x?: number).
  • arrow_function: tanto (x, y) => body como shorthand x => body.
  • statement_block: lexical_declaration (let/const) y variable_declaration (var) introducen binders al resto del block.
  • for_in_statement (cubre for-of y for-in): left → body.
  • for_statement (C-style): initializer (lexical decl) introduce binders al condition + increment + body.
  • catch_clause: parameter → body.

Go:

  • function_declaration, method_declaration, func_literal (closure): parameter_list → body. parameter_declaration con varios names agrupa varios binders bajo un mismo tipo (a, b int).
  • block: short_var_declaration (x := ...) introduce binders al resto.
  • for_statement con range_clause (for k, v := range m): los identifiers del left son binders al body.
  • for_statement con for_clause (C-style): initializer → body.
  • if_statement con initializer (if x := init(); x > 0): binders viven en condition + consequence + alternative.

API:

  • hash_alpha_with(Dialect, &SemanticNode) -> ContentHash — despacho per-dialect.
  • hash_node_alpha(&SemanticNode) -> ContentHash — alias histórico asume Rust (back-compat).

Tests: 26 nuevos en tests/alpha_polyglot.rs:

  • Python (9): def rename, lambda rename, for-loop rename, list comp, nested comp, with rename, function name matters, iterable name matters, sanity negativo (operación distinta → hash distinto).
  • JS/TS (9): function rename, function name matters, arrow rename, arrow shorthand rename, let/const rename, for-of rename, classic for rename, catch rename, TS typed param rename, TS type matters.
  • Go (6): function rename, function name matters, short var decl rename, range_clause rename, if-init rename, func_literal closure rename.
  • Cross-language (1): mismos shapes en lenguajes distintos producen hashes distintos (sanity para evitar colisiones).

141 tests verdes en minga-core (115 antes; +26 polyglot). Refactor sin regresión: 36 α-Rust tests siguen pasando.

Pendientes que quedan en Minga (orden de prioridad):

  • minga-vfs FUSE (proyecto independiente, scope grande).
  • Cobertura adicional por-lenguaje: Python class, JS destructuring, Go type_switch, etc. — cada uno pequeño, no urgente.

feat(minga-core): cierre del α-hashing de Rust — if let, while let, let-else, or-pattern, let-chains

Cierra los 5 pendientes documentados en alpha.rs. El hash α-equivalente ahora es estable bajo renombre de TODOS los binders de Rust, no sólo los del MVP (parámetros, let, for, match arms).

Pendientes cerrados:

  • if let X = expr { ... }: if_expression detecta let_condition en su condition, recolecta los binders del pattern, los propaga al consequence. El alternative (else) NO los ve.
  • while let X = expr { ... }: simétrico al if-let, propaga al body. El condition mismo se evalúa con scope previo (los binders todavía no existen).
  • let-else: let_declaration con campo alternative. El alternative se procesa con el scope ANTES de los binders (ya funcionaba: feed_let llama feed para no-pattern children con el scope actual; feed_block extiende el scope DESPUÉS de feed_let).
  • or_pattern: en pat1 | pat2 (Rust enforcement: ambos lados introducen los mismos binders). Para emit, recorremos cada lado con feed_pattern. Para collect, sólo el primer lado — iterar todos duplicaría binders y rompería los índices de Bruijn.
  • let-chains (if let X = a && let Y = b { ... }): el collect_let_condition_binders recursa en el árbol del condition, capturando todos los let_condition (vivan dentro de binary_expression u otros nodos). Ambos binders quedan en scope del consequence.

Helper nuevo: feed_let_condition para que el pattern del let_condition pase por feed_pattern (que distingue binders vs constructors). Sin esto, los identifiers del pattern se hasheaban como variables libres y Some(x)Some(y) aún teniendo el mismo significado.

Tests: 6 nuevos en tests/alpha_invariants.rs:

  • alpha_if_let_binder_rename_invariant
  • alpha_if_let_else_does_not_see_binder (sanity)
  • alpha_while_let_binder_rename_invariant
  • alpha_let_else_binder_rename_invariant
  • alpha_or_pattern_binder_rename_invariant
  • alpha_let_chain_binders_propagate_to_consequence
  • alpha_if_let_does_not_collide_with_unrelated_program (negativo: programas distintos NO deben dar el mismo hash)

36 tests α verdes (eran 30). 115 tests totales en minga-core.

Lo que esto significa: el hash α-equivalente de Rust en minga es completo — cubre todos los constructos del lenguaje que introducen bindings. Dos versiones del mismo programa que difieren sólo en nombres de variables (incluyendo en if let, while let, or-pattern, etc.) producen el mismo hash y por tanto la misma identidad CAS. Refactorings del tipo "rename variable" no inflan el storage del repo.

Pendientes futuros:

  • α-hashing per-language (Python: def/lambda/comprehensions; TS/JS: function/arrow/destructuring; Go: func/closure). Cada uno requiere conocimiento profundo de la gramática y tests exhaustivos. Plantilla genérica no aplica.

feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go

Minga deja de ser Rust-only. Cualquiera de los cinco dialectos (Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea estructuralmente, sincroniza por DHT como cualquier nodo. La auto-detección por extensión hace que minga ingest archivo.py o .ts o .go "simplemente funcione".

API nueva en minga_core::parse:

  • Funciones por dialecto (~6 LOC c/u sobre el parse_with común): python, typescript, javascript, go. Más la rust existente.
  • Enum Dialect con parse(source) -> Result<SemanticNode> y name() -> &'static str para logging.
  • detect_by_extension(ext) -> Option<Dialect>: mapea rs/py/ pyi/ts/js/mjs/cjs/go (case-insensitive). None para extensiones desconocidas — el caller decide si es error o se ignora silente.

Wire en minga-cli:

  • cmd_ingest deja de hardcodear parse::rust — usa detect_dialect(file)?.parse(...). Acepta .py, .ts, .js, .go además de .rs.
  • initial_scan y cmd_watch cambian is_rs_fileis_supported_source para incluir todas las extensiones soportadas en el filtro.
  • CliError::UnsupportedLanguage { path, extension } nuevo, con mensaje que lista las extensiones reconocidas.

Notas sobre hashing:

  • El AST normalizado (SemanticNode) descarta whitespace y comentarios — propiedad universal de tree-sitter (extras). Misma lógica para los 5 dialectos.
  • Hashing estructural (cas::hash_node) funciona para todos: dos textos semánticamente equivalentes-por-estructura producen el mismo hash. NO α-equivalente (las variables ligadas distinguen).
  • Hashing α-equivalente (alpha::hash_node_alpha) sigue siendo Rust-only: cada lenguaje tiene reglas distintas para qué es binder vs. constructor (def/lambda en Python, arrow functions en TS/JS, func + closures en Go). Implementación per-language queda como work futuro — requiere conocimiento profundo de cada gramática y no se plantilla genéricamente.
  • Sanity test structural_hash_distinguishes_languages verifica que x = 1 parseado como Python ≠ parseado como JavaScript: las gramáticas no comparten kinds y los hashes salen distintos. Importante para evitar colisiones cuando el mismo source se ingresa bajo dialectos distintos.

Deps nuevas (workspace + minga-core):

  • tree-sitter-python = "0.23"
  • tree-sitter-typescript = "0.23" (sólo el modo LANGUAGE_TYPESCRIPT, no TSX — bumpear a TSX es agregar otro dialecto cuando se necesite).
  • tree-sitter-javascript = "0.23"
  • tree-sitter-go = "0.23"

Tests:

  • 9 nuevos en parse::tests: parse básico para los 5 dialectos (Python con type hints, TS con tipos, JS sin tipos, Go con package declaration), detect_by_extension canonical + case-insensitive, dialect_name, structural_hash_distinguishes_languages.
  • 108 tests verdes en minga-core (39 → 48 unit + integration tests pre-existentes intactos).
  • 10 tests verdes en minga-cli (sin regresión en el path Rust; el refactor a detect_dialect/is_supported_source no rompe nada).

Pendientes futuros del changelog:

  • α-hashing per-language (Python: def/lambda/comprehensions; TS/JS: function/arrow/destructuring; Go: func/closure). Trabajo profundo, scope independiente.
  • α-Rust pendientes documentados en alpha.rs: if let, while let, let-else, let-chains, or_pattern con bindings.