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
@@ -97,6 +97,23 @@ pub fn cmd_ingest(
})
}
/// `minga mount <punto>`: monta el repositorio como un filesystem FUSE
/// de sólo lectura. Cada hash del store se vuelve un archivo
/// navegable con `ls`/`cat`. Bloquea hasta que se desmonte el punto
/// (`fusermount -u <punto>` o una señal al proceso).
pub fn cmd_mount(
repo_path: &Path,
passphrase: &str,
mountpoint: &Path,
) -> Result<(), CliError> {
// Cargar el keypair valida la passphrase: montar es navegar el
// repo, así que pedimos la misma credencial que `status`/`watch`.
let _keypair = keypair_file::load(repo_path.join(KEYPAIR_FILENAME), passphrase)?;
let repo = PersistentRepo::open(repo_path.join(REPO_DIRNAME))?;
minga_vfs::mount(minga_vfs::RepoSource::new(repo), mountpoint)?;
Ok(())
}
/// Detecta el dialecto desde la extensión del archivo. Error si la
/// extensión no corresponde a un lenguaje soportado.
fn detect_dialect(file: &Path) -> Result<parse::Dialect, CliError> {