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>
This commit is contained in:
sergio
2026-05-22 13:23:44 +00:00
parent 762bf95dfd
commit e77a32f4d6
15 changed files with 1094 additions and 14 deletions
+52
View File
@@ -1,5 +1,57 @@
# 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):
- **`render`** — `SemanticNode` → 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