Files
hammer/docs/18-wawafs-store-vivo.md

170 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDD 18 — wawafs: el store como filesystem del sistema vivo (CAS + erofs + fs-verity)
> Origen: la pregunta "¿se podría hostear hammer en un FS de grafos sobre discos en vivo,
> sacándole ventaja a ext4?" (2026-07-17). La respuesta corta: sí, y no hay que escribir un
> filesystem — la pila composefs (CAS + erofs + overlayfs + fs-verity, mainline) es exactamente
> esa idea, y el modelo de hammer ya es casi isomorfo a ella. "wawafs" queda como apodo del
> frente; no es código de wawa (tawasuyu), es la misma filosofía content-addressed bajada al VFS.
**Estado:** diseño aceptado en conversación; sin implementar. **Fecha:** 2026-07-17.
## 1. Problema
Hoy la materialización del store al sistema vivo es **por copia u hardlink** (`hammer hydrate
<hash> --into`, ADR 0005). Funciona y escala rebuild-free (el escritorio KDE son 137 artefactos
hidratados), pero en el **sistema desplegado** (live-disk, metal, generaciones de boot) paga
tres costos:
1. **Duplicación**: el store tiene los bytes y el rootfs hidratado tiene otra copia (o un
hardlink que exige mismo-filesystem y se rompe silencioso al escribir encima).
2. **Integridad histórica, no vigente**: bit-repro es una propiedad del día del sellado; nada
verifica el binario en cada lectura años después (bit-rot, disco vivo).
3. **Rollback O(bytes)**: cambiar de generación implica re-hidratar; dos generaciones = casi 2×
disco si no comparten filesystem con el store.
## 2. La idea en una línea
**Bajar el checkout de userspace al kernel**: hoy `hydrate` es un checkout con copia (git-style:
store = DAG, hidratar = materializar el árbol); wawafs es el mismo checkout hecho *lazy* por el
VFS al montar, sin copiar un byte. ext4 queda abajo como capa de bloques tonta; la "evolución
sobre ext4" es la capa content-addressed encima — la arquitectura que hammer ya tiene en
userspace, un nivel más abajo.
## 3. Las capas
```
proceso: open("/usr/bin/kate") → fd normal; execve/mmap/musl sin cambios
▲ VFS (rutas FHS reales — no hay pueblo fantasma en la vista)
composefs mount (overlayfs data-only lowerdir + redirect)
▲ resuelve nombre → puntero por hash (sólo en el open frío; dcache después)
manifest erofs por clausura (~KBMB): árbol de dirs + modos + punteros. CERO datos.
▲ direccionado por hash = un nodo del grafo de estados (ADR 0010)
CAS de objetos: /store/objects/ab/cdef… (archivos planos, 1 copia por contenido único)
▲ fs-verity opcional por objeto: el kernel verifica el Merkle tree EN CADA READ
ext4 — intacto, aburrido, sólo guarda archivos sueltos
```
Propiedades que caen solas:
- **Dedup de disco y de RAM**: contenido idéntico entre N clausuras = 1 copia en ext4 y 1
entrada en page cache (la segunda app Qt arranca con las .so calientes).
- **Activar/rollback O(metadata)**: cambiar de generación = escribir unos KB de manifest y
montar. Atómico.
- **Inmutabilidad estructural**: la vista es ro por construcción; "alguien escribió /usr" deja
de ser un estado alcanzable.
- **bit-repro en runtime**: con fs-verity, el hash sellado lo hace cumplir el kernel en cada
lectura de por vida (un bit podrido = EIO, no un binario silenciosamente distinto).
## 4. Identidad: dos granos, un mapa
- **Grano artefacto** (existente): ArtifactHash blake3 — identidad de recetas, sellado, grafo.
No se toca.
- **Grano archivo** (nuevo, derivado): el CAS de objetos se indexa por el **digest fs-verity**
(sha256, es lo que el kernel sabe verificar). Es una capa *derivada* del store: un comando
(`hammer fs manifest <ArtifactHash…>`) explota los árboles de N artefactos en objetos por
archivo + genera el manifest erofs de la clausura. blake3 sigue siendo la identidad; el
digest verity es el enforcement.
Misma jugada que ADR 0009 (paquete/módulo/función: granos distintos, identidades intactas,
un mapa entre ellos).
## 5. Relación con los ADRs vigentes
- **ADR 0005 (hardlinks) sigue vigente** para el lado dev/laptop: rutas reales *escribibles*,
mutar con red de seguridad. wawafs no lo reemplaza: es el modo **despliegue** (ro). El corte
cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil veces al
vivir. Los builds — escritura intensiva — siguen en ext4 plano; la indirección se paga sólo
donde es barata. Bonus: cae la restricción "store y FHS en el mismo filesystem" y el agujero
del hardlink roto en silencio.
- **ADR 0010 (arranque por grafo)**: encaje directo. Un nodo del `boot-graph.json` = un
manifest erofs direccionado por hash; "activar un nodo" = montar su composefs como `/usr` de
la generación. El menú de mirada navega el grafo; wawafs hace que elegir nodo cueste un mount.
- **ADR 0004 (no nix)**: esto tampoco es nix — no hay rutas crípticas en la vista (el proceso
ve `/usr/bin/kate`, FHS real), no hay lenguaje de build nuevo, no hay daemon. Sólo mounts.
- **SDD 17 (cierres de frontera)**: refuerza los tres invariantes a la vez — bit-repro
(verity en runtime), CAS (bajado al VFS), clausura (la vista montada ES la clausura, nada más).
## 6. Costos honestos
- **Open frío**: un lookup extra en el manifest erofs (µs; Android monta `/system` así en cada
teléfono). Repetidos los absorbe el dentry cache; los `read()`/`mmap()` van directo al inode
ext4 — el data path es nativo.
- **fs-verity**: ~0.8% de disco extra (árbol Merkle) + hash por página en el primer read (miss
de page cache). Opt-in por objeto.
- **Complejidad**: dos recetas nuevas (`erofs-utils`, `composefs`) y un conversor
store→objetos. El formato erofs/overlayfs es mainline y ajeno — no mantenemos un FS.
## 7. Precondiciones y gates (medidos, no supuestos)
1. **Kernel propio SIN soporte hoy**: `linux-generic.toml` ya trae `OVERLAY_FS` +
`REDIRECT_DIR`/`METACOPY`/`XINO` (justo lo que composefs necesita del lado overlay), pero
**falta `EROFS_FS` y `FS_VERITY`** (verificado 2026-07-17, líneas 4551 de la receta).
Agregar `-e EROFS_FS -e EROFS_FS_ZIP_ZSTD -e FS_VERITY` re-sella el kernel → **worker**
(mismo patrón que el re-sellado LANDLOCK/IO_URING de frente-kikin).
2. **Recetas nuevas**: `erofs-utils` (mkfs.erofs) y `composefs` (mkcomposefs) — C, no existen
en el catálogo → **worker** (el laptop no construye recetas C).
3. **Conversor**: `hammer fs manifest` — explotar artefactos en objetos por archivo. El store
actual (1608 artefactos, directorios por hash) no se toca; el CAS de objetos es derivado y
regenerable.
## 8. Fases
- **F0 — spike en QEMU** (sin tocar nada de hammer): rootfs Alpine con composefs-tools de
paquete, `mkcomposefs` sobre UNA clausura hidratada (p.ej. la de KDE), montar, comparar
`find | sort | b3sum` contra la hidratación por copia. Gate: árboles idénticos + medir
open-frío y RAM compartida. Decide si el frente existe.
- **F1 — kernel + recetas**: configs EROFS/FS_VERITY en `linux-generic.toml` (re-sellar en
worker) + recetas `erofs-utils`/`composefs`. Gate: el kernel propio montando el spike de F0.
- **F2 — `hammer fs manifest` + generaciones**: manifest erofs por clausura, generación de boot
= manifest por hash, arje monta `/usr` de la generación elegida (contrato ADR 0010). Gate:
bootear en QEMU una generación montada, rollback = elegir otro nodo en el menú.
- **F3 — sellado verity + live media**: fs-verity en el CAS de objetos; evaluar erofs como
reemplazo de squashfs en el USB live (es el patrón vigente en Fedora/Android). Gate: bit
flipeado a mano en un objeto ⇒ EIO en lectura, no ejecución.
## 9. Layouts de instalación (el corte ro/rw es de MOUNTS, no de particiones)
La parte ro no ocupa partición: es una vista sintética montada desde el CAS. Las particiones
guardan siempre lo mutable + el store. Dos instalaciones tipo:
**A — un solo ext4 en `/`** (particionado idéntico a una instalación normal: ESP + 1 ext4):
```
/ ext4 rw /etc /var /home /root /run /store …
├─ /store/objects/… CAS plano en este mismo ext4
├─ /store/manifests/<hash> manifests erofs por generación (~KB)
└─ /usr composefs ro — mount sintético de la generación activa
```
arje monta `/`, lee el nodo elegido del boot-graph, monta su manifest como `/usr`. Fin.
**B — `/home` y `/var` en particiones propias**:
```
/ ext4 rw (chica: /etc, /root, /store)
/usr composefs ro
/var ext4 rw propia ← estado puro: ni se entera de wawafs
/home ext4 rw propia ← ídem
```
`/home`/`/var` son ortogonales al esquema. El caso B además *mejora*: la restricción de
ADR 0005 (store y FHS mismo-filesystem) desaparece — el redirect de composefs cruza mounts,
el store podría incluso tener partición propia.
Reglas del patrón:
- **ro**: `/usr` = el OS entero (requiere usr-merge para que un mount lo cubra todo).
- **rw**: `/etc` (se hidrata al activar la generación), `/var`, `/home`, `/root`, `/run`,
`/tmp` y el propio `/store`.
- **Actualizar** = escribir objetos+manifest nuevos en `/store` (rw) y cambiar el mount;
la generación corriendo nunca se toca.
- **Parchear `/usr` a mano** = modo dev (ADR 0005) o un overlay rw de emergencia explícito
encima del mount; nunca un estado alcanzable por accidente.
## 10. No-metas
- **No** escribir un filesystem de kernel (años de fsck y corrupción ajena: landmine).
- **No** reemplazar `hydrate`/hardlinks en el laptop de desarrollo (ADR 0005 queda).
- **No** tocar el build lab ni el sellado: el CAS de objetos es una proyección derivada del
store, nunca la fuente de verdad.