wawafs (SDD 18): el store como FS del sistema vivo — CAS+erofs+fs-verity sobre ext4; gates medidos (kernel sin EROFS/FS_VERITY)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Semántico sayk'uy
Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.
1 qillqakuna · +130 −0
- docs/18-wawafs-store-vivo.md +130 −0
diff --git a/docs/18-wawafs-store-vivo.md b/docs/18-wawafs-store-vivo.md
new file mode 100644
index 00000000..fdc16816
--- /dev/null
+++ b/docs/18-wawafs-store-vivo.md
@@ -0,0 +1,130 @@
+# 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 (~KB–MB): á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 45–51 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. 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.