1# SDD 18 — wawafs: el store como filesystem del sistema vivo (CAS + erofs + fs-verity)
2
3> Origen: la pregunta "¿se podría hostear takana en un FS de grafos sobre discos en vivo,
4> sacándole ventaja a ext4?" (2026-07-17). La respuesta corta: sí, y no hay que escribir un
5> filesystem — la pila composefs (CAS + erofs + overlayfs + fs-verity, mainline) es exactamente
6> esa idea, y el modelo de takana ya es casi isomorfo a ella. "wawafs" queda como apodo del
7> frente; no es código de wawa (tawasuyu), es la misma filosofía content-addressed bajada al VFS.
8
9**Estado:** diseño aceptado en conversación; sin implementar. **Fecha:** 2026-07-17.
10
11## 1. Problema
12
13Hoy la materialización del store al sistema vivo es **por copia u hardlink** (`takana hydrate
14<hash> --into`, ADR 0005). Funciona y escala rebuild-free (el escritorio KDE son 137 artefactos
15hidratados), pero en el **sistema desplegado** (live-disk, metal, generaciones de boot) paga
16tres costos:
17
181. **Duplicación**: el store tiene los bytes y el rootfs hidratado tiene otra copia (o un
19 hardlink que exige mismo-filesystem y se rompe silencioso al escribir encima).
202. **Integridad histórica, no vigente**: bit-repro es una propiedad del día del sellado; nada
21 verifica el binario en cada lectura años después (bit-rot, disco vivo).
223. **Rollback O(bytes)**: cambiar de generación implica re-hidratar; dos generaciones = casi 2×
23 disco si no comparten filesystem con el store.
24
25## 2. La idea en una línea
26
27**Bajar el checkout de userspace al kernel**: hoy `hydrate` es un checkout con copia (git-style:
28store = DAG, hidratar = materializar el árbol); wawafs es el mismo checkout hecho *lazy* por el
29VFS al montar, sin copiar un byte. ext4 queda abajo como capa de bloques tonta; la "evolución
30sobre ext4" es la capa content-addressed encima — la arquitectura que takana ya tiene en
31userspace, un nivel más abajo.
32
33## 3. Las capas
34
35```
36proceso: open("/usr/bin/kate") → fd normal; execve/mmap/musl sin cambios
37 ▲ VFS (rutas FHS reales — no hay pueblo fantasma en la vista)
38composefs mount (overlayfs data-only lowerdir + redirect)
39 ▲ resuelve nombre → puntero por hash (sólo en el open frío; dcache después)
40manifest erofs por clausura (~KB–MB): árbol de dirs + modos + punteros. CERO datos.
41 ▲ direccionado por hash = un nodo del grafo de estados (ADR 0010)
42CAS de objetos: /store/objects/ab/cdef… (archivos planos, 1 copia por contenido único)
43 ▲ fs-verity opcional por objeto: el kernel verifica el Merkle tree EN CADA READ
44ext4 — intacto, aburrido, sólo guarda archivos sueltos
45```
46
47Propiedades que caen solas:
48
49- **Dedup de disco y de RAM**: contenido idéntico entre N clausuras = 1 copia en ext4 y 1
50 entrada en page cache (la segunda app Qt arranca con las .so calientes).
51- **Activar/rollback O(metadata)**: cambiar de generación = escribir unos KB de manifest y
52 montar. Atómico.
53- **Inmutabilidad estructural**: la vista es ro por construcción; "alguien escribió /usr" deja
54 de ser un estado alcanzable.
55- **bit-repro en runtime**: con fs-verity, el hash sellado lo hace cumplir el kernel en cada
56 lectura de por vida (un bit podrido = EIO, no un binario silenciosamente distinto).
57
58## 4. Identidad: dos granos, un mapa
59
60- **Grano artefacto** (existente): ArtifactHash blake3 — identidad de recetas, sellado, grafo.
61 No se toca.
62- **Grano archivo** (nuevo, derivado): el CAS de objetos se indexa por el **digest fs-verity**
63 (sha256, es lo que el kernel sabe verificar). Es una capa *derivada* del store: un comando
64 (`takana fs manifest <ArtifactHash…>`) explota los árboles de N artefactos en objetos por
65 archivo + genera el manifest erofs de la clausura. blake3 sigue siendo la identidad; el
66 digest verity es el enforcement.
67
68Misma jugada que ADR 0009 (paquete/módulo/función: granos distintos, identidades intactas,
69un mapa entre ellos).
70
71## 5. Relación con los ADRs vigentes
72
73- **ADR 0005 (hardlinks) sigue vigente** para el lado dev/laptop: rutas reales *escribibles*,
74 mutar con red de seguridad. wawafs no lo reemplaza: es el modo **despliegue** (ro). El corte
75 cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil veces al
76 vivir. Los builds — escritura intensiva — siguen en ext4 plano; la indirección se paga sólo
77 donde es barata. Bonus: cae la restricción "store y FHS en el mismo filesystem" y el agujero
78 del hardlink roto en silencio.
79- **ADR 0010 (arranque por grafo)**: encaje directo. Un nodo del `boot-graph.json` = un
80 manifest erofs direccionado por hash; "activar un nodo" = montar su composefs como `/usr` de
81 la generación. El menú de mirada navega el grafo; wawafs hace que elegir nodo cueste un mount.
82- **ADR 0004 (no nix)**: esto tampoco es nix — no hay rutas crípticas en la vista (el proceso
83 ve `/usr/bin/kate`, FHS real), no hay lenguaje de build nuevo, no hay daemon. Sólo mounts.
84- **SDD 17 (cierres de frontera)**: refuerza los tres invariantes a la vez — bit-repro
85 (verity en runtime), CAS (bajado al VFS), clausura (la vista montada ES la clausura, nada más).
86
87## 6. Costos honestos
88
89- **Open frío**: un lookup extra en el manifest erofs (µs; Android monta `/system` así en cada
90 teléfono). Repetidos los absorbe el dentry cache; los `read()`/`mmap()` van directo al inode
91 ext4 — el data path es nativo.
92- **fs-verity**: ~0.8% de disco extra (árbol Merkle) + hash por página en el primer read (miss
93 de page cache). Opt-in por objeto.
94- **Complejidad**: dos recetas nuevas (`erofs-utils`, `composefs`) y un conversor
95 store→objetos. El formato erofs/overlayfs es mainline y ajeno — no mantenemos un FS.
96
97## 7. Precondiciones y gates (medidos, no supuestos)
98
991. **Kernel propio SIN soporte hoy**: `linux-generic.toml` ya trae `OVERLAY_FS` +
100 `REDIRECT_DIR`/`METACOPY`/`XINO` (justo lo que composefs necesita del lado overlay), pero
101 **falta `EROFS_FS` y `FS_VERITY`** (verificado 2026-07-17, líneas 45–51 de la receta).
102 Agregar `-e EROFS_FS -e EROFS_FS_ZIP_ZSTD -e FS_VERITY` re-sella el kernel → **worker**
103 (mismo patrón que el re-sellado LANDLOCK/IO_URING de frente-kikin).
1042. **Recetas nuevas**: `erofs-utils` (mkfs.erofs) y `composefs` (mkcomposefs) — C, no existen
105 en el catálogo → **worker** (el laptop no construye recetas C).
1063. **Conversor**: `takana fs manifest` — explotar artefactos en objetos por archivo. El store
107 actual (1608 artefactos, directorios por hash) no se toca; el CAS de objetos es derivado y
108 regenerable.
109
110## 8. Fases
111
112- **F0 — spike en QEMU** (sin tocar nada de takana): rootfs Alpine con composefs-tools de
113 paquete, `mkcomposefs` sobre UNA clausura hidratada (p.ej. la de KDE), montar, comparar
114 `find | sort | b3sum` contra la hidratación por copia. Gate: árboles idénticos + medir
115 open-frío y RAM compartida. Decide si el frente existe.
116- **F1 — kernel + recetas**: configs EROFS/FS_VERITY en `linux-generic.toml` (re-sellar en
117 worker) + recetas `erofs-utils`/`composefs`. Gate: el kernel propio montando el spike de F0.
118- **F2 — `takana fs manifest` + generaciones**: manifest erofs por clausura, generación de boot
119 = manifest por hash, arje monta `/usr` de la generación elegida (contrato ADR 0010). Gate:
120 bootear en QEMU una generación montada, rollback = elegir otro nodo en el menú.
121- **F3 — sellado verity + live media**: fs-verity en el CAS de objetos; evaluar erofs como
122 reemplazo de squashfs en el USB live (es el patrón vigente en Fedora/Android). Gate: bit
123 flipeado a mano en un objeto ⇒ EIO en lectura, no ejecución.
124
125## 9. Layouts de instalación (el corte ro/rw es de MOUNTS, no de particiones)
126
127La parte ro no ocupa partición: es una vista sintética montada desde el CAS. Las particiones
128guardan siempre lo mutable + el store. Dos instalaciones tipo:
129
130**A — un solo ext4 en `/`** (particionado idéntico a una instalación normal: ESP + 1 ext4):
131
132```
133/ ext4 rw /etc /var /home /root /run /store …
134├─ /store/objects/… CAS plano en este mismo ext4
135├─ /store/manifests/<hash> manifests erofs por generación (~KB)
136└─ /usr composefs ro — mount sintético de la generación activa
137```
138
139arje monta `/`, lee el nodo elegido del boot-graph, monta su manifest como `/usr`. Fin.
140
141**B — `/home` y `/var` en particiones propias**:
142
143```
144/ ext4 rw (chica: /etc, /root, /store)
145/usr composefs ro
146/var ext4 rw propia ← estado puro: ni se entera de wawafs
147/home ext4 rw propia ← ídem
148```
149
150`/home`/`/var` son ortogonales al esquema. El caso B además *mejora*: la restricción de
151ADR 0005 (store y FHS mismo-filesystem) desaparece — el redirect de composefs cruza mounts,
152el store podría incluso tener partición propia.
153
154Reglas del patrón:
155
156- **ro**: `/usr` = el OS entero (requiere usr-merge para que un mount lo cubra todo).
157- **rw**: `/etc` (se hidrata al activar la generación), `/var`, `/home`, `/root`, `/run`,
158 `/tmp` y el propio `/store`.
159- **Actualizar** = escribir objetos+manifest nuevos en `/store` (rw) y cambiar el mount;
160 la generación corriendo nunca se toca.
161- **Parchear `/usr` a mano** = modo dev (ADR 0005) o un overlay rw de emergencia explícito
162 encima del mount; nunca un estado alcanzable por accidente.
163
164## 10. No-metas
165
166- **No** escribir un filesystem de kernel (años de fsck y corrupción ajena: landmine).
167- **No** reemplazar `hydrate`/hardlinks en el laptop de desarrollo (ADR 0005 queda).
168- **No** tocar el build lab ni el sellado: el CAS de objetos es una proyección derivada del
169 store, nunca la fuente de verdad.
18-wawafs-store-vivo.md
SDD 18 — wawafs: el store como filesystem del sistema vivo (CAS + erofs + fs-verity)
Origen: la pregunta "¿se podría hostear takana 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 takana 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 (takana 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:
- 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).
- 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).
- 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 takana 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
(
takana 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)
- 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).
- Recetas nuevas:
erofs-utils (mkfs.erofs) y composefs (mkcomposefs) — C, no existen
en el catálogo → worker (el laptop no construye recetas C).
- Conversor:
takana 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 takana): 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 —
takana 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.