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

9.4 KiB
Raw Blame History

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.