sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
fix(simi,perfiles): NINGÚN perfil proveía `/bin/sh` — funcionaba por la capa de debajo
1# targets.toml — el MANIFIESTO DE OBJETIVO: qué paquetes debe tener la distro.
2#
3# POR QUÉ EXISTE. `build-state.json` es el grafo de lo que EXISTE (768 recetas, cierra sin huérfanas).
4# Lo que la distro DEBE tener no estaba en ningún lado: vivía en dos strings de shell de
5# `product-userland-from-repo.sh`, otro de `mirada-usb.sh`, 55 listas planas en `tandas/` y una cola
6# de 206 recetas KDE. Por eso "¿cuánto falta?" no tenía respuesta: el mapa cerraba perfecto, pero
7# sólo cubría el territorio ya conquistado. Este fichero es el destino. (Plan: docs/plan-catalogo-objetivo.md)
8#
9# LA INVERSIÓN. `paquetes` lista sólo las RAÍCES — lo que un usuario pide POR NOMBRE. La clausura
10# (las libs de las que cuelgan) la calcula el grafo siguiendo las aristas de deps, no la escribe un
11# humano. Al revés de `tandas/base-system-3-libs.txt`, que enumera a mano miembros de la clausura.
12#
13# Un `perfil` = una imagen enviable. `hereda` compone (transitivo, sin ciclos). `cola` dice en qué
14# árbol de recetas viven sus raíces: `corpus` = `recipes/`; otra cosa = `recipes/incoming-<x>/`, que
15# `build-state.py` sólo carga con su flag (`--kde`) ⇒ los perfiles de una cola no cargada se saltan
16# en vez de reportar raíces fantasma.
17#
18# Consumidores: `scripts/targets.py <perfil>` (expande a lista de nombres) y `scripts/build-state.py`
19# (marca los nodos `wanted` y calcula la membresía de perfil de cada receta).
20schema = "hammer-targets/1"
21
22# ══ LO QUE NO VIAJA EN NINGUNA IMAGEN ══════════════════════════════════════════════════════════
23#
24# El cierre de un perfil se calcula sobre `build ∪ runtime` (`scripts/yupana.py:98`), porque para
25# CONSTRUIR hacen falta las dos y porque leer sólo `build` dejó a firefox sin `libstdc++.so.6`. Pero
26# la IMAGEN no se construye a sí misma: **medido el 2026-09-22, las 8 imágenes llevaban adentro
27# `llvm21` (1.527 MiB), `go`, `python3`, `binutils`, `cmake`, `perl`, `nasm`, `bison`, `flex`,
28# `wasi-sdk`, `nodejs`…** — el 44 % de los bytes de `servidor` y el 41 % de los de `base`.
29#
30# Es la MISMA forma que el caso busybox: `busybox` nunca estuvo declarado acá y llegaba a los 7
31# perfiles por la clausura, porque 24 recetas lo pedían en `[deps] build`. Lo que el paso 6 de
32# `docs/plan-botar-busybox.md` arregló para un nombre, esta lista lo arregla para la clase.
33#
34# ── LA REGLA, y por qué es ésta y no «usá sólo las aristas de runtime» ────────────────────────
35# Podar por tipo de ARISTA es lo que rompió firefox. Acá se poda por NODO: una receta de esta lista
36# no entra a ninguna imagen **ni se sigue explorando desde ella**, y todo lo demás sigue entrando
37# por donde entraba. Es conservador a propósito: `qtbase`, `glib` o `harfbuzz` llegan por aristas de
38# build de apps que sí viajan, y **se quedan** — decidir sobre cada librería es otra unidad de
39# trabajo, no ésta.
40#
41# ── LA VÁLVULA: una RAÍZ DECLARADA GANA ──────────────────────────────────────────────────────
42# Si un perfil declara en `paquetes` algo de esta lista, viaja en ESE perfil. No es una excepción
43# incómoda: es cómo `servidor` sigue llevando `rust`, `lld21` y `mold` —«la caja que sirve el repo y
44# construye, sí»— mientras los otros siete perfiles no los ven. Querer `python3` en una imagen es
45# una línea en su `paquetes`.
46#
47# ── CÓMO SE VALIDÓ (dos instrumentos, porque uno solo no alcanza) ─────────────────────────────
48# 1. `scripts/vigia-sonames.py --imagen --fail` — ¿queda algún `NEEDED` sin proveedor al podar?
49# Así se cayó **`libtool`** de esta lista: provee `libltdl.so.7` y lo pide `libcanberra`, que sí
50# viaja. Los `.so` los ve este vigía y NADIE MÁS.
51# 2. `scripts/vigia-interpretes.py` — los SONAME no ven un `#!`. Así se supo el precio exacto de
52# podar `perl` y `python3` (abajo), que ningún soname delataba.
53#
54# ⚠ **Esto NO re-hashea nada.** Es una propiedad de la IMAGEN, no de la receta: si el campo viviera
55# en el `.toml` entraría en `hash_inputs` y marcar 40 herramientas costaría 40 reconstrucciones más
56# las de sus consumidores. Vive acá, en el manifiesto de qué lleva cada imagen, que es de quien es.
57#
58# ⚠ **El cierre del GRAFO no cambia.** `build-state.py` sigue contando `clausura` = build ∪ runtime
59# (es «qué hay que construir», y para eso el toolchain hace falta) y ahora informa `imagen` al lado.
60# `repo-perfil.sh` tampoco cambia: un REPOSITORIO no es una imagen — que se pueda instalar `cmake`
61# desde el repo es correcto; que venga horneado en el rootfs, no.
62[solo-build]
63paquetes = [
64 # ── la cadena de compilación ──────────────────────────────────────────────────────────────────
65 # `llvm21` es el caso de libro: 1.527 MiB en la imagen del servidor y `rustc` NO lo abre —
66 # `readelf -d …/rustc` da `librustc_driver`+`libc` y nada más, porque `recipes/llvm21.toml:75`
67 # apaga el DYLIB a propósito («rustc quiere las estáticas»). 1,5 G para que nadie los mire.
68 "llvm18", "llvm21", "clang18",
69 # Enlazadores. `lld21` y `mold` son RAÍZ declarada (servidor y cli) ⇒ la válvula los deja viajar
70 # ahí; en los otros perfiles entraban por aristas de build y no tenían por qué.
71 "lld21", "mold", "binutils", "nasm",
72 # `rust` es RAÍZ de `servidor` (SDD 31, decisión escrita más abajo) ⇒ sigue viajando ahí. Se lista
73 # porque el día que otro perfil lo alcance por una arista de build, no debe entrar por eso.
74 "rust",
75 # Generadores y parsers del build. `samurai` y `meson` son los que arrastran a `python3`.
76 "make", "cmake", "meson", "samurai", "pkgconf", "bison", "flex", "m4", "gperf", "cbindgen",
77 # `autoconf` y `automake` (2026-10-20): entran al corpus y **su sitio es esta lista**, por el
78 # mismo argumento que `cmake` — que se puedan instalar desde el repo es correcto; horneados en el
79 # rootfs, no.
80 #
81 # ⚠ El motivo de que existan NO es «tener autoconf»: es poder usarlo de forma REPRODUCIBLE. El
82 # lab (`.dev-fs/alpine`) ya trae `autoconf` y `automake-1.18` con sus macros —medido—, y una
83 # receta que corra `autoreconf` sin fijar `autom4te_perllibdir`/`AC_MACRODIR`/`AUTOM4TE` usa esos,
84 # que **no entran en `hash_inputs`**: dos labs distintos sellarían bytes distintos y los dos
85 # builds parecerían correctos. Con las recetas en el corpus, un build puede declararlas y fijar
86 # las rutas.
87 #
88 # Lo que destraban, concreto: un paquete que publique sólo `configure.ac` y ningún tarball de
89 # release. Ese hueco se pisó cuatro veces el mismo día (`pcaudiolib`, `speech-dispatcher`,
90 # `brltty` se salvaron usando el asset del release; `dotconf` no, y hubo que compilar sus dos
91 # `.c` a mano).
92 #
93 # ⚠ **No manda rehacer nada**: usar el `configure` que upstream publicó sigue siendo MÁS
94 # reproducible que regenerarlo, porque el nuestro saldría de nuestra versión de autoconf — un
95 # input más. Esto abre una puerta, no cambia las recetas que ya andan.
96 "autoconf", "automake",
97 # `nodejs`: el JS del build de Gecko. Llega a las 4 imágenes de escritorio por `atuq → firefox`.
98 "nodejs",
99 # `go`: el toolchain, 222 MiB. 366 recetas lo declaran build-dep y ninguna imagen lo necesita
100 # para CORRER lo que esas recetas producen (los binarios Go son estáticos).
101 "go",
102 # ── entradas de build que no son programas: headers, datos de perfilado, código a generar ─────
103 "linux-headers", "simde", "uthash", "nlohmann-json", "vulkan-headers", "glad",
104 "wasi-sdk", "wasi-libc", "wasi-libc-headers", "wasi-libcxx", "wasi-compiler-rt",
105 # `firefox-pgo-profile`: 118 MiB de perfil de ejecución que alimenta al compilador (ADR 0013, su
106 # fuente vive sólo en el mirror). Es una ENTRADA del build de firefox, no algo que firefox abra.
107 "firefox-pgo-profile",
108 # ── generadores de documentación y de traducciones ────────────────────────────────────────────
109 "scdoc", "itstool", "intltool", "perl-xml-parser", "sassc", "libsass",
110 # ── módulos de Python que sólo existen para que corran meson y los scripts de build ───────────
111 "markupsafe", "jinja2", "mako", "packaging",
112 # ── LOS DOS INTÉRPRETES, y lo que cuesta cada uno (medido, no estimado) ───────────────────────
113 # `python3` 209 MiB · `perl` 84 MiB. Ningún artefacto que viaja pide su `.so`: el vigía de sonames
114 # da cero huecos con los dos fuera. Lo que sí hay son SCRIPTS con `#!`, y por eso existe
115 # `vigia-interpretes.py`: los cuenta, los nombra y separa los que son codegen de build horneado
116 # dentro de una librería (`glib-genmarshal`, `g-ir-scanner`, ECM, `qt-*`: inertes por diseño) de
117 # los que un usuario podría ejecutar. Los segundos son **10**, y quedan inertes a propósito:
118 # perl → 9 de `git` (`git-send-email`, `git-svn`…) · `sensors-detect` de lm-sensors
119 # python3 → `libinput-analyze/measure` (22) · `routel` · `rrsync` · `lsusb.py` · 2 filtros de aerc
120 # Un `#!` que no resuelve falla RUIDOSO y nombra al intérprete (`exec: no such file`), que es el
121 # modo de fallo bueno — no es un `.so` que se resuelva contra el sysroot del lab ni un artefacto
122 # vacío. La alternativa era hornear 293 MiB en las 8 imágenes para esos 10 scripts.
123 # ⚠ Se revierte con UNA línea: `"perl"` o `"python3"` en el `paquetes` del perfil que los quiera.
124 # ⚠ Lo que NO se midió: un binario que llame al intérprete por `system()` sin `#!`. No hay
125 # instrumento para eso acá; lo caza el arranque en QEMU, y por eso el criterio de terminado del
126 # paso 1 incluye `product-boot-test.sh`.
127 "python3", "perl",
128]
129
130
131[perfil.base]
132descripcion = "userland foundational C: la distro arranca y se usa (shell, VCS, privilegios, red, disco, TLS, proceso, editor, docs)"
133# Lift verbatim de BASE_SYSTEM_PKGS (scripts/product-userland-from-repo.sh). De-Alpinizado y firmado,
134# tandas base-system-1..3.
135# El reenviador de uevents es de la MÁQUINA, no de un escritorio: sin él, lo que se
136# enchufa después de arrancar no aparece nunca (libudev-zero escucha un grupo netlink al
137# que, sin udevd, nadie emite). Va en `base` por el mismo argumento que `dhcpcd`.
138servicios = ["uevent-relay"]
139paquetes = [
140 # `gcc-libs` (2026-09-14): `libgcc_s.so.1` y `libstdc++.so.6`. Estaba sellado y declarado sólo en
141 # los CUATRO perfiles de escritorio — y lo necesita cualquier binario ajeno que no sea estático
142 # puro. Se vio al traer el toolchain oficial de Rust: `rustc` arrancó (el cargador musl ya está,
143 # `musl-shared`) y murió con `Error loading shared library libgcc_s.so.1`. La lección de `foot`
144 # otra vez, y van tres hoy: el paquete existe y no viaja en la imagen que lo necesita.
145 "gcc-libs",
146 # `os-release` (2026-09-14): la IDENTIDAD del sistema — nombre, URL, color y **el logo oficial**.
147 # No existía: la caja decía «Host: vServer» y ni una palabra de takana. Va en `base` y no en `cli`
148 # porque toda imagen de takana debe saber decir qué es, incluida la más pelada: lo lee cualquier
149 # programa que no sea nuestro, y sin él la distro es anónima. «No sólo para esta máquina, para
150 # siempre en takana» (usuario, 2026-09-14).
151 "os-release",
152 # `tar` (GNU, 2026-09-14): el `tar` de busybox **no entiende `--zstd`** ni `-p` ni
153 # `--delay-directory-restore`. Ya costó dos veces: la caja no podía desempacar su propio LAB, y
154 # `qorpa pull` de un rootfs ajeno moría imprimiendo la ayuda de busybox sin nombrar la causa.
155 # Estaba sellado desde hace meses y en UN solo perfil (`escritorio-kde`) — la lección de `foot`
156 # otra vez: el paquete existe, y no viaja en la imagen que lo necesita.
157 "tar",
158 # `bwrap` + `harkaq-exec` (2026-09-15): LA JAULA. Los invoca `takana qorpa` —para correr una imagen
159 # ajena— y también el sandbox de `takana build` (`takana-build/src/sandbox.rs` llama a `bwrap` por
160 # PATH). Estaban en **CERO perfiles**: `bwrap` sellado desde hace meses con `"perfiles": []`, y
161 # `harkaq-exec` sin receta siquiera, sólo como un `gcc` a mano en `~/.cache/harkaq` del hub. O sea
162 # que ninguna imagen de takana podía enjaular nada, ni construir. Se descubrió mudando
163 # `api.sergio.gioser.net` a la caja de producción: `qorpa provision` abortó pidiendo un binario que
164 # sólo existía en la máquina donde alguien lo había compilado. Van en `base` porque el CLI que los
165 # llama viaja en TODAS las imágenes — [[subcomando-sin-driver]] visto un piso más abajo.
166 "bwrap", "harkaq-exec",
167 # `bash-completion` (2026-09-09): el tabulador de las herramientas del sistema. Entra como raíz
168 # porque NADIE lo alcanza por deps de build — los paquetes que instalan completados sólo preguntan
169 # por su `.pc` y, si no está, se saltan la instalación en silencio. Es un hueco de RUNTIME puro,
170 # de los que ninguna métrica de deuda puede ver. Salió del triaje de la frontera con 5 consumidores.
171 # ── `sudo` SALIÓ de acá el 2026-09-22, y `doas` se queda como único camino de escalada ───────
172 # Viajaban LOS DOS en los 7 perfiles (base lo hereda todo el mundo) y **ninguno de los dos podía
173 # funcionar**: los dos artefactos salen `r-xr-xr-x` y no había nadie que pusiera el bit setuid
174 # (`grep -rn S_ISUID crates/` ⇒ CERO). Dos herramientas para lo mismo, las dos inertes, 13 M de
175 # una de ellas en cada imagen.
176 # Lo medido que decidió cuál se queda: `sudo` 13 M contra `doas` 134 KB, y el `/etc/sudoers` que
177 # sudo instala sólo trae `root ALL=(ALL:ALL) ALL` — o sea que ni con el bit habría servido a un
178 # usuario no-root sin añadirle una regla. La receta de sudo se queda en el CATÁLOGO, que no es la
179 # imagen; el bit se declara en `recipes/doas.toml` (`[[setuid]]`) y lo pone el ensamblado.
180 # ⚠ Falta el `/etc/doas.conf`: sin él doas rechaza todo. Es la unidad siguiente y está anotada en
181 # la receta — no se inventa acá una regla de quién puede escalar.
182 # ⚠ `simi` PRIMERO, y no es cosmético: es `/bin/sh`. Faltaba, y la consecuencia era grande y
183 # silenciosa — medido el 2026-10-23 sobre el cierre de IMAGEN de `cli` (154 nodos), NINGÚN
184 # paquete proveía `sh`: `bash` instala `/bin/bash` y nada más, y `hydrate-profile.py` proyecta
185 # sólo el cierre del perfil, no parte del rootfs de arranque. O sea que todo script con
186 # `#!/bin/sh` fallaba en la imagen y el login de consola —`agetty -l /bin/sh`— no tenía qué
187 # ejecutar. El rootfs de ARRANQUE sí lo tenía desde que se quitó busybox; la imagen del perfil
188 # no. Dos fuentes de verdad que discrepaban sin que nada lo dijera.
189 "simi",
190 "bash", "bash-completion", "git", "doas", "util-linux", "shadow", "kbd", "procps-ng",
191 "iproute2", "iputils", "dhcpcd", "e2fsprogs", "dosfstools", "parted", "rsync",
192 "sed", "tzdata", "vim", "dbus", "ca-certificates", "gnupg", "lsof", "strace",
193 "pciutils", "usbutils", "mandoc",
194 # `smartmontools` (2026-10-20): SMART, o sea preguntarle a un disco si se está muriendo. Va en
195 # `base` —y por tanto en las 8 imágenes— por dos razones:
196 # · es de la MISMA familia que `parted` y `e2fsprogs`, que ya están en esta lista: herramientas
197 # de disco que uno quiere tener **antes** de necesitarlas, no después
198 # · el censo marcaba «discos y salud del equipo» 4 de 4 ausente, y acá no es hipotético: la
199 # memoria del repo tiene un disco de 5,9 G que al llenarse NO se degrada, se cae entero, y un
200 # «0 fallan» que era mentira por falta de inodos. Todo eso es ESPACIO; la salud del hardware
201 # no la miraba nadie. Un disco con sectores reasignados en aumento avisa días antes — pero
202 # sólo si alguien pregunta
203 #
204 # ⚠ PESO: 13 MB, de los que **el 82 % es `.debug_*`** (10,7 MB, sumado con readelf) ⇒ ~2,4 MB
205 # desnudado. Norma del corpus; su sitio es el SDD 23, no esta línea.
206 #
207 # ⚠ Lo que NO cubre, dicho: su receta va con `--without-libusb`, así que **no lee el SMART de un
208 # disco externo por USB**; los internos (ATA/SCSI/NVMe) van por ioctl. Y no instala servicio: el
209 # init es `arje`, así que `smartd` —la vigilancia continua— se declara ahí cuando alguien lo
210 # decida. Lo que esta línea da es `smartctl`, a mano.
211 "smartmontools",
212 # `pinentry` (2026-09-22, paso 9): el diálogo de contraseña que a `gnupg` le faltaba. Sin él, el
213 # gnupg que ya viajaba acá VERIFICA bien pero no puede usar NINGUNA clave con contraseña
214 # —`gpg: signing failed: No pinentry`—, o sea que `git commit -S` fallaba en la imagen con la
215 # única clase de clave que un usuario trae de otra máquina. Medido con los artefactos sellados:
216 # con pinentry, el mismo commit firmado sale 0. Entra como RAÍZ porque nadie lo alcanza por deps
217 # de build: gpg-agent lo busca por RUTA (`bindir/pinentry` ⇒ `/usr/bin/pinentry`) y lo ejecuta, que
218 # es un hueco de runtime puro — el mismo que `bash-completion` un poco más arriba. 1,1 MiB.
219 "pinentry",
220 # `musl-shared` (2026-09-11): EL CARGADOR DINÁMICO. Va en `base` y no en un perfil concreto porque
221 # sin él cualquier binario dinámico de la clausura queda INERTE — presente y sin poder ejecutarse.
222 # Medido en la imagen del perfil `servidor`: **23 binarios de 726** pedían
223 # `/lib/ld-musl-x86_64.so.1` y NINGÚN artefacto del corpus lo publicaba; funcionaban en el hub sólo
224 # porque el rootfs Alpine del LAB lo presta. Entre ellos, `python3` y `perl` — o sea TODO el
225 # instrumental del proyecto. El síntoma era `sh: python3: not found` con `command -v python3`
226 # contestando `/usr/bin/python3`: no falta, no arranca. Ver SDD 28 §6.4.
227 "musl-shared",
228 # `zlib-shared` (2026-09-11): el único hueco que quedó tras publicar el cargador. Con
229 # `musl-shared`, 22 de los 23 binarios inertes de la imagen del servidor pasaron a correr; el 23º
230 # era `sqlite3`, que muere con `Error loading shared library libz.so.1`. La zlib canónica es
231 # `--disable-shared` y ningún artefacto publicaba ese soname — otra vez el rootfs del lab tapando
232 # el agujero. La variante ya existía en el corpus; sólo faltaba declararla.
233 "zlib-shared",
234 # `libffi-shared` (2026-09-13): el TERCER hueco de la misma familia, y lo encontró ampliar el
235 # alcance de un vigía, no una imagen rota. `vigia-sonames.py` corría por defecto SÓLO sobre los
236 # cinco perfiles de escritorio (`p.startswith("escritorio")`), así que `base`, `cli` y `servidor`
237 # —el objetivo de la mudanza— nunca se miraron. Apuntándolo a ellos:
238 #
239 # == servidor 147 nodos · 183 sonames provistos · 1 sin proveedor
240 # FALTA libffi.so.8 ← lo piden: python3
241 #
242 # O sea que `python3` volvía a ser INERTE en las tres imágenes, por el mismo mecanismo exacto que
243 # `musl-shared` y `zlib-shared` arreglaron dos días antes: la receta canónica de libffi es
244 # `--disable-shared`, ningún artefacto del cierre publica ese soname, y en el lab el agujero lo
245 # tapa el rootfs de Alpine. La variante `libffi-shared` YA existía y ya estaba declarada en
246 # `escritorio-mirada` y en otro perfil — sólo faltaba acá.
247 "libffi-shared",
248 # `zstd-cli` (2026-09-11): la distro comprime TODO con zstd —la imagen del lab
249 # (`lab-image.tar.zst`), el respaldo al Storage Box, el `dd` remoto de una instalación— y la imagen
250 # no traía con qué descomprimirlo: **un takana recién instalado no puede desempacar su propio
251 # laboratorio** (`tar: unrecognized option: zstd`, el tar del rootfs es el de busybox).
252 #
253 # ⚠ Es `zstd-cli` y NO `zstd`. La receta canónica construye **sólo `lib/`** —lo dice su cabecera— y
254 # su artefacto no tiene `usr/bin`: declararla acá no habría arreglado nada. Se vio al aplicarla con
255 # `takana upgrade` en la caja: «✓ generación 1 aplicada, +8 añadidos» y `zstd` seguía ausente,
256 # porque los 8 ficheros eran headers y `libzstd.a`. Ampliar la canónica re-hashearía a sus 13
257 # consumidores (mesa entre ellos), así que va como variante. Ver SDD 28 §6.13.
258 "zstd-cli",
259 # ── LA CAPACIDAD QUE FALTABA: WiFi (2026-09-13, barrido de perfiles) ─────────────────────────
260 # La imagen NO podía asociarse a una red inalámbrica. `dhcpcd`, ahí arriba, resuelve la IP de un
261 # cable; el handshake WPA2 de 4 vías lo hace un supplicant en userspace, y busybox NO lo trae.
262 # La receta existía desde el frente de metal —su cabecera dice literalmente «para asociar el WiFi
263 # del medio live al AP»— y no estaba declarada en NINGÚN perfil: sellada, reproducible, y en cero
264 # imágenes. Es la figura de `foot` otra vez, y esta vez el precio era quedarse sin red.
265 # Va en `base` por el mismo argumento que `dhcpcd`: es la capa que convierte «la máquina arrancó»
266 # en «la máquina tiene red», y un portátil sin WiFi es una máquina sin red aunque el resto esté.
267 # ⚠ Son TRES binarios (`wpa_supplicant`, `wpa_cli`, `wpa_passphrase`) y nada más. NO trae gestor
268 # de red: `networkmanager` existe SÓLO en `incoming-kde`, así que GNOME, COSMIC y sway no lo
269 # alcanzan (cola hermana). Eso es una decisión aparte, escrita en el plan del barrido.
270 "wpa_supplicant",
271 # ── LA MUNICIÓN QUE YA ESTABA FABRICADA (2026-09-21, censo de proveedores) ───────────────────
272 # Seis recetas `sealed` con `perfiles: []`: construidas, reproducibles, y en CERO imágenes. Es el
273 # paso 2 de `docs/plan-botar-busybox.md`, y lo que las declara ahora no es el plan sino la
274 # MEDIDA: `scripts/busybox-censo-proveedores.py` cruzó los 277 applets vivos de busybox contra
275 # los 1407 artefactos del store y dijo, applet por applet, quién provee qué. Sin ellas, `find`,
276 # `xargs`, `diff`, `cmp`, `gzip`, `zcat` y `grep` en una imagen de takana son **el applet de
277 # busybox** — no porque falte escribirlos, sino porque nadie los declaró.
278 #
279 # `uutils` es el multicall Rust: 106 applets tras arreglarle las features (traía 78; los otros 25
280 # vivían detrás de `feat_os_unix_musl` y el binario contestaba `unknown program 'chmod'`). Ya se
281 # hidrata en el producto por `USERLAND_COMPONENTS` de takana-bootstrap, pero eso es el ensamblado
282 # del rootfs, no el catálogo: por esta vía lo alcanza también quien instale desde el repo.
283 "uutils", "findutils", "findutils-xargs", "diffutils", "gzip",
284 # `grep` es el GNU, y entra como PUENTE declarado: el plan quiere un grep POSIX en Rust sobre
285 # `grep-matcher`/`grep-regex`, y hasta que exista es esto o el applet. `ripgrep` ya viaja en
286 # `cli`, pero publica `rg` y NO es `grep` POSIX — cambian las banderas, y por eso no cuenta como
287 # el reemplazo (`docs/plan-botar-busybox.md`, «existe pero no es exacto»).
288 "grep",
289 # `xz-tools` (2026-09-21): el TERCER caso de la familia de `zstd-cli`, ahí arriba, y esta vez lo
290 # encontró el censo y no una imagen rota. `xz` figuraba en SEIS perfiles con el `usr/bin` VACÍO
291 # —su receta pasa `--disable-xz --disable-xzdec --disable-scripts`, o sea liblzma y nada más—,
292 # así que el único `xz` de la imagen era el applet de busybox. Ampliar la canónica re-hashearía
293 # a sus 14 consumidores; va como variante, igual que `zstd-cli`.
294 "xz-tools",
295 # `psmisc` y `bc` (2026-09-21): las dos piezas que el censo marcaba como TRAER, y ninguna estaba
296 # en el corpus. `psmisc` da `killall pstree fuser prtstat pslog` — y esto es lo que el plan daba
297 # por cubierto y no lo estaba: **`procps-ng` NO los trae**. procps-ng es `ps top free kill pgrep
298 # pkill pidof uptime vmstat w watch`; matar por NOMBRE y el árbol de procesos son de psmisc.
299 # `bc` es gavinhoward/bc: `bc` Y `dc` del mismo árbol, C99 sin dependencias, el que usan Alpine y
300 # FreeBSD. La alternativa de GNU arrastra `ed` y `flex`, y su `dc` va en otro paquete.
301 "psmisc", "bc",
302 # `ncurses-tools` (2026-09-22): `clear`, `reset`, `tput`, `tset`, `infocmp`, `tic`. La canónica
303 # `ncurses` instala `install.libs install.includes` y nada más ⇒ `clear` y `reset` en una imagen
304 # de takana eran el applet de busybox. Es el mismo caso que `xz-tools` y `zstd-cli`: variante con
305 # las herramientas, porque ampliar la canónica re-hashearía a sus 15 consumidores.
306 # ⚠ Instala los binarios y NO la base terminfo. La nota que había acá decía que no hacía falta
307 # porque los `--with-fallbacks` compilados dentro de la librería —linux, vt100, vt220, xterm,
308 # xterm-256color, screen, tmux, tmux-256color, ansi— cubrían «la consola del metal, QEMU y
309 # cualquier ssh moderno». **ESO ERA UNA ETIQUETA, NO UN HECHO**, y está medido en el anfitrión
310 # `takana` el 2026-09-23, con TERM que es UNO DE LOS NUEVE PINEADOS:
311 #
312 # $ TERM=xterm-256color zsh -f -c 'zmodload zsh/terminfo; echo ${#terminfo}'
313 # 0
314 #
315 # Cero capacidades: el fallback compilado NO llega. Por eso entra `ncurses-terminfo` abajo.
316 # Corolario que SÍ se sostiene: `tic` e `infocmp` no tienen base que consultar sin esa receta.
317 "ncurses-tools",
318 # `ncurses-terminfo` (2026-09-23): la BASE de datos, `/usr/share/terminfo`. Entra porque sin ella
319 # una terminal interactiva no funciona, y falla de una forma que NADIE asocia con ncurses: sin
320 # `cub1`, zle no sabe mover el cursor a la izquierda y el BACKSPACE emite un espacio suelto en vez
321 # de `\b \b` —el carácter no se borra y el hueco se corre a la derecha—; sin `kcuu1`/`khome` las
322 # flechas y Home quedan inertes; y el prompt sale con un `?` espurio pegado a cada glifo del tema.
323 # Se lee como una fuente rota y un teclado roto, no como una base de datos que falta.
324 # Control que separa la causa (el anfitrión es musl y `LANG` viene VACÍO, así que «es el locale»
325 # es la respuesta que uno quiere dar): sólo `LANG=C.UTF-8` ⇒ SIGUE ROTO; sólo la base ⇒ arreglado.
326 # Cuesta ~7,5 M y ~2900 entradas de las que una máquina usa tres: recortar ese set es su propia
327 # unidad de trabajo, pero el recorte correcto es elegir cuáles viajan, no quedarse sin ninguna.
328 "ncurses-terminfo",
329 # `akllay` (2026-09-22): el `awk` del producto, takana-PROPIO. *akllay* = escoger. «awk soberano» (usuario), que era
330 # la cuarta opción del ADR — las otras tres adoptaban la semántica de otro: gawk es C con
331 # extensiones GNU, goawk es Go y rompe la premisa de cadena de la Etapa C, frawk es otro dialecto.
332 # La receta instala `awk` como enlace, y ESO es lo que lo pone en juego: `awk` es contrato, lo
333 # llaman los `configure` de medio corpus y los guiones del producto.
334 # Medido antes de declararlo: 104 de 109 casos idénticos a busybox awk Y a gawk --posix con 0
335 # divergencias, y las tres recetas autotools de la prueba de brush (pkgconf, flex, oniguruma)
336 # sellaron con akllay como /usr/bin/awk del lab **con el hash idéntico** al vigente. Brush hacía
337 # 0 de 3. Ver `docs/plan-botar-busybox.md` §akllay.
338 "akllay",
339
340 # ── QUE LA MAQUINA TENGA RED Y VEA LO QUE SE ENCHUFA (2026-10-15) ──────────────────────────────
341 # Las dos salen del mismo reporte en metal: «que ni ethernet funciona» y «los accesorios no
342 # aparecen cuando se conectan despues de iniciar». Van en `base` y no en un perfil de escritorio
343 # porque las dos son de la MAQUINA: un servidor tambien necesita su NIC, y el greeter necesita
344 # que un teclado enchufado aparezca antes de que haya nadie dentro.
345 #
346 # ⚠ `firmware-metal` pesa 1047 MiB y el product-rootfs pesa 264-358: esto multiplica el medio
347 # por cuatro. Es una decision tomada con el peso medido delante, no por descuido — la alternativa
348 # era `firmware-intel` (120 MB, solo Intel) y dejaba sin red a cualquier maquina con wifi Realtek
349 # o Atheros, y sin pantalla a cualquiera con GPU AMD o NVIDIA (sin su firmware el driver no
350 # inicializa el dispositivo y no hay KMS). Para una imagen que ya sabe en que maquina corre esta
351 # `firmware-tigerlake` y su perfil `metal-tigerlake`.
352 "firmware-metal", # el firmware de una PC x86 COMPLETO (1047 MiB medidos): wifi, bluetooth, ethernet, graficos Intel/AMD/NVIDIA, audio
353 "uevent-relay", # sin el, un accesorio enchufado DESPUES de arrancar no aparece nunca: libudev-zero escucha un grupo netlink al que nadie emite
354]
355
356[perfil.cli]
357descripcion = "la capa CLI moderna (Rust) encima de base — el userland real del producto"
358hereda = ["base"]
359# Lift verbatim de CLI_RUST_PKGS (scripts/product-userland-from-repo.sh).
360#
361# ⚠ 2026-09-12: ESTA LISTA ERA UN LIFT DE UN SCRIPT, NO UNA DECLARACIÓN DE LO QUE HAY. Medido
362# contra los cinco grafos: de las 892 recetas selladas del corpus, **649 no están en NINGÚN
363# perfil** — o sea que el 73% del catálogo está sellado y no viaja en ninguna imagen. No es deuda
364# de build (`drenaje.json` dice 0) ni la métrica de perfil lo puede ver: mide la clausura de lo
365# DECLARADO. Es la lección de `foot` a escala de catálogo, y se descubrió al ir a declarar recetas
366# nuevas y notar que las viejas tampoco estaban.
367#
368# El barrido completo de las 649 es su propia unidad de trabajo (hay 363 recetas Go que son
369# herramientas de desarrollo importadas en tanda y NO todas deben entrar en una imagen). Lo que se
370# declara abajo es el subconjunto que ya estaba sellado y que un usuario de esta capa espera
371# encontrar — cero recetas nuevas, cero builds: sólo dejan de ser invisibles.
372paquetes = [
373 # `fastfetch` (2026-09-14, pedido del usuario): la tarjeta de presentación del sistema. En una
374 # distro propia es lo primero que contesta «¿qué estoy corriendo?» sin leer tres ficheros de
375 # `/proc`, y en una caja recién instalada distingue «arrancó» de «arrancó lo que yo creía».
376 "fastfetch",
377 "bat", "fd", "ripgrep", "jq", "zoxide", "dust", "procs", "htop", "less", "tree",
378 "hyperfine", "delta", "eza", "sd", "tokei", "fzf", "skim", "git-cliff", "gitui",
379 "starship",
380 # ── EL GESTOR DE CONTRASEÑAS (2026-10-21) ────────────────────────────────────────────────────
381 # `pass` cierra la fila «contraseñas» del censo, que decía *«nada: la gente guarda sus claves en
382 # un .txt o no usa la distro»*. Era de los pocos huecos SIN ningún camino alternativo.
383 #
384 # Va en `cli` y no en cada escritorio porque kde/gnome/cosmic/sway heredan de acá: una sola
385 # declaración cubre CINCO imágenes. (`escritorio-mirada` no hereda, es slim a propósito.)
386 #
387 # Coste medido ≈ 400 KB: `pass` son 136 KB y `qrencode` 272 KB; `tree` ya está en esta misma
388 # lista y `gnupg` (40 MB), `git`, `pinentry` y `util-linux` ya viajan en `base`. O sea que el
389 # paquete caro de la cadena estaba pagado desde antes y esto es calderilla.
390 #
391 # ⚠ `pass -c` (copiar al portapapeles) necesita `wl-clipboard`, que hoy sólo declara
392 # `escritorio-sway`. En las otras imágenes el resto de `pass` funciona y `-c` muere con un
393 # mensaje claro, que es el comportamiento de upstream, no una avería nuestra.
394 #
395 # Y NO se declara como dep de runtime de `pass` a propósito — lo hice y hubo que deshacerlo el
396 # mismo día: las deps de runtime se propagan al cierre de IMAGEN, así que metía
397 # `wayland ← wl-clipboard ← pass` en esta capa, o sea 5 nodos y ~3 MB, **más un binario roto**
398 # (el `wayland-scanner` del artefacto de wayland pide `libexpat.so.1`, que acá no existe; lo
399 # denunció `vigia-sonames.py --imagen`). Y encima para nada: sin servidor gráfico `wl-copy` no
400 # puede funcionar en `cli` ni en `servidor`. ⇒ Lo declara quien tiene sesión Wayland.
401 "pass", "qrencode",
402 # ── LOS ESENCIALES QUE FALTABAN (2026-10-23) ─────────────────────────────────────────────────
403 # Medido: `curl` y `wget` sólo estaban en `servidor`/`servidor-minimo`, así que una imagen de
404 # escritorio o de terminal NO PODÍA BAJAR UN FICHERO. Y `which` y `file` estaban SELLADOS Y EN
405 # NINGÚN PERFIL — `which` lo invocan incontables scripts ajenos y pesa 448 K.
406 #
407 # Van en `cli` y no en cada sitio porque los cuatro escritorios y `servidor` heredan de acá.
408 # Coste medido: curl 39 M + wget 24 M + file 11 M + which 448 K ≈ 74 M. Es caro para un fetcher,
409 # y es lo que cuesta un TLS estático; se paga porque «no puedo descargar nada» no es una distro.
410 #
411 # ⚠ `curl` Y `wget` los dos a propósito, no por descuido: un script ajeno usa uno o el otro sin
412 # preguntar, y tener sólo uno convierte cada script de internet en una edición a mano.
413 "curl", "wget", "which", "file",
414 # ── EL CABLEADO DE LA TERMINAL (2026-10-23) ──────────────────────────────────────────────────
415 # `consola` NO trae herramientas: trae `/etc/profile`, `/etc/simirc`, `/etc/bash.bashrc`,
416 # `/etc/inputrc`, el `starship.toml` y el `LS_COLORS` horneado. Sin él las doce herramientas de
417 # consola que el corpus ya embarcaba estaban INSTALADAS Y SIN CONECTAR: `LANG` sin definir, el
418 # prompt pelado, ↑ recorriendo el historial entero, campana en cada Tab, `ls` sin color.
419 #
420 # Todas sus menciones de herramientas van tras un `command -v` (lo verifica su guardián 4), así
421 # que puede instalarse en una imagen que no traiga ninguna. Por eso también cabría en `base`;
422 # está en `cli` porque es donde viven las herramientas que cablea.
423 "consola",
424 # ── LA DOCUMENTACIÓN DEL SISTEMA (2026-10-21) ────────────────────────────────────────────────
425 # `man-pages` son 18 MB y 3.153 páginas, y cierran un hueco que el censo describía MAL. No era
426 # «no hay páginas de manual»: el store ya tenía 4.710 de sección 3 y 1.074 de la 1, porque cada
427 # paquete trae las suyas y `man pass` funciona desde siempre. Lo que faltaba era exacto:
428 #
429 # find /store/*/usr/share/man/man2 -type f | wc -l ⇒ 0
430 #
431 # CERO llamadas al sistema documentadas en todo el corpus, más las funciones de libc y las
432 # páginas de concepto. `man open`, `man mmap`, `man malloc` y `man signal` no contestaban nada.
433 # Para una distro que se compila a sí misma desde fuente eso es un hueco con nombre.
434 #
435 # Va en `cli` porque es la capa de quien vive en una terminal, y por herencia llega a los cuatro
436 # escritorios grandes y a servidor. `mandoc` —que publica `man`, `apropos` y `whatis`— ya viaja
437 # en `base`, así que el lector estaba puesto y sólo faltaba qué leer.
438 #
439 # ⚠ Lo que esto NO arregla: `apropos`, `whatis` y `man -k` siguen sin funcionar, y no es culpa de
440 # este paquete. `makewhatis` (de mandoc, en /usr/sbin) aborta a mitad de escribir el índice.
441 # Diagnóstico medido con strace desde el anfitrión (dentro de la jaula no se puede: ptrace está
442 # prohibido), y corrige dos cosas que creí antes:
443 #
444 # · NO es endianness. El mágico (0x3a7d0cdb) y la versión se leen BIEN con be32toh.
445 # · NO falla ninguna llamada al sistema: todos los write y lseek de la traza tienen éxito.
446 # · El fichero queda INCOMPLETO: `int[2]` y `int[3]` —los punteros que `dba_write()` rellena
447 # AL FINAL con un seek hacia atrás— quedan en CERO con 552 bytes de contenido detrás. Por eso
448 # el lector dice «Inconsistent file size 552 (expected 4)»: calcula el tamaño desde int[3].
449 # Es un SÍNTOMA del aborto, no un desacuerdo de formato.
450 # · El mensaje de error es INÚTIL: sale del `perror(NULL)` final de su `say()` y muestra un
451 # errno RANCIO. Probado: con `TZ=UTC` el mismo fallo pasa a decir «No error information»
452 # mientras el índice sigue sin generarse. El `/etc/localtime` ausente sólo COLOREA el texto.
453 #
454 # ⇒ El aborto ocurre entre escribir las tablas de páginas/macros y rellenar los punteros, y sale
455 # con 6 (SYSERR). Pinchar la línea exacta pide una sesión de depurador sobre mandoc: es su propia
456 # unidad de trabajo, y además tocar mandoc reconstruye `base` y cascadea a las diez imágenes.
457 "man-pages",
458 # ── SELLADAS DESDE HACE MESES Y EN NINGUNA IMAGEN (2026-09-12) ───────────────────────────────
459 # editor, multiplexor, gestor de ficheros e historial de shell: las cuatro piezas con las que se
460 # VIVE en una terminal. `helix` estaba declarada sólo en escritorio-gnome, que es donde menos
461 # sentido tiene — es la capa CLI la que la necesita.
462 "helix", "zellij", "yazi", "atuin",
463 # VCS más allá de git: jj (jujutsu) como VCS moderno, lazygit de TUI, difftastic para diffs
464 # que entienden sintaxis.
465 "jujutsu", "lazygit", "difftastic",
466 # el cinturón de quien construye cosas
467 "just", "direnv", "watchexec", "mise",
468 # red y datos: cliente HTTP, cifrado de ficheros, copia a remoto y respaldo
469 "xh", "age", "rclone", "restic",
470 # sistema: los dos monitores y los dos contadores de disco
471 "bottom", "btop", "duf", "ncdu",
472 # ── NUEVAS DE LA TANDA DEL 2026-09-12 ───────────────────────────────────────────────────────
473 # shell moderna (INTERACTIVA, no /bin/sh: su lenguaje no es POSIX), correo en terminal,
474 # reproductor de música y sincronización entre máquinas.
475 "nushell", "aerc", "cmus", "syncthing",
476 # herramientas de quien CONSTRUYE con la distro, no de quien la usa: `sccache` ya estaba sellada
477 # y sin perfil, `mold` es de hoy. Van acá y no en `base` porque un servidor pelado no las quiere.
478 "sccache", "mold",
479 # ── BARRIDO DE PERFILES (2026-09-13): selladas hace meses y en ninguna imagen ────────────────
480 # Criterio para entrar acá, y está escrito porque el barrido encontró 620 hojas sin perfil y
481 # meterlas todas habría sido peor que no mirar: entra lo que un usuario de esta capa ESPERA
482 # encontrar y hoy no está. Lo demás queda en el catálogo y se instala por nombre — ver
483 # `docs/plan-barrido-perfiles-y-servicios.md`, §la política.
484 "nano", # el editor que teclea quien no sabe salir de vim; `base` sólo trae vim
485 "tig", # git en TUI; el corpus tenía lazygit y gitui y ninguno declarado hasta hoy
486 "patch", # aplicar un diff a mano. Userland clásico que simplemente faltaba
487 "socat", # el destornillador de red: sockets, puertos, diagnóstico
488 "pigz", # gzip en paralelo
489 "miller", # `jq` para CSV/TSV — el hueco de datos tabulares
490 "dwarves", # `pahole`: inspeccionar el BTF/DWARF del kernel. Esta distro compila el suyo
491 # ── BARRIDO DE LA MÁQUINA DEL USUARIO (2026-09-16) ──────────────────────────────────────────
492 # `zsh` es EL SHELL DE LOGIN del usuario, medido en su historial (1611 `cd`, 1054 `cargo`, 134
493 # `zellij`), y no tenía receta ni en el corpus ni en ninguna de las cuatro colas. Una imagen de
494 # takana instalada en su laptop lo dejaba en `bash`: funciona, pero no es su máquina. Entra en
495 # `cli` y no en `base` porque `base` define el shell del SISTEMA (bash) y esto es el del usuario
496 # — dos hechos distintos, como `paquetes` y `servicios` un piso más abajo.
497 # Es el mismo hueco de RUNTIME que `bash-completion`: nadie lo alcanza por deps de build, así que
498 # ninguna métrica de deuda podía verlo. Sólo se ve mirando la máquina de quien la usa.
499 "zsh",
500 # ── LOS QUE ESTABAN SELLADOS Y NO VIAJABAN (2026-10-19) ─────────────────────────────────────
501 # Salen de `scripts/censo-distro.py`, que mide lo que el resto del tablero no: no «¿está
502 # construido lo que declaramos?» —eso da 1158/1158— sino **«¿alcanza para usarla?»**. De sus
503 # 170 renglones, 12 ya estaban SELLADOS y en NINGÚN perfil: declararlos es una línea cada uno,
504 # no una receta. Es la lección de `foot` por enésima vez, y la razón por la que el párrafo de
505 # arriba existe.
506 #
507 # ⚠ De los 12 entran SIETE, y las cinco bajas valen más que las altas:
508 # · `mako` NO es el daemon de notificaciones de sway — es el MOTOR DE PLANTILLAS de Python
509 # que Mesa usa para codegen. Mi censo lo clasificó por el nombre y se equivocó;
510 # está fuera de todo perfil con razón, es build-only.
511 # · `nodejs` su propia receta dice que la distro NO lo publica: «acá Node es un compilador
512 # más», entra al sandbox de Firefox y desaparece. Declararlo contradice una
513 # decisión escrita.
514 # · `typst` · `valkey` · `kakoune` — el artefacto VIGENTE no está en el store (y kakoune vive
515 # en `tandas/`, fuera del catálogo). Son trabajo de granja antes de ser una línea.
516 "uv", # el instalador de herramientas Python. Sin él, `pip` no viaja y no hay forma de
517 # instalar nada del ecosistema Python en la máquina instalada
518 "micro", # editor de terminal con teclas de hoy (Ctrl-S/Ctrl-Q). `vim` y `helix` están, y
519 # los dos exigen aprenderlos: esto es para quien no quiere
520 "elvish", # shell estructurada. Va en `cli` por lo mismo que `zsh`: es shell de USUARIO, no
521 # del sistema — `base` define el del sistema y son dos hechos distintos
522 "gopass", # gestor de contraseñas. El censo marca la categoría entera como hueco (nada:
523 # ni keepassxc, ni pass) y éste ya estaba sellado — tapa la mitad de CLI del hueco
524 # `typst` (2026-10-19): composición tipográfica. Tapa el hueco de «documentos técnicos», que el
525 # censo marcaba 3/4 — no hay texlive, ni pandoc, ni groff, o sea que la distro no sabía producir
526 # un PDF desde texto marcado por NINGÚN camino. Es Rust puro y CLI: ni toolkit ni muro gráfico.
527 # ⚠ Su artefacto estaba sellado en el WORKER y no en el hub. No era trabajo de granja: era
528 # traerlo (`rsync -azH`, excluyendo `.dmerge`, aterrizando por `.incoming-*` + `mv` para que un
529 # directorio a medias no quede como cache-hit envenenado). El grafo decía `sealed` con razón —
530 # lee el manifiesto del worker además del store local—; `takana hash --check` decía NO VIGENTE
531 # con razón también, porque mira el store LOCAL. Las dos fuentes eran ciertas y medían cosas
532 # distintas, y yo leí la contradicción como «falta construirlo».
533 "typst",
534 # ── ABRIR UN .ZIP, QUE CREÍAMOS QUE NO SE PODÍA (2026-10-19) ─────────────────────────────────
535 # `censo-distro.py` marcaba «archivos comprimidos ajenos» 5/5 ausente, con el argumento de que
536 # `libzip` es una BIBLIOTECA y no el comando. Cierto para `libzip` — y FALSO como conclusión:
537 # **`libarchive` estaba sellado y trae `bsdunzip`, `bsdtar`, `bsdcpio`, `bsdcat`**, y sólo se
538 # declaraba en `escritorio-kde`. O sea que las otras nueve imágenes no podían abrir un adjunto
539 # por un paquete que ya estaba construido.
540 #
541 # MEDIDO con un zip de verdad (deflate, con subdirectorio) antes de escribir esto:
542 # bsdunzip -o x.zip extrae y conserva el árbol ✓
543 # bsdtar -xf x.zip ídem ✓
544 # bsdtar -a -cf x.zip ... CREA un zip que python zipfile lee ✓
545 #
546 # ⚠ Los binarios NO se llaman `unzip` ni `zip`, así que un script ajeno que invoque esos nombres
547 # sigue fallando y el censo sigue contándolos como huecos — hace bien: mide equivalencia por
548 # NOMBRE. Lo que cambia es que la CAPACIDAD está, y eso baja el hueco de «no hay camino» a
549 # «faltan los alias». Es la misma figura que `agetty` cubriendo a `getty` con otro nombre.
550 # ⚠ No se midió 7z ni rar: libarchive los soporta en lectura según upstream, pero sin un fichero
551 # de prueba no lo afirmo.
552 "libarchive",
553]
554
555[perfil.escritorio-mirada]
556descripcion = "rootfs del USB mirada: compositor Wayland propio sobre mesa software, riel pata y el instalador churay"
557# NO hereda base: es una imagen slim a propósito. El ORDEN importa (libs primero, binarios mirada
558# al final) y se preserva. `scripts/mirada-usb.sh` deriva su PKGS de acá (`targets.py
559# escritorio-mirada`), así que esta lista ES la del USB — no una copia que pueda divergir.
560#
561# ── Las tres `-shared` (2026-09-05) ──────────────────────────────────────────────────────────────
562# Cazadas por `scripts/vigia-sonames.py`: `mesa-swrast` y `wayland` salen con `NEEDED libexpat.so.1`,
563# `libz.so.1` y `libffi.so.8`, y las recetas canónicas de expat/zlib/libffi son `--disable-shared`.
564# Ningún artefacto del cierre publicaba esos SONAME ⇒ el rootfs los resolvía contra el sysroot Alpine
565# DEL LAB y la imagen sólo arrancaba donde hubiera Alpine debajo.
566#
567# Es la MISMA fuga que los otros cuatro perfiles ya cerraron el 2026-09-03; mirada quedó fuera porque
568# su lista nació como copia literal del script y nadie la revisó. Van de raíces —y no de `[deps]`—
569# porque son data de RUNTIME: nadie las declara como dep de BUILD, así que la clausura no las alcanza
570# por ninguna arista. Las tres viven en `corpus`, la misma cola, y ya están selladas ⇒ CERO rebuilds.
571#
572# NO se suman `bzip2-shared` ni `ncurses-shared`, que sí llevan los otros perfiles: acá esos sonames
573# los pide sólo `python3`, que es HERRAMIENTA DE BUILD y no viaja en la imagen. Lo accionable del
574# vigía es lo que pide un componente de runtime, y por eso imprime siempre quién pide cada cosa.
575#
576# ── EL ESCRITORIO COMPLETO, Y ALIGERAR ES APAGAR PLUGINS (2026-10-13, decisión del usuario) ──────
577# Hasta hoy este perfil declaraba compositor, greeter y ctl: **un escritorio sin panel, sin fondo y
578# sin notificaciones**. El síntoma fue de instalación — con churay, «me montó un busybox, luego dice
579# que no hay pata» — y la causa doble: `pata` no existía como receta Y no estaba en ningún perfil.
580# Faltar una de las dos cosas no se parece a faltar la otra, y acá faltaban las dos.
581#
582# ⇒ **La imagen lleva el componente COMPLETO. Aligerar se hace deshabilitando plugins, no omitiendo
583# paquetes.** Es la regla que fija el usuario y conviene entender por qué: un paquete que no está no
584# se puede encender, así que recortar la lista decide por el usuario de la máquina y además lo
585# decide en un sitio —esta tabla— donde él no puede llegar. Apagar un plugin es reversible en su
586# máquina y sin reconstruir nada; no empaquetarlo no lo es.
587#
588# `pata` son SIETE binarios y no uno: el riel (`pata-llimphi`), el que toma los pedidos (`pata`),
589# los espacios (`pata-space`), el centro (`pata-centro`) y la cadena de notificaciones
590# (`pata-notify`, `pata-notify-panel`, `pata-notify-triage`). Y `mirada-wallpaper` entra por el
591# mismo censo: el código de mirada lo referencia 30 veces y tampoco estaba.
592#
593# ⚠ **`churay` aparecerá como `wanted` en el grafo, y está bien así.** Es la cara de ventana del
594# instalador, y su receta la tiene OTRO AGENTE escrita y SIN COMMITEAR (9.487 bytes, 11-oct 19:00,
595# con la medición de por qué la cara de Vello no sirve acá). Declararla acá deja el hueco VISIBLE en
596# `cierres.json` y en `build-state` —«declara sin receta: wanted/churay»— en vez de en un comentario
597# que nadie lee. El día que se commitee, la marca pasa a hash sola.
598# ── LO QUE ARRANCA EL ESCRITORIO (2026-10-15) ────────────────────────────────────────────
599# Hasta hoy este perfil no habilitaba NINGÚN servicio, y `targets.py --services` lo venía
600# denunciando: «`mirada-compositor` está en la imagen y TRAE este servicio, pero el perfil no
601# lo arranca». La semilla del piso trae `hammerd`, `console-getty` y `sshd` y nada más, así
602# que la imagen arrancaba a una consola de texto con el escritorio adentro y nadie
603# llamándolo — la misma figura que `foot`, una vuelta más arriba.
604#
605# El ORDEN importa: `seatd` antes que `mirada`, porque sin el seat el compositor no obtiene
606# el DRM y muere con el dispositivo ocupado.
607servicios = ["seatd", "mirada", "shuma-daemon", "shuma-gateway"]
608paquetes = [
609 # ── BAJAR PRIVILEGIOS (2026-10-22) ───────────────────────────────────────────────────────────
610 # `setuidgid` lo invocan las cards de servicio de este perfil por ruta absoluta: lo necesitan
611 # `gitea`, `minga`, `pacha`, `pacha-secretos` y `shuma-daemon`, porque esos daemons se niegan a
612 # correr como root y arje-zero encarna sus cards COMO root. Hasta hoy lo daba el applet de
613 # busybox y era el ÚLTIMO sin dueño propio; ahora es un binario de 330 KB, estático y sólo libc.
614 "setuidgid",
615 "expat-shared", "zlib-shared", "libffi-shared",
616 "mesa-swrast", "wayland", "libdrm", "seatd", "libinput", "libudev-zero",
617 "libxkbcommon", "pixman", "linux-pam", "mtdev", "libevdev",
618 "mirada-compositor", "mirada-greeter", "mirada-ctl", "mirada-wallpaper",
619 # el riel completo
620 "pata", "pata-llimphi", "pata-space", "pata-centro",
621 "pata-notify", "pata-notify-panel", "pata-notify-triage",
622 # el instalador: las dos caras que ya tienen receta, y la de ventana que la espera
623 "churay-tui", "churay-metal", "churay",
624
625 # ── LO QUE EL ESCRITORIO NECESITA Y NADIE DECLARABA (2026-10-15) ──────────────────────────
626 # Salieron de armar el escritorio del operador a mano y CONTAR lo que quedaba adentro, uno por
627 # uno. Hasta hoy vivian en la variable `EXTRA` de `tawasuyu:scripts/armar-escritorio-de-takana.sh`
628 # ⇒ el escritorio que el operador usa no lo reproducia una instalacion de cero. Es la MISMA fuga
629 # que cerraron las tres `-shared` de arriba: `mirada-usb.sh` cierra las librerias copiando del
630 # devfs de Alpine DEL LAB, asi que el medio arranca y el hueco no se ve hasta que la imagen vive
631 # sola. El precio medido en metal el 14-oct: un escritorio que no pinta un solo rotulo, sin riel
632 # y sin ninguna terminal, en una maquina donde claude ya estaba instalado.
633 "dejavu-fonts", # el compositor NO embute fuentes: sin un .ttf no pinta un solo rotulo, y se calla
634 # ── LAS FUENTES DEL MUNDO (2026-10-19) ───────────────────────────────────────────────────────
635 # `dejavu-fonts` cubre latino, griego y cirílico, y era LO ÚNICO que tenía la distro: medido con
636 # `scripts/censo-distro.py`, chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés,
637 # hebreo y los emoji salían como cuadraditos — con el tablero entero en verde, porque ninguna
638 # otra métrica mide «¿alcanza para usarla?».
639 #
640 # Las tres hacen falta y ninguna sustituye a otra: son tres repositorios distintos de upstream.
641 # 144 MB en total por imagen, y es el precio de no dejar afuera a la mayor parte de la humanidad.
642 # noto-fonts 48 MB Regular+Bold hinted de TODAS las familias (321 ficheros)
643 # noto-fonts-cjk 86 MB 2 .ttc; cada uno trae zh-Hans, zh-Hant, ja y ko a la vez
644 # noto-fonts-emoji 10 MB NotoColorEmoji — fontconfig ya la prefiere de fábrica, sin `.conf`
645 "noto-fonts", "noto-fonts-cjk", "noto-fonts-emoji",
646 # ── ESCRIBIR, NO SÓLO LEER (2026-10-20) ──────────────────────────────────────────────────────
647 # Las fuentes Noto del 19-oct hicieron que la distro LEA chino, japonés, coreano, árabe e hindi.
648 # Esto es lo que falta para ESCRIBIRLOS: un teclado tiene cien teclas y esos sistemas no se
649 # teclean uno-a-uno. `censo-distro.py` marcaba la categoría «escribir en esos idiomas» 5 de 6.
650 #
651 # Entran los TRES JUNTOS y el orden del razonamiento importa:
652 # `fcitx5` el marco: daemon, protocolo y barra de candidatos. Habla
653 # `zwp_text_input_v3` / `zwp_input_method_v2`, que `mirada-compositor` registra
654 # (`setup.rs:600-602`) — sin eso ningún IME serviría en esta distro.
655 # `fcitx5-hangul` el MOTOR coreano. Sin un motor, el marco es una barra que no convierte nada:
656 # la figura de `crun` sin podman, y la razón por la que fcitx5 se construyó el
657 # 19-oct y NO se declaró hasta hoy.
658 # `libhangul` ⚠ y no es sólo dep de build. Su código va ESTÁTICO dentro del addon, pero el
659 # diccionario hanja NO: la ruta `/usr/share/libhangul/hanja/hanja.txt` está
660 # HORNEADA en la `.a` (medido con `strings`), así que sin este paquete en la
661 # imagen la conversión hanja busca un fichero que no existe. Es la figura de
662 # «un directorio vacío como sonda» por el otro lado: el código está, el dato no.
663 #
664 # ⚠ EL COREANO PRIMERO POR COSTE MEDIDO, no por preferencia: deps nuevas por motor —coreano 1
665 # (`libhangul`), japonés 2 (`anthy` + `fcitx5-qt`), chino 6 (`libime`, `libpinyin`, `opencc`,
666 # `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**). El chino además necesita que Qt salga de
667 # `incoming-kde` al corpus. Los otros dos son campañas con su propio presupuesto.
668 #
669 # ⚠ PESO: 294 MB (fcitx5 284 + libhangul 6,6 + el motor 3,3). Y **el 87 % de los binarios de
670 # fcitx5 son secciones `.debug_*`** —294 MB de 334, medido con readelf—, así que desnudado serían
671 # ~40 MB. No es una anomalía suya: `foot` y `mpv` también van sin desnudar, es la norma del corpus
672 # y su sitio es la campaña del split de debug (SDD 23), no esta línea.
673 # Y el JAPONÉS (2026-10-20), por el mismo camino y a la primera: `anthy-unicode` convierte kana a
674 # kanji y `fcitx5-anthy` lo enchufa al marco. ⚠ Costó UNA receta, no dos — en la nota de arriba
675 # escribí «japonés 2 (anthy + fcitx5-qt)» leyendo los makedepends de Alpine, y su CMakeLists no
676 # menciona Qt ni una vez. `anthy-unicode` viaja por su DICCIONARIO (4 ficheros), igual que
677 # `libhangul` por el hanja: sin los datos el motor carga y no convierte nada.
678 "fcitx5", "fcitx5-hangul", "libhangul", "fcitx5-anthy", "anthy-unicode",
679 # Y el CHINO (2026-10-20), que cierra los tres grandes. ⚠ **La estimación de la nota de arriba era
680 # FALSA, y es la TERCERA vez con el mismo defecto**: decía «chino 6 (`libime`, `libpinyin`,
681 # `opencc`, `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**)» y que haría falta sacar Qt de
682 # `incoming-kde` al corpus. Medido sobre los `CMakeLists` y no sobre los `makedepends` de Alpine:
683 # **cero Qt, cero libpinyin, cero fcitx5-lua, cero qtwebengine.** Qt sólo aparecía detrás de
684 # `ENABLE_GUI`, que es la ventana de configuración y se apaga; el navegador embebido, detrás de
685 # `ENABLE_BROWSER`. Mismo error que ya está anotado dos párrafos arriba para el japonés.
686 #
687 # ⚠ **`libime` NO se declara acá, y eso es medido, no un olvido.** Se construye con
688 # `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias de libc++), así que entra ENTERA
689 # dentro de los addons: comprobado con `readelf -d` que **ningún** `.so` de
690 # `fcitx5-chinese-addons` declara `libIME*`. Declararla metería 159 MB en cada imagen para nada.
691 # Lo que sí viaja son sus DATOS, que el addon busca en ejecución.
692 #
693 # `fcitx5-chinese-addons` pinyin + tabla + helper de trazos + chttrans + puntuación +
694 # ancho completo. 6 addons
695 # `opencc` simplificado ↔ tradicional. Viaja porque `libchttrans.so` enlaza
696 # `libopencc.so` de verdad (lo exige el guardián): sin él la mitad del
697 # chino escrito queda afuera, y no con un error sino en silencio
698 # `libime-lm` el modelo de lenguaje (`zh_CN.lm`, 35 MB). Sin esto el pinyin carga
699 # y **no ordena candidatos**
700 # `libime-dict` el diccionario de pinyin + la Extensión B de Unicode
701 # `libime-table` las 8 tablas (wubi, cangjie, erbi…): quien escribe por FORMA y no
702 # por pronunciación. Sin ellas el método por tabla existe y no carga nada
703 # `fcitx5-pinyin-stroke` buscar un carácter por sus TRAZOS — el caso de quien no sabe cómo se
704 # `fcitx5-pinyin-table` pronuncia, que es justo cuando un método por pinyin no sirve
705 #
706 # ⚠ PESO: 189 MB los siete. El gordo es el addon (105) y el modelo (35), y **el 90 % de libime es
707 # `.debug_*`** (143 de 159 MB, sumado con readelf) — la norma del corpus, cuyo sitio es el SDD 23.
708 #
709 # ⚠ QUEDA AFUERA Y SE DICE: `ENABLE_GUI=Off` ⇒ **no hay ventana de configuración** (es Qt y no
710 # tenemos `fcitx5-qt`), así que elegir diccionarios o disposición se hace editando ficheros. Y
711 # `ENABLE_CLOUDPINYIN=Off` es DECISIÓN y no falta de dep: manda lo que teclea el usuario a un
712 # servidor remoto.
713 "fcitx5-chinese-addons", "opencc", "libime-lm", "libime-dict", "libime-table",
714 "fcitx5-pinyin-stroke", "fcitx5-pinyin-table",
715 # ── LA MÁQUINA HABLA (2026-10-20) ────────────────────────────────────────────────────────────
716 # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**, y es el hueco que deja gente
717 # afuera de verdad: sin voz, una distro no es incómoda para quien no ve — es INUSABLE. Y ninguna
718 # otra métrica del repo lo ve: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la
719 # máquina es muda.
720 #
721 # `espeak-ng` son **21 MB con 114 diccionarios de idioma**, por síntesis formante: sin modelos, sin
722 # red y sin GPU. Instala cuatro nombres (`espeak-ng`, `espeak`, `speak-ng`, `speak`) porque los
723 # scripts viejos invocan los dos primeros.
724 #
725 # Los tres van juntos y el orden es el de la cadena:
726 # `espeak-ng` el sintetizador y su `libespeak-ng.so` (lo que `orca` cargaría con dlopen)
727 # `pcaudiolib` la salida de audio. ⚠ Sin ella espeak-ng **compila igual** y sintetiza a `.wav`
728 # sin reproducir: `espeak-ng "hola"` no haría ruido. Verificado en el artefacto —
729 # la `.so` trae libpulse/libasound en NEEDED y exporta los `audio_object_*`
730 # `sonic` cambia la velocidad sin cambiar el tono. ⚠ No es opcional aunque su
731 # `find_library` no lleve REQUIRED: si no la encuentra, espeak-ng la CLONA de git
732 # en tiempo de build, y el sandbox no tiene red
733 #
734 # ⚠ Lo que esto NO cierra: **`orca`**, el lector de pantalla, necesita `at-spi2-core` y
735 # `py3-gobject`, los dos sólo en `incoming-gnome` — la misma pila de introspección que bloqueó a
736 # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
737 # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
738 "espeak-ng", "pcaudiolib", "sonic",
739 # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
740 # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
741 # distinto que las otras no tapan:
742 #
743 # `speech-dispatcher` el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
744 # `dotconf` sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
745 # Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
746 # Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
747 # detrás como módulos. `dotconf` es su parser de configuración, la única
748 # dep que faltaba
749 # `brltty` los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
750 # de gusto a la voz: braille es lo que permite leer código, ortografía y
751 # matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
752 # además no oye. Con voz y sin braille se deja afuera a sordociegos por
753 # completo
754 # `liblouis` braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
755 # carácter: legible, tres veces más largo, y nadie lee así un texto
756 #
757 # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
758 #
759 # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
760 # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
761 # bloqueada por DOS cosas distintas:
762 # · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
763 # dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
764 # rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
765 # Es una decisión de operador, no algo que se hace de paso
766 # · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
767 # en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
768 #
769 # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
770 # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
771 # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
772 # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
773 # pendiente y no un olvido.
774 "speech-dispatcher", "dotconf", "brltty", "liblouis",
775 # ── Y SE PUEDE ABRIR UNA IMAGEN (2026-10-20) ────────────────────────────────────────────────
776 # `censo-distro.py` marcaba «visores de imagen» **5 de 5 ausente**: la distro no tenía NINGUNA
777 # forma de abrir un JPEG. Los tres candidatos obvios se descartaron por MEDICIÓN, no por fama:
778 # `nsxiv`, `feh` son Xlib, y esta distro es Wayland-only ⇒ muro
779 # `imv` el recomendado para Wayland, y **no sirve acá**: pide `egl`, `gl`, `opengl`,
780 # `glu` y `wayland-egl`, y este corpus no publica `libGL.so` ni `gl.pc`
781 # (comprobado) — el mismo muro que bloquea a `kitty`
782 # `swayimg` **cero menciones de GL** en su `meson.build`. Pinta por software
783 #
784 # `swayimg` png, jpeg, webp, tiff y gif, **cada formato con su perilla FIJA**: sus opciones son
785 # `feature` con default `auto`, y una dep ausente se saltaría EN SILENCIO dejando un
786 # visor que no abre PNG y no dice por qué
787 # `exiv2` la rotación EXIF. Una cámara de móvil no rota los píxeles: anota la orientación en
788 # una etiqueta. Sin esto **todas las fotos verticales salen tumbadas**, sin error y
789 # sin remedio. ⚠ NO se declara acá: entra ESTÁTICA dentro del binario (su `.so`
790 # habría cruzado objetos de C++ entre módulos, que es lo que segfaulteó a
791 # `libime_pinyindict`)
792 #
793 # ⚠ PESO: 70 MB, y **el 85 % es `.debug_*`** (59 de 69 MB, sumado con readelf) ⇒ ~10 MB
794 # desnudado. La norma del corpus; su sitio es el SDD 23.
795 #
796 # ✅ El binario sale **autosuficiente: un solo `NEEDED`, `libc.so`** (medido). Todo lo demás
797 # —wayland, xkb, drm, png, jpeg, webp, tiff, gif, fontconfig, freetype, lua— entra estático, así
798 # que declararlo no arrastra ningún proveedor compartido.
799 #
800 # ⚠ QUEDA AFUERA Y SE DICE: `svg=disabled`, porque `librsvg` sólo existe en `incoming-gnome`
801 # enredada con su pila de introspección (promoverla cuesta 35 recetas, medido). **No abre SVG.**
802 # Y `heif/avif/jxl/jp2/exr/raw` tampoco: esas bibliotecas no están en el corpus.
803 #
804 # ⚠ Y UNA DECISIÓN QUE NO TOMO ACÁ: `swayimg` se construyó con `drm=enabled`, o sea que puede
805 # mostrar una imagen **desde una consola TTY, sin compositor**. Eso es exactamente lo que querría
806 # el perfil `cli`, que hoy no lleva visor. No se declara ahí porque son 70 MB en una imagen que no
807 # me pidieron cambiar — pero el coste está medido y la capacidad existe.
808 "swayimg",
809 # ── Y SE PUEDE SUBIR EL VOLUMEN (2026-10-20) ────────────────────────────────────────────────
810 # El censo marcaba «audio de escritorio» 5 de 5 ausente, con el problema exacto: «PipeWire suena y
811 # no hay con qué subirle el volumen desde una ventana». O sea que la distro reproducía audio y
812 # **no tenía ningún control de volumen** — ni gráfico, ni de consola, ni un atajo de teclado
813 # posible, porque un atajo llama a un comando y el comando no existía.
814 #
815 # `pamixer` sube, baja, silencia y lista fuentes y sumideros: es lo que un compositor engancha a
816 # las teclas de volumen. Su `cxxopts` NO se declara — es sólo una cabecera y no deja nada que viaje.
817 #
818 # ⚠ Habla el protocolo de PulseAudio y eso NO es un desajuste: `pipewire` publica esa capa
819 # (`pipewire-pulse` + `libpipewire-module-protocol-pulse.so`, verificado en el artefacto) y la
820 # biblioteca cliente `libpulse.so.0` la publica `pulseaudio`. Las dos ya viajan en estos cinco
821 # perfiles, igual que `libsndfile`, así que esto cuesta sólo sus 7,5 MB.
822 #
823 # ⚠ Su cadena de runtime baja TRES niveles y es lo que hizo fallar el primer build:
824 # `pamixer → libpulse.so → libpulsecommon.so → libsndfile.so.1`. Está documentada igual en
825 # `recipes/espeak-ng.toml` — el sandbox sólo monta lo declarado, y yo no la consulté.
826 "pamixer",
827 "fontconfig-shared", # /etc/fonts/fonts.conf y el soname que enlaza el riel
828 "freetype-shared", # libfontconfig.so.1 pide libfreetype.so.6: sin ella el riel muere al cargar
829 "libpng-shared", # y freetype pide libpng16.so.16 — la cadena entera entro por querer letras
830 "alsa-lib", # pata-llimphi pide libasound.so.2: sin ella EL RIEL NO ARRANCA
831 "xkeyboard-config", # la Card exporta XKB_CONFIG_ROOT a /usr/share/X11/xkb, que estaba VACIO
832 "ncurses-shared", # soname de los de arriba y de la TUI
833 "bzip2-shared", # idem
834 "foot", # NINGUNA terminal en una maquina donde ya esta instalado claude
835 "shuma-tui", # el terminal de la suite: corre DENTRO de foot, no lo reemplaza
836 "shuma-daemon", # su cadena
837 "shuma-gateway", # su cadena
838 "shuma-pregunta", # su cadena
839]
840
841[perfil.escritorio-kde]
842descripcion = "escritorio KDE Plasma 6 desde fuente"
843cola = "incoming-kde"
844# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
845# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
846# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
847# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
848# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
849# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
850# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
851# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
852# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
853hereda = ["cli"]
854# Raíces del rootfs de metal (docs/HANDOFF-noche-kde-2026-07-14.md:135). El resto del escritorio
855# —137 artefactos hidratados— es CLAUSURA de estas 7, no lista a mano.
856paquetes = [
857 # ── `python3` (2026-09-22): la VÁLVULA de `[solo-build]`, usada con el dato delante ───────────
858 # `python3` está en `[solo-build]` y NO viaja en `base`/`cli`/`servidor`/`sway`/`cosmic`/`mirada`.
859 # Acá SÍ, y no por precaución: `scripts/vigia-interpretes.py` nombró lo que se rompía sin él, y
860 # no son scripts accesorios —el `kconf_update` de plasma-workspace, que MIGRA la config del calendario al actualizar (si no corre, la migración no pasa y nada lo dice). Un `#!` que no resuelve falla al EJECUTAR, o
861 # sea el día que el usuario abre la app, no al armar la imagen.
862 # Coste: 209 MiB sobre una imagen de 12,9 G ⇒ ~2%. En `base` eran 209 sobre 1.070: el 17%.
863 "python3",
864 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
865 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
866 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
867 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
868 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
869 # lecturas del modelo de composición.
870 "desktop-file-utils",
871 "plasma-workspace", "kwin", "kscreenlocker", "breeze", "frameworkintegration",
872 "qqc2-desktop-style", "plasma-integration",
873 # Huecos de RUNTIME confirmados por el triaje de la frontera (2026-07-22). Ninguno bloquea un
874 # build —todas las recetas que los piden ya están selladas— pero sin ellos el escritorio arranca
875 # roto: sin tipos de fichero, sin apps X11, sin diálogos de autorización y con los controles QML
876 # en un estilo genérico. Ver docs/state/frontera-triaje.toml para el porqué de cada uno.
877 "shared-mime-info", "xwayland", "polkit-qt-1", "qqc2-breeze-style",
878 # ⚠ Añadido el 2026-09-03 arrancando la imagen en QEMU. NO sale de la clausura de las 7 raíces
879 # porque no es dep de BUILD de nada: es dep de RUNTIME de plasmashell, que lo pide por D-Bus y
880 # **ABORTA** si no contesta —`kde.plasmashell: Aborting shell load: The activity manager daemon
881 # (kactivitymanagerd) is not running.`— dejando kwin vivo y la pantalla EN NEGRO. El síntoma miente:
882 # `STATUS: kwin=1 plasmashell=1` durante minutos, con los dos procesos vivos y nada pintado.
883 # `plasma-start-qemu.sh` ya lo lanzaba (línea 188); lo que faltaba era que el paquete ENTRARA.
884 # Misma figura que las raíces de runtime de GNOME (imports.gi.*): el cierre de BUILD no es el
885 # cierre de RUNTIME, y lo que se invoca por bus es invisible al grafo de deps.
886 "kactivitymanagerd",
887 # ── CAPA 2: EL ESCRITORIO USABLE ─────────────────────────────────────────────────────────────
888 # ⚠ Añadidas el 2026-09-03. **Las 22 ya estaban SELLADAS en esta cola y no las declaraba nadie**,
889 # así que no cuestan un build: sólo entran a la clausura. Se descubrió listando los `(cola,nombre)`
890 # cuyo artefacto instala un `usr/share/applications/*.desktop` y que `yupana.membresia()` no
891 # asigna a ningún perfil — salieron 23, y 22 son de acá.
892 #
893 # POR QUÉ FALTABAN, que no es lo mismo que «se decidió que no fueran». Las 7 raíces de arriba son
894 # el set MÍNIMO del arranque en metal, tomado de `docs/HANDOFF-noche-kde-2026-07-14.md:135`. Ese
895 # mismo documento nombra lo que faltaba como el trabajo siguiente: *«Capa 2 KF6 / apps núcleo:
896 # importar+construir konsole/dolphin/kate vía granja (extiende el escritorio usable)»*. Se
897 # construyeron y se sellaron; lo que nunca ocurrió fue DECLARARLAS. La métrica no lo podía ver
898 # porque mide lo declarado — la misma figura que dejó a sway en 121/121 sin emulador de terminal.
899 #
900 # Sin esto la imagen es compositor + shell + tema y nada más: **sin terminal no se puede salir de
901 # un fallo, y sin plasma-nm/plasma-pa/powerdevil no hay red, ni audio, ni gestión de energía.**
902 #
903 # Plomería de escritorio — esto no son "apps", es lo que hace que Plasma funcione:
904 "plasma-desktop", "plasma-nm", "plasma-pa", "powerdevil", "kscreen",
905 "systemsettings", "kinfocenter", "kmenuedit", "kwalletmanager",
906 # Núcleo usable: terminal, ficheros, editor, documentos, archivos comprimidos, captura, imágenes.
907 # `konsole` es el que cierra el agujero de «no hay terminal».
908 "konsole", "dolphin", "kate", "okular", "ark", "spectacle", "gwenview",
909 # Accesorios que ya estaban construidos; entran porque no cuesta nada y completan el menú.
910 "kcalc", "kfind", "filelight", "kcharselect", "kruler", "kdf",
911 # ── FUENTES ──────────────────────────────────────────────────────────────────────────────────
912 # Añadida el 2026-09-03 por el mismo recuento que destapó el hueco de GNOME: contando
913 # `.ttf/.otf/.ttc/.pfb` sobre los artefactos del cierre, este perfil tenía **UNA sola**, y de
914 # rebote — viene dentro de `qtbase`, no porque nadie la haya pedido. `fontconfig` estaba, pero
915 # fontconfig es el MOTOR que busca fuentes, no una fuente.
916 # Depender de la que trae qtbase es apoyarse en un detalle de empaquetado de Qt que puede
917 # desaparecer en cualquier versión, y encima deja a todo lo que NO es Qt sin nada.
918 "dejavu-fonts",
919 # ── LAS FUENTES DEL MUNDO (2026-10-19) ───────────────────────────────────────────────────────
920 # `dejavu-fonts` cubre latino, griego y cirílico, y era LO ÚNICO que tenía la distro: medido con
921 # `scripts/censo-distro.py`, chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés,
922 # hebreo y los emoji salían como cuadraditos — con el tablero entero en verde, porque ninguna
923 # otra métrica mide «¿alcanza para usarla?».
924 #
925 # Las tres hacen falta y ninguna sustituye a otra: son tres repositorios distintos de upstream.
926 # 144 MB en total por imagen, y es el precio de no dejar afuera a la mayor parte de la humanidad.
927 # noto-fonts 48 MB Regular+Bold hinted de TODAS las familias (321 ficheros)
928 # noto-fonts-cjk 86 MB 2 .ttc; cada uno trae zh-Hans, zh-Hant, ja y ko a la vez
929 # noto-fonts-emoji 10 MB NotoColorEmoji — fontconfig ya la prefiere de fábrica, sin `.conf`
930 "noto-fonts", "noto-fonts-cjk", "noto-fonts-emoji",
931 # ── ESCRIBIR, NO SÓLO LEER (2026-10-20) ──────────────────────────────────────────────────────
932 # Las fuentes Noto del 19-oct hicieron que la distro LEA chino, japonés, coreano, árabe e hindi.
933 # Esto es lo que falta para ESCRIBIRLOS: un teclado tiene cien teclas y esos sistemas no se
934 # teclean uno-a-uno. `censo-distro.py` marcaba la categoría «escribir en esos idiomas» 5 de 6.
935 #
936 # Entran los TRES JUNTOS y el orden del razonamiento importa:
937 # `fcitx5` el marco: daemon, protocolo y barra de candidatos. Habla
938 # `zwp_text_input_v3` / `zwp_input_method_v2`, que `mirada-compositor` registra
939 # (`setup.rs:600-602`) — sin eso ningún IME serviría en esta distro.
940 # `fcitx5-hangul` el MOTOR coreano. Sin un motor, el marco es una barra que no convierte nada:
941 # la figura de `crun` sin podman, y la razón por la que fcitx5 se construyó el
942 # 19-oct y NO se declaró hasta hoy.
943 # `libhangul` ⚠ y no es sólo dep de build. Su código va ESTÁTICO dentro del addon, pero el
944 # diccionario hanja NO: la ruta `/usr/share/libhangul/hanja/hanja.txt` está
945 # HORNEADA en la `.a` (medido con `strings`), así que sin este paquete en la
946 # imagen la conversión hanja busca un fichero que no existe. Es la figura de
947 # «un directorio vacío como sonda» por el otro lado: el código está, el dato no.
948 #
949 # ⚠ EL COREANO PRIMERO POR COSTE MEDIDO, no por preferencia: deps nuevas por motor —coreano 1
950 # (`libhangul`), japonés 2 (`anthy` + `fcitx5-qt`), chino 6 (`libime`, `libpinyin`, `opencc`,
951 # `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**). El chino además necesita que Qt salga de
952 # `incoming-kde` al corpus. Los otros dos son campañas con su propio presupuesto.
953 #
954 # ⚠ PESO: 294 MB (fcitx5 284 + libhangul 6,6 + el motor 3,3). Y **el 87 % de los binarios de
955 # fcitx5 son secciones `.debug_*`** —294 MB de 334, medido con readelf—, así que desnudado serían
956 # ~40 MB. No es una anomalía suya: `foot` y `mpv` también van sin desnudar, es la norma del corpus
957 # y su sitio es la campaña del split de debug (SDD 23), no esta línea.
958 # Y el JAPONÉS (2026-10-20), por el mismo camino y a la primera: `anthy-unicode` convierte kana a
959 # kanji y `fcitx5-anthy` lo enchufa al marco. ⚠ Costó UNA receta, no dos — en la nota de arriba
960 # escribí «japonés 2 (anthy + fcitx5-qt)» leyendo los makedepends de Alpine, y su CMakeLists no
961 # menciona Qt ni una vez. `anthy-unicode` viaja por su DICCIONARIO (4 ficheros), igual que
962 # `libhangul` por el hanja: sin los datos el motor carga y no convierte nada.
963 "fcitx5", "fcitx5-hangul", "libhangul", "fcitx5-anthy", "anthy-unicode",
964 # Y el CHINO (2026-10-20), que cierra los tres grandes. ⚠ **La estimación de la nota de arriba era
965 # FALSA, y es la TERCERA vez con el mismo defecto**: decía «chino 6 (`libime`, `libpinyin`,
966 # `opencc`, `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**)» y que haría falta sacar Qt de
967 # `incoming-kde` al corpus. Medido sobre los `CMakeLists` y no sobre los `makedepends` de Alpine:
968 # **cero Qt, cero libpinyin, cero fcitx5-lua, cero qtwebengine.** Qt sólo aparecía detrás de
969 # `ENABLE_GUI`, que es la ventana de configuración y se apaga; el navegador embebido, detrás de
970 # `ENABLE_BROWSER`. Mismo error que ya está anotado dos párrafos arriba para el japonés.
971 #
972 # ⚠ **`libime` NO se declara acá, y eso es medido, no un olvido.** Se construye con
973 # `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias de libc++), así que entra ENTERA
974 # dentro de los addons: comprobado con `readelf -d` que **ningún** `.so` de
975 # `fcitx5-chinese-addons` declara `libIME*`. Declararla metería 159 MB en cada imagen para nada.
976 # Lo que sí viaja son sus DATOS, que el addon busca en ejecución.
977 #
978 # `fcitx5-chinese-addons` pinyin + tabla + helper de trazos + chttrans + puntuación +
979 # ancho completo. 6 addons
980 # `opencc` simplificado ↔ tradicional. Viaja porque `libchttrans.so` enlaza
981 # `libopencc.so` de verdad (lo exige el guardián): sin él la mitad del
982 # chino escrito queda afuera, y no con un error sino en silencio
983 # `libime-lm` el modelo de lenguaje (`zh_CN.lm`, 35 MB). Sin esto el pinyin carga
984 # y **no ordena candidatos**
985 # `libime-dict` el diccionario de pinyin + la Extensión B de Unicode
986 # `libime-table` las 8 tablas (wubi, cangjie, erbi…): quien escribe por FORMA y no
987 # por pronunciación. Sin ellas el método por tabla existe y no carga nada
988 # `fcitx5-pinyin-stroke` buscar un carácter por sus TRAZOS — el caso de quien no sabe cómo se
989 # `fcitx5-pinyin-table` pronuncia, que es justo cuando un método por pinyin no sirve
990 #
991 # ⚠ PESO: 189 MB los siete. El gordo es el addon (105) y el modelo (35), y **el 90 % de libime es
992 # `.debug_*`** (143 de 159 MB, sumado con readelf) — la norma del corpus, cuyo sitio es el SDD 23.
993 #
994 # ⚠ QUEDA AFUERA Y SE DICE: `ENABLE_GUI=Off` ⇒ **no hay ventana de configuración** (es Qt y no
995 # tenemos `fcitx5-qt`), así que elegir diccionarios o disposición se hace editando ficheros. Y
996 # `ENABLE_CLOUDPINYIN=Off` es DECISIÓN y no falta de dep: manda lo que teclea el usuario a un
997 # servidor remoto.
998 "fcitx5-chinese-addons", "opencc", "libime-lm", "libime-dict", "libime-table",
999 "fcitx5-pinyin-stroke", "fcitx5-pinyin-table",
1000 # ── LA MÁQUINA HABLA (2026-10-20) ────────────────────────────────────────────────────────────
1001 # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**, y es el hueco que deja gente
1002 # afuera de verdad: sin voz, una distro no es incómoda para quien no ve — es INUSABLE. Y ninguna
1003 # otra métrica del repo lo ve: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la
1004 # máquina es muda.
1005 #
1006 # `espeak-ng` son **21 MB con 114 diccionarios de idioma**, por síntesis formante: sin modelos, sin
1007 # red y sin GPU. Instala cuatro nombres (`espeak-ng`, `espeak`, `speak-ng`, `speak`) porque los
1008 # scripts viejos invocan los dos primeros.
1009 #
1010 # Los tres van juntos y el orden es el de la cadena:
1011 # `espeak-ng` el sintetizador y su `libespeak-ng.so` (lo que `orca` cargaría con dlopen)
1012 # `pcaudiolib` la salida de audio. ⚠ Sin ella espeak-ng **compila igual** y sintetiza a `.wav`
1013 # sin reproducir: `espeak-ng "hola"` no haría ruido. Verificado en el artefacto —
1014 # la `.so` trae libpulse/libasound en NEEDED y exporta los `audio_object_*`
1015 # `sonic` cambia la velocidad sin cambiar el tono. ⚠ No es opcional aunque su
1016 # `find_library` no lleve REQUIRED: si no la encuentra, espeak-ng la CLONA de git
1017 # en tiempo de build, y el sandbox no tiene red
1018 #
1019 # ⚠ Lo que esto NO cierra: **`orca`**, el lector de pantalla, necesita `at-spi2-core` y
1020 # `py3-gobject`, los dos sólo en `incoming-gnome` — la misma pila de introspección que bloqueó a
1021 # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
1022 # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
1023 "espeak-ng", "pcaudiolib", "sonic",
1024 # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
1025 # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
1026 # distinto que las otras no tapan:
1027 #
1028 # `speech-dispatcher` el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
1029 # `dotconf` sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
1030 # Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
1031 # Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
1032 # detrás como módulos. `dotconf` es su parser de configuración, la única
1033 # dep que faltaba
1034 # `brltty` los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
1035 # de gusto a la voz: braille es lo que permite leer código, ortografía y
1036 # matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
1037 # además no oye. Con voz y sin braille se deja afuera a sordociegos por
1038 # completo
1039 # `liblouis` braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
1040 # carácter: legible, tres veces más largo, y nadie lee así un texto
1041 #
1042 # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
1043 #
1044 # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
1045 # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
1046 # bloqueada por DOS cosas distintas:
1047 # · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
1048 # dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
1049 # rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
1050 # Es una decisión de operador, no algo que se hace de paso
1051 # · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
1052 # en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
1053 #
1054 # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
1055 # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
1056 # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
1057 # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
1058 # pendiente y no un olvido.
1059 "speech-dispatcher", "dotconf", "brltty", "liblouis",
1060 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
1061 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
1062 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
1063 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
1064 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
1065 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
1066 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
1067 "dejavu-fonts-nerd",
1068 # ⚠ `xwayland` sigue acá como RAÍZ pero su receta NO se va a escribir: lo provee una imagen ajena
1069 # enjaulada, declarado en ../state/qorpa-ajenos.toml (ADR 0015 D5 capa 3, qorpa). El Xwayland vive
1070 # DENTRO de la imagen —Arch y Fedora ya lo traen y se cuelga de kwin por el socket— así que el
1071 # hueco se disuelve sin receta y sin tocar la postura Wayland-only. Queda como raíz A PROPÓSITO:
1072 # la imagen lo NECESITA, y sacarlo de acá lo volvería invisible en vez de contado aparte. El grafo
1073 # ya lo reporta con clase `ajeno` («+ 1 ajenas»), nunca sumado a las nativas. Los otros tres ya
1074 # están sellados (2026-09-02/03).
1075 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
1076 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
1077 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
1078 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
1079 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
1080 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
1081 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
1082 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
1083 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
1084 "mpv",
1085 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
1086 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
1087 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
1088 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
1089 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
1090 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
1091 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
1092 "atuq",
1093 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
1094 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
1095 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
1096 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
1097 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
1098 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
1099 # así que la imagen necesita además a alguien escuchando ese nombre.
1100 "libnotify",
1101 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
1102 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
1103 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
1104 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
1105 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
1106 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
1107 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
1108 "llama-cpp",
1109 "ia-modelo-chat",
1110 # ── LA BÓVEDA DEL NAVEGADOR (SDD 26 §7.sexies, decisión 1 — 2026-09-21) ──────────────────────
1111 # `atuq` trae la décima extensión desde el 2026-09-15 y el host pineado atiende `vault.*` desde
1112 # el 2026-09-16; aun así la función venía **APAGADA DE FÁBRICA en las cuatro imágenes**, sin un
1113 # solo error: `vault.status` contesta `{"ok":true,"locked":true}` porque el host NUNCA abre la
1114 # base (sled toma lock exclusivo y el proceso que lanza Gecko muere y revive con cada pestaña);
1115 # le habla a un DUEÑO por un socket, y el dueño no estaba declarado en ningún perfil. Las dos
1116 # recetas estaban selladas desde el 2026-09-18 con `perfiles: []` — sellado ≠ instalado, que es
1117 # la lección de `foot`, escrita QUINCE veces en este fichero antes de hoy (`grep 'lección de
1118 # .foot.'`) y aun así vuelta a pasar. Y «cerrada» es una respuesta EXITOSA:
1119 # ni un log, ni un reintento, ni la insignia la distinguen de un usuario que no desbloqueó la suya.
1120 # `boveda` la app DUEÑA de la base: la abre para su ventana y levanta el socket del
1121 # navegador en un hilo. Sin ella, `vault.match` no ofrece nada, nunca.
1122 # `shuma-pregunta` el diálogo de consentimiento que `PorDialogo` lanza por PATH. Sin él,
1123 # `Command::new` falla ⇒ TODO `vault.fill` se deniega, indistinguible de que
1124 # la persona haya dicho que no.
1125 # **Las dos o ninguna**: media bóveda es una que niega todo en silencio. Cuestan ~43 M por imagen
1126 # (22 M + 21 M medidos sobre los artefactos sellados), contra los ~1,25 GiB del §6.7 de acá arriba.
1127 # ⚠ La respuesta fue NO hasta el 2026-09-18, y no por el tamaño: ninguna app llimphi de escritorio
1128 # podía abrir ventana sin Vulkan (§7.septies), así que declararlas habría sido instalar una bóveda
1129 # que niega todo. Eso se arregló en llimphi y se MIDIÓ en metal: las seis etapas de
1130 # `scripts/test-atuq-boveda-metal.py` en verde —con el navegador de verdad, el diálogo a la vista
1131 # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes.
1132 "boveda",
1133 "shuma-pregunta",
1134 # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21)
1135 # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se
1136 # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que
1137 # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo
1138 # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de
1139 # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además
1140 # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las
1141 # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo:
1142 # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y
1143 # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual
1144 # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa.
1145 # `agora-cli` el sembrador: `identity new --name <nombre>` crea la identidad y `unlock` la
1146 # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado
1147 # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene
1148 # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá.
1149 # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va
1150 # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la
1151 # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta
1152 # «autenticación fallida» y NO re-siembra.
1153 # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide
1154 # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA,
1155 # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque
1156 # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como
1157 # «la que siembra el login» (comentario de `pacha-secretos`).
1158 # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN
1159 # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow`
1160 # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no
1161 # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos
1162 # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le
1163 # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos
1164 # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de
1165 # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker.
1166 "agora-cli",
1167 # ── CAPTURA Y STREAMING ──────────────────────────────────────────────────────────────────────
1168 # `obs-studio` (2026-09-04). A diferencia de mpv, ésta vive en ESTA COLA y no en el corpus, y no
1169 # es preferencia: su frontend es Qt6 y las 13 recetas Qt viven sólo en `incoming-kde`. Un qtbase
1170 # de corpus tendría otro hash con el MISMO nombre de artefacto ⇒ dos Qt peleando por
1171 # `/usr/lib/libQt6Core.so`. ⇒ **por ahora OBS es de KDE y de nadie más**; el día que exista un Qt
1172 # de corpus (`yupana radio qtbase` = 119, campaña propia) puede subir y listarse en las cuatro.
1173 #
1174 # Su renderer va PORTADO a EGL (`obs-glad-egl-loader.patch`): el glad de upstream hacía
1175 # `dlopen("libGL.so.1")` + `dlsym("glXGetProcAddressARB")`, o sea GLX en runtime, que esta distro
1176 # no tiene ni quiere.
1177 #
1178 # ⚠ Entra con tres huecos escritos en su receta: sin webcam (`ENABLE_V4L2=OFF`, `libv4l2` no lo
1179 # publica ninguna cola), sin salida MPEG-TS (SRT/RIST ausentes) y sin scripting (falta `swig`).
1180 # Grabar, capturar la pantalla y publicar por RTMP —el uso real— no pasan por ninguno de los tres.
1181 "obs-studio",
1182 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
1183 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
1184 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
1185 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
1186 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
1187 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
1188 "swayimg",
1189 # ── Y SE PUEDE SUBIR EL VOLUMEN (2026-10-20) ────────────────────────────────────────────────
1190 # El censo marcaba «audio de escritorio» 5 de 5 ausente, con el problema exacto: «PipeWire suena y
1191 # no hay con qué subirle el volumen desde una ventana». O sea que la distro reproducía audio y
1192 # **no tenía ningún control de volumen** — ni gráfico, ni de consola, ni un atajo de teclado
1193 # posible, porque un atajo llama a un comando y el comando no existía.
1194 #
1195 # `pamixer` sube, baja, silencia y lista fuentes y sumideros: es lo que un compositor engancha a
1196 # las teclas de volumen. Su `cxxopts` NO se declara — es sólo una cabecera y no deja nada que viaje.
1197 #
1198 # ⚠ Habla el protocolo de PulseAudio y eso NO es un desajuste: `pipewire` publica esa capa
1199 # (`pipewire-pulse` + `libpipewire-module-protocol-pulse.so`, verificado en el artefacto) y la
1200 # biblioteca cliente `libpulse.so.0` la publica `pulseaudio`. Las dos ya viajan en estos cinco
1201 # perfiles, igual que `libsndfile`, así que esto cuesta sólo sus 7,5 MB.
1202 #
1203 # ⚠ Su cadena de runtime baja TRES niveles y es lo que hizo fallar el primer build:
1204 # `pamixer → libpulse.so → libpulsecommon.so → libsndfile.so.1`. Está documentada igual en
1205 # `recipes/espeak-ng.toml` — el sandbox sólo monta lo declarado, y yo no la consulté.
1206 "pamixer",
1207 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
1208 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
1209 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
1210 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
1211 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
1212 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
1213 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
1214 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
1215 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
1216 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
1217 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
1218 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
1219 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
1220 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
1221 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
1222 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
1223 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
1224 # partes ⇒ un solo artefacto, cero rebuilds).
1225 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
1226 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
1227 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
1228 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
1229 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
1230 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
1231 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
1232 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
1233 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
1234 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
1235 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
1236 # ── TEMA DE ICONOS ───────────────────────────────────────────────────────────────────────────
1237 # Añadido el 2026-09-03 releyendo la captura que dio el arranque por bueno
1238 # (`docs/evidencia/plasma6-qemu-2026-09-03-kactivitymanagerd.png`): el panel salía con el reloj y
1239 # NADA MÁS, y se leyó como «el panel arranca». No era el panel — era que ninguno de sus applets
1240 # tenía icono que dibujar. `kickoff`, `systemtray`, `showdesktop` y `trash` SON un icono y poco
1241 # más, así que sin tema quedan de ancho cero; el reloj se ve porque dibuja TEXTO. Los `.so` de los
1242 # applets estaban todos instalados (`/usr/lib/qt6/plugins/plasma/applets/`), o sea que el síntoma
1243 # no distingue «no cargó» de «cargó y no tiene qué pintar».
1244 # El log lo decía en UNA línea que nadie leyó: `kf.iconthemes: Icon theme "breeze" not found.`
1245 #
1246 # `breeze`, que ya estaba de raíz, es el tema de WIDGETS y de CURSORES: instala
1247 # `usr/share/icons/Breeze_Light` y `usr/share/icons/breeze_cursors`, que son punteros de ratón, no
1248 # iconos de aplicación. Los ~11.000 iconos viven en `breeze-icons`, receta APARTE que estaba
1249 # sellada desde siempre y que no declaraba NINGÚN perfil. Misma figura que las fuentes y que
1250 # `foot`: es data de RUNTIME, no la alcanza ninguna arista de BUILD, y la clausura no la puede
1251 # echar de menos porque mide lo declarado.
1252 #
1253 # `hicolor-icon-theme` es el FALLBACK obligatorio del estándar freedesktop: cada aplicación
1254 # instala sus iconos en `usr/share/icons/hicolor/...` y todo buscador termina ahí cuando el tema
1255 # activo no tiene lo pedido. Sin su `index.theme` el directorio existe pero NO es un tema y la
1256 # búsqueda no lo recorre. Vive en el corpus desde hoy (promovida con hash IDÉNTICO
1257 # b3:98ad44a5 desde `incoming-gnome`/`incoming-cosmic`, 0 rebuilds en sus 4 consumidores) para que
1258 # la alcancen las cuatro imágenes.
1259 "breeze-icons", "hicolor-icon-theme",
1260 # ── ENERGÍA: sin esto la imagen NO TIENE BATERÍA (añadido 2026-09-09) ─────────────────────────
1261 # ⚠ El perfil declaraba `powerdevil` —el gestor de energía de Plasma— pero NO a upower, que es
1262 # con quien powerdevil habla para leer la batería y aplicar perfiles de energía. Medido: `upower`
1263 # no estaba ni en esta cola ni en el corpus, sólo en la de GNOME, y las colas no se ven entre sí.
1264 # En un portátil eso es un escritorio sin indicador de batería, y el arranque no falla ⇒ no se ve
1265 # hasta usarlo. Es el mismo tipo de hueco que las fuentes de GNOME o el «abrir con» vacío.
1266 # La receta es una VARIANTE con `-Dintrospection=disabled`: GNOME la necesita encendida porque su
1267 # shell hace `imports.gi.UPowerGlib`, pero powerdevil habla D-Bus con `org.freedesktop.UPower` y
1268 # no toca el typelib. Apagarla evita copiar cuatro recetas más de introspección a esta cola.
1269 "upower",
1270 # ── PORTALES: sin esto NO SE COMPARTE PANTALLA (añadido 2026-09-09) ───────────────────────────
1271 # ⚠ Segundo hueco que dejaba a KDE por debajo de una instalación estándar. Medido recorriendo los
1272 # `.service` de D-Bus de TODO el store: los únicos backends de portal sellados eran los de COSMIC
1273 # y GNOME. Ninguno de KDE — y esta cola no tenía ni el frontend. Consecuencia concreta: la imagen
1274 # trae `obs-studio` con su plugin linux-pipewire, que captura pantalla POR EL PORTAL, así que en
1275 # Plasma no tenía con quién hablar mientras en los otros dos ese frente estaba cerrado.
1276 # Van los DOS porque la cadena es de tres: cliente → org.freedesktop.portal.Desktop [frontend] →
1277 # org.freedesktop.impl.portal.* [backend]. El frontend comparte artefacto con el de COSMIC (hash
1278 # idéntico ⇒ un solo directorio en el store, cero builds).
1279 # ⚠ El backend va SIN el portal de impresión, y no es un recorte por comodidad: no hay ninguna
1280 # receta de cups en el catálogo ⇒ qtbase se construyó sin CUPS ⇒ no existe `qcups_p.h` y
1281 # `src/print.cpp` no compila. Un portal de impresión sin con qué imprimir es código muerto. El
1282 # backend tampoco lo ANUNCIA ya en su `kde.portal` (verificado: 0 apariciones de Print), que es lo
1283 # que evita que el frontend enrute a un portal que no contesta.
1284 "xdg-desktop-portal", "xdg-desktop-portal-kde",
1285 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
1286 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
1287 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
1288 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
1289 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
1290 # una sola copia.
1291 #
1292 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
1293 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
1294 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
1295 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
1296 # recetas: son tres perfiles sin «enable» ninguno.
1297 #
1298 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
1299 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
1300 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
1301 "cups", "bluez",
1302 # `wireplumber` (2026-09-13): **el audio de esta imagen no encaminaba nada.** `pipewire` está y
1303 # arranca, pero se construyó con `-Dsession-managers=[]`: sin gestor de sesión no hay política de
1304 # qué es entrada, qué es salida ni qué se enlaza con qué — el sink que ve la aplicación es
1305 # `auto_null`, o sea el nodo de descarte. Audio que parece vivo y no suena.
1306 # Vive en el CORPUS —y no en esta cola— porque desde acá no se alcanza una cola hermana: las
1307 # copias de `incoming-gnome` e `incoming-cosmic` eran invisibles para este perfil. Ver su receta:
1308 # hay tres variantes vivas a propósito y consolidarlas es el trabajo siguiente.
1309 "wireplumber",
1310]
1311
1312# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
1313# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
1314# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
1315# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
1316# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
1317#
1318# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
1319# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
1320# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
1321# `perfiles` de los cinco `build-state*.json`, no a ojo.
1322#
1323# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
1324# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
1325#
1326# ⚠ DOS AUSENCIAS QUE ESTE CRUCE DESTAPÓ Y QUE NO SE ARREGLAN ACÁ:
1327# · **no hay `wireplumber`** en la clausura. `pipewire` sin gestor de sesión no encamina nada:
1328# los nodos existen y nadie los conecta. GNOME y COSMIC sí lo tienen; KDE y sway, no.
1329# · **no hay `logind-compat`**. KWin pide una sesión de logind para tomar el seat; acá la cubre
1330# `seatd`/el propio `plasma-workspace`, pero conviene saber que el servicio no está.
1331# Las dos son deuda del perfil, no de esta lista. Se escriben para que se vean.
1332servicios = [
1333 "dbus-system", "upowerd", "cupsd", "bluetoothd",
1334 "pipewire", "pipewire-pulse",
1335 # 2026-09-13: con `wireplumber` declarado arriba, el audio pasa de «acepta clientes» a «encamina».
1336 "wireplumber",
1337 # `NetworkManager` llega por la clausura de `plasma-nm` y hasta hoy NADIE lo arrancaba: el applet
1338 # de red de Plasma se dibujaba sobre un demonio apagado. Con la cola retirada es el del corpus,
1339 # o sea el que sí maneja WiFi.
1340 "NetworkManager",
1341]
1342
1343[perfil.escritorio-gnome]
1344descripcion = "escritorio GNOME (Wayland-nativo) desde fuente — 3er escritorio, X11 al tacho"
1345cola = "incoming-gnome"
1346# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
1347# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
1348# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
1349# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
1350# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
1351# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
1352# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
1353# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
1354# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
1355hereda = ["cli"]
1356# Raíces de una sesión GNOME que bootea a un shell usable. El resto es CLAUSURA. Hoy son HUECOS
1357# (cero recetas en incoming-gnome): build-state SALTA este perfil hasta que su cola se cargue
1358# (--gnome), así que declararlo NO reporta raíces fantasma — sólo lo hace un objetivo de primera
1359# clase que el sistema conoce. La frontera de autoría se descubre con `seed-gnome.py` (bootstrap por
1360# metadata mientras no haya recetas); OJO: ese cierre SOBREESTIMA ~5-10× (ver docs/state/seed-gnome.json
1361# y la memoria frente-gnome). El palo largo / keystone es spidermonkey(mozjs) → gjs → gnome-shell.
1362#
1363# ── SIN gnome-session / gnome-settings-daemon / gdm: APARCADAS POR DISEÑO (2026-08-07) ───────────
1364# Estaban acá y hacían que el perfil reportara 124/127 con 3 en `never` PARA SIEMPRE, que se lee
1365# como trabajo pendiente. No lo es: las tres mueren en GTK3, y GTK3 es una de las tres deudas que
1366# el frente GNOME aparcó a propósito (GTK3 / X11 / PAM). El diagnóstico completo, verificado contra
1367# el `meson.build` de cada tag, está en el encabezado de cada receta — que sigue en el repo, con su
1368# análisis intacto, por si algún día se autora el stack GTK3.
1369#
1370# NO son tres problemas, es UNO. Y no bloquean el escritorio: gnome-shell NO depende de
1371# gnome-session ni de g-s-d, ni en build ni para arrancar. El camino vivo es mutter → gnome-shell,
1372# lanzado por arje. El único que pedía gnome-session era gdm.
1373#
1374# ⚠ La contradicción costó: `targets.toml` las reclamaba como raíces mientras las recetas decían
1375# «aparcadas», así que el grafo reportaba deuda fantasma y el worker las reintentaba en cada ciclo.
1376# Dos documentos del repo diciendo cosas opuestas, y el que se mira primero es el grafo.
1377# Si algún día se autora GTK3, se vuelven a añadir acá y el objetivo reaparece solo.
1378paquetes = [
1379 # ── `python3` (2026-09-22): la VÁLVULA de `[solo-build]`, usada con el dato delante ───────────
1380 # `python3` está en `[solo-build]` y NO viaja en `base`/`cli`/`servidor`/`sway`/`cosmic`/`mirada`.
1381 # Acá SÍ, y no por precaución: `scripts/vigia-interpretes.py` nombró lo que se rompía sin él, y
1382 # no son scripts accesorios —`gnome-tweaks`, que es una app entera, y el `gdctl` de mutter (configurar las salidas). Un `#!` que no resuelve falla al EJECUTAR, o
1383 # sea el día que el usuario abre la app, no al armar la imagen.
1384 # Coste: 209 MiB sobre una imagen de 10,8 G ⇒ ~2%. En `base` eran 209 sobre 1.070: el 17%.
1385 "python3",
1386 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
1387 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
1388 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
1389 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
1390 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
1391 # lecturas del modelo de composición.
1392 "desktop-file-utils",
1393 "mutter", "gnome-shell", "gjs",
1394 "gsettings-desktop-schemas", "gnome-desktop", "xdg-desktop-portal-gnome",
1395 # ── `shared-mime-info`: SELLADA DESDE SIEMPRE Y EN NINGUNA IMAGEN DE GNOME (2026-09-21) ───────
1396 # Vive en el CORPUS, está sellada, y hasta hoy la declaraba **sólo `escritorio-kde`**
1397 # (`perfiles: ['escritorio-kde']` en los cinco grafos). O sea que la imagen de GNOME se hidrataba
1398 # SIN LA BASE DE DATOS DE TIPOS MIME. Cuesta 0 de build —ya está en el store— y es la mitad que
1399 # falta del par que este fichero explica seis renglones más abajo: `desktop-file-utils` dice CON
1400 # QUÉ se abre un fichero, `shared-mime-info` dice QUÉ ES. Sin ella, `content_type_guess` cae al
1401 # nombre, todo es `application/octet-stream`, y el «abrir con» sale vacío aunque la caché de
1402 # desktop esté construida. La lección de `foot` otra vez, con el agravante de que acá la receta ni
1403 # siquiera hacía falta: era una línea.
1404 "shared-mime-info",
1405 # ── LA SESIÓN: LAS TRES BARATAS, MEDIDAS CONTRA `seed-gnome.json` ─────────────────────────────
1406 # El coste se midió invirtiendo el mapa `demanda` del seed (qué deps de FRONTERA arrastra cada
1407 # raíz, descontando lo ya sellado), no a ojo:
1408 # gnome-text-editor 2 (editorconfig-core-c, libspelling) ← el `kate` de este escritorio
1409 # gnome-console 2 (libgtop, vte) ← `foot` ya está, pero no integra
1410 # gnome-keyring 2 (libcap-ng, libselinux) ← el `kwalletmanager` de acá
1411 # ⚠ `gnome-keyring` NO es un lujo: `libsecret` y `evolution-data-server` YA están sellados en esta
1412 # cola, y libsecret sin un demonio que sirva `org.freedesktop.secrets` es exactamente el cuadro
1413 # de la bóveda del §7.sexies — una API que contesta «vacío» con éxito, indistinguible de un
1414 # usuario sin credenciales guardadas. Media cadena de secretos es la que niega todo en silencio.
1415 # ⚠ `libselinux` en la demanda de gnome-keyring es casi seguro nix-ismo (no hay SELinux en esta
1416 # distro, musl y sin systemd): el seed AVISA que sobreestima 5-10×. Se autora y se ve.
1417 # ✅ LAS TRES SELLADAS EL 2026-09-23, y el «2 de frontera cada una» de arriba era optimista: la
1418 # cuenta real fue 7, 4 y 2. Lo que el seed no puede ver son las fronteras DE las fronteras.
1419 # gnome-text-editor b3:d363693e → libspelling + editorconfig-core-c + **gtksourceview (SOMBRA
1420 # dinámica: la del corpus es estática y del mundo estático, y meterla en una .so de la isla
1421 # es la enfermedad de las dos glib)** + la cadena del corrector: enchant + hunspell +
1422 # hunspell-en. Los tres últimos porque libspelling construye igual sin corrector —expone un
1423 # proveedor VACÍO— y el editor seguiría enseñando su interruptor de ortografía sin efecto.
1424 # gnome-console b3:dec8319c → vte + libgtop, más `lz4-shared` (el liblz4.a canónico no es PIC)
1425 # y `fast-float` (vte cae a un subproyecto GIT si no encuentra su cabecera, y el sandbox no
1426 # tiene red).
1427 # gnome-keyring b3:5a93bd6b → gcr3 (la 48 sigue pidiendo `gck-1`/`gcr-base-3`, y el gcr de esta
1428 # cola publica `gck-2`/`gcr-4`: dos APIs, no dos versiones) + libcap-ng.
1429 # ⚠ Y `libselinux` ERA un nix-ismo, exactamente como este fichero sospechaba doce líneas más
1430 # arriba: `-Dselinux=disabled` y no hizo falta.
1431 #
1432 # ⚠ LO QUE NO ESTÁ, con nombre:
1433 # · ~~Diccionarios sólo en inglés~~ **CERRADO el 2026-09-23**: `hunspell-es` (b3:7ebfe406) trae
1434 # las 23 variantes de RLA-ES —es_ES, es_MX, es_AR, es_PE, es_BO…— por 20 MB. Van todas porque
1435 # elegir una es elegir el país del usuario; enchant escoge por locale. Receta aparte y no una
1436 # línea en `hunspell-en`, que habría sido un nombre que miente; el tarball es el mismo y el
1437 # fetcher lo cachea, así que el idioma 24 no descarga nada.
1438 # ⇒ Y los diccionarios son dep de **`runtime`** del editor, no de `build`: no los pide ningún
1439 # `.pc` ni ningún `NEEDED`, los abre enchant en disco. Así no entran en `hash_inputs` y añadir
1440 # un idioma no reconstruye la app.
1441 # · **El manual del editor, sólo en inglés**: itstool falla con status 1 y sin mensaje en las 21
1442 # traducciones (sospecha no verificada: el `.gmo` del msgfmt de gettext-tiny).
1443 # · **`yelp` no está en el catálogo** ⇒ el menú «Ayuda» de estas apps no tiene visor. Las
1444 # páginas se instalan igual, para el día que entre.
1445 # · **gnome-keyring NO se declara como `[[service]]`**: se activa por D-Bus
1446 # (`org.freedesktop.secrets`), que es como libsecret lo despierta. Si algún día se quiere el
1447 # llavero vivo ANTES de que alguien lo pida, es una Card de sesión y su propia decisión.
1448 "gnome-text-editor", "gnome-console", "gnome-keyring",
1449 # ── EL GESTOR DE FICHEROS, Y SU MITAD QUE NADIE VE ────────────────────────────────────────────
1450 # `nautilus` (15 de frontera) es el `dolphin` de este escritorio: un escritorio sin gestor de
1451 # ficheros no es un escritorio. Va PEGADO a `gvfs` (11) y por la misma razón que el plugin de
1452 # zathura va pegado al visor: sin gvfs, nautilus abre y navega, pero **no hay papelera, ni montar
1453 # un USB, ni red** — funciones que la interfaz SIGUE DIBUJANDO y que fallan al pulsarlas. Un
1454 # gestor de ficheros que enseña una papelera que no existe miente más que no traerlo.
1455 # ✅ LAS DOS SELLADAS EL 2026-09-23 — y el «~26 recetas de frontera» que decía acá eran SEIS.
1456 # Lo de arriba salía de `seed-gnome.json`, que avisa en su cabecera que sobreestima 5-10×; leídos
1457 # los `meson.build` de verdad, la cuenta es otra:
1458 # gvfs → CERO recetas nuevas. Sus 11 «fronteras» son backends OPCIONALES con perilla propia
1459 # (samba, libmtp, libnfs, avahi, libcdio, libgphoto2, libimobiledevice, libbluray,
1460 # goa/google/onedrive) y `fuse` es `fuse3`, que ya estaba en el corpus.
1461 # nautilus → cuatro deps duras ⇒ TRES recetas: `libportal` publica `libportal.pc` Y
1462 # `libportal-gtk4.pc` desde un solo source, y `tinysparql` publica
1463 # `tracker-sparql-3.0.pc` además del suyo. Más `gnome-autoar`.
1464 # la sexta → `libarchive-shared`: la canónica no es PIC (3205 reubicaciones R_X86_64_32 fuera
1465 # de .debug) y no entra en la `.so` de gnome-autoar.
1466 # Artefactos: nautilus b3:5183572a · gvfs b3:eb831c9e (13 mounts, con `trash` y `gvfsd-fuse`).
1467 #
1468 # ⚠ LO QUE SIGUE SIN HABER, y hay que decirlo porque esta lista ahora se lee como completa:
1469 # · ~~`udisks` ⇒ enchufar un USB no hace nada~~ **CERRADO el 2026-09-23.** `udisks` b3:e91e70b3
1470 # + `libblockdev` + `kmod`, y gvfs re-sellado con `-Dudisks2=true` ⇒ ya trae el
1471 # `gvfs-udisks2-volume-monitor`. Costó 7 recetas, 4 de ellas variantes `-shared` del cierre de
1472 # bloque (libblkid, libmount, libfdisk, e2fsprogs): libblockdev carga sus plugins con `dlopen`
1473 # y ninguna estática del corpus es PIC. `libparted` NO hizo falta — el plugin `part` usa
1474 # libfdisk, y libparted sólo entra con `--with-tools`.
1475 # ⚠ LO QUE NO HACE: **LUKS** (abrir un disco cifrado), RAID software y las funciones propias
1476 # de NVMe. udisksd arranca igual y lo dice en el log —usa `bd_try_init`—, y esas operaciones
1477 # fallan al usarlas. Cerrarlo es la cadena cryptsetup+lvm2+libnvme+libbytesize, que es su
1478 # propia unidad de trabajo.
1479 # ⚠ Y HOY SÓLO LO VE GNOME: la receta vive en `incoming-gnome/` porque `polkit` no existe en
1480 # el corpus y una receta del corpus no alcanza a una hermana de cola. KDE, COSMIC y sway no
1481 # pueden declararlo hasta que se promueva. Detalle en `recipes/incoming-gnome/udisks.toml`.
1482 # · **`localsearch`** (ex tracker-miners): sin el indexador, la búsqueda de nautilus cae en
1483 # `NautilusSearchEngineSimple` — por nombre, recorriendo directorios, sin mirar el contenido.
1484 # · **`-Dextensions=false`**: sin `gexiv2` ni gstreamer no hay pestañas de metadatos EXIF ni de
1485 # audio/vídeo en «Propiedades». El resto del diálogo es del núcleo y está.
1486 # Los tres están escritos también en el encabezado de su receta, que es donde los va a leer quien
1487 # toque esto. Ninguno queda como `wanted` en el grafo — son perillas apagadas, no huecos del
1488 # grafo — así que esta lista es el ÚNICO sitio donde se ven juntos.
1489 # `udisks` (2026-09-23): el demonio de almacenamiento. Va de RAÍZ y no por clausura aunque gvfs
1490 # lo enlace, por la lección de `foot`: lo que un perfil no declara puede desaparecer el día que su
1491 # consumidor cambie una bandera, y esto es el «enchufo un USB y aparece» de la imagen.
1492 "nautilus", "gvfs", "udisks",
1493 # ── ⚠ NO ENTRA `gnome-control-center`, Y NO ES UN OLVIDO (2026-09-21) ─────────────────────────
1494 # Es el `systemsettings` que le falta a este perfil y el hueco se NOTA — pero su cierre arrastra
1495 # **28 de frontera y entre ellas `gnome-settings-daemon`**, que está APARCADA POR DISEÑO doce
1496 # renglones más arriba de esta lista porque muere en GTK3. Declararla acá la dejaría en `never`
1497 # PARA SIEMPRE y el worker la reintentaría en cada ciclo: es la contradicción exacta que ya costó
1498 # una vez —«targets.toml las reclamaba como raíces mientras las recetas decían aparcadas»— y que
1499 # el propio encabezado de este perfil documenta haber pagado. No se repite el mismo error el
1500 # mismo fichero. Entra el día que se autore GTK3, o el día que se compruebe que g-c-c moderno
1501 # construye sin g-s-d (es GTK4 desde hace varias versiones; la demanda de `gtk+3` del seed es
1502 # sospechosa de sobreestimar, como lo fue para gjs/gnome-desktop/gcr, que están sellados sin él).
1503 # Escribirlo acá es barato; descubrirlo dentro de tres semanas por un grafo en rojo, no.
1504 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
1505 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
1506 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
1507 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
1508 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
1509 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
1510 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
1511 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
1512 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
1513 "mpv",
1514 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
1515 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
1516 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
1517 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
1518 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
1519 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
1520 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
1521 "atuq",
1522 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
1523 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
1524 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
1525 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
1526 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
1527 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
1528 # así que la imagen necesita además a alguien escuchando ese nombre.
1529 "libnotify",
1530 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
1531 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
1532 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
1533 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
1534 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
1535 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
1536 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
1537 "llama-cpp",
1538 "ia-modelo-chat",
1539 # ── LA BÓVEDA DEL NAVEGADOR (SDD 26 §7.sexies, decisión 1 — 2026-09-21) ──────────────────────
1540 # `atuq` trae la décima extensión desde el 2026-09-15 y el host pineado atiende `vault.*` desde
1541 # el 2026-09-16; aun así la función venía **APAGADA DE FÁBRICA en las cuatro imágenes**, sin un
1542 # solo error: `vault.status` contesta `{"ok":true,"locked":true}` porque el host NUNCA abre la
1543 # base (sled toma lock exclusivo y el proceso que lanza Gecko muere y revive con cada pestaña);
1544 # le habla a un DUEÑO por un socket, y el dueño no estaba declarado en ningún perfil. Las dos
1545 # recetas estaban selladas desde el 2026-09-18 con `perfiles: []` — sellado ≠ instalado, que es
1546 # la lección de `foot`, escrita QUINCE veces en este fichero antes de hoy (`grep 'lección de
1547 # .foot.'`) y aun así vuelta a pasar. Y «cerrada» es una respuesta EXITOSA:
1548 # ni un log, ni un reintento, ni la insignia la distinguen de un usuario que no desbloqueó la suya.
1549 # `boveda` la app DUEÑA de la base: la abre para su ventana y levanta el socket del
1550 # navegador en un hilo. Sin ella, `vault.match` no ofrece nada, nunca.
1551 # `shuma-pregunta` el diálogo de consentimiento que `PorDialogo` lanza por PATH. Sin él,
1552 # `Command::new` falla ⇒ TODO `vault.fill` se deniega, indistinguible de que
1553 # la persona haya dicho que no.
1554 # **Las dos o ninguna**: media bóveda es una que niega todo en silencio. Cuestan ~43 M por imagen
1555 # (22 M + 21 M medidos sobre los artefactos sellados), contra los ~1,25 GiB del §6.7 de acá arriba.
1556 # ⚠ La respuesta fue NO hasta el 2026-09-18, y no por el tamaño: ninguna app llimphi de escritorio
1557 # podía abrir ventana sin Vulkan (§7.septies), así que declararlas habría sido instalar una bóveda
1558 # que niega todo. Eso se arregló en llimphi y se MIDIÓ en metal: las seis etapas de
1559 # `scripts/test-atuq-boveda-metal.py` en verde —con el navegador de verdad, el diálogo a la vista
1560 # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes.
1561 "boveda",
1562 "shuma-pregunta",
1563 # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21)
1564 # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se
1565 # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que
1566 # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo
1567 # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de
1568 # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además
1569 # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las
1570 # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo:
1571 # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y
1572 # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual
1573 # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa.
1574 # `agora-cli` el sembrador: `identity new --name <nombre>` crea la identidad y `unlock` la
1575 # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado
1576 # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene
1577 # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá.
1578 # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va
1579 # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la
1580 # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta
1581 # «autenticación fallida» y NO re-siembra.
1582 # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide
1583 # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA,
1584 # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque
1585 # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como
1586 # «la que siembra el login» (comentario de `pacha-secretos`).
1587 # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN
1588 # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow`
1589 # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no
1590 # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos
1591 # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le
1592 # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos
1593 # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de
1594 # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker.
1595 "agora-cli",
1596 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
1597 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
1598 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
1599 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
1600 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
1601 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
1602 "swayimg",
1603 # ── Y SE PUEDE SUBIR EL VOLUMEN (2026-10-20) ────────────────────────────────────────────────
1604 # El censo marcaba «audio de escritorio» 5 de 5 ausente, con el problema exacto: «PipeWire suena y
1605 # no hay con qué subirle el volumen desde una ventana». O sea que la distro reproducía audio y
1606 # **no tenía ningún control de volumen** — ni gráfico, ni de consola, ni un atajo de teclado
1607 # posible, porque un atajo llama a un comando y el comando no existía.
1608 #
1609 # `pamixer` sube, baja, silencia y lista fuentes y sumideros: es lo que un compositor engancha a
1610 # las teclas de volumen. Su `cxxopts` NO se declara — es sólo una cabecera y no deja nada que viaje.
1611 #
1612 # ⚠ Habla el protocolo de PulseAudio y eso NO es un desajuste: `pipewire` publica esa capa
1613 # (`pipewire-pulse` + `libpipewire-module-protocol-pulse.so`, verificado en el artefacto) y la
1614 # biblioteca cliente `libpulse.so.0` la publica `pulseaudio`. Las dos ya viajan en estos cinco
1615 # perfiles, igual que `libsndfile`, así que esto cuesta sólo sus 7,5 MB.
1616 #
1617 # ⚠ Su cadena de runtime baja TRES niveles y es lo que hizo fallar el primer build:
1618 # `pamixer → libpulse.so → libpulsecommon.so → libsndfile.so.1`. Está documentada igual en
1619 # `recipes/espeak-ng.toml` — el sandbox sólo monta lo declarado, y yo no la consulté.
1620 "pamixer",
1621 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
1622 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
1623 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
1624 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
1625 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
1626 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
1627 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
1628 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
1629 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
1630 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
1631 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
1632 "zathura", "zathura-pdf-poppler",
1633 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
1634 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
1635 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
1636 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
1637 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
1638 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
1639 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
1640 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
1641 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
1642 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
1643 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
1644 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
1645 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
1646 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
1647 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
1648 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
1649 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
1650 # partes ⇒ un solo artefacto, cero rebuilds).
1651 # ── ⚠ TERMINAL Y EDITOR: ESTA IMAGEN NO TENÍA NI UNO NI OTRO ─────────────────────────────────
1652 # Medido el 2026-09-03 cruzando la membresía de los cuatro perfiles contra una lista de emuladores
1653 # de terminal, editores y gestores de ficheros:
1654 #
1655 # escritorio-cosmic term=cosmic-term editor=cosmic-edit archivos=cosmic-files
1656 # escritorio-sway term=foot editor=vim archivos=—
1657 # escritorio-gnome term=— editor=— archivos=—
1658 # escritorio-kde term=— editor=— archivos=—
1659 #
1660 # Es la lección de `foot` otra vez y a mayor escala: sway llegó a 121/121 SIN emulador de terminal
1661 # porque nadie lo declaraba. Un escritorio sin terminal no es uno incompleto: es uno del que no se
1662 # puede salir cuando algo falla.
1663 #
1664 # `foot` y `helix` viven en el CORPUS —no en una cola—, así que desde acá se alcanzan y no hacen
1665 # falta recetas nuevas: las dos ya estaban selladas y sin declarar por NADIE. foot es Wayland puro
1666 # sobre `xdg-shell` (no pide protocolos de wlroots), así que corre bajo mutter igual que bajo sway;
1667 # su único NEEDED es `libc.so`. helix es TUI y corre dentro de la terminal.
1668 #
1669 # ⚠ ESTO ESTABA VIEJO, corregido el 2026-09-09. Decía que KDE quedaba fuera del arreglo y que «su
1670 # perfil son 13 raíces de ARRANQUE EN METAL, no la imagen completa», con konsole/kate/dolphin y
1671 # otras 19 apps selladas y sin declarar. Ya no: el frente KDE lo reordenó y hoy su perfil son **48
1672 # raíces** con konsole, dolphin, kate, okular, ark, spectacle, gwenview, kcalc, las fuentes y las
1673 # `*-shared` de runtime declaradas. De su cola quedan 8 recetas fuera del perfil, y son las que
1674 # corresponde: libICE, libSM, libXtst y los cinco eslabones que sólo servían a xwayland
1675 # (libfontenc, libXfont2, libxkbfile, libxshmfence, xkbcomp), huérfanos desde que la receta se
1676 # retiró.
1677 #
1678 # Lo que a KDE le queda no es este arreglo sino OTRO, medido el mismo día: su imagen no se hidrata
1679 # desde el perfil sino de un rootfs YA ARMADO (`kde-qemu-rootfs`, 8,4 G) que nadie re-deriva, así
1680 # que se queda atrás a medida que el perfil crece. Comprobado contra ese rootfs: de las 18 hojas
1681 # que el perfil declara están 15 — faltan `obs-studio` y `atuq`, justo las dos que se añadieron
1682 # después de aquella hidratación. Re-hidratarlo con `scripts/hydrate-profile.py escritorio-kde`
1683 # es decisión del frente KDE, no de éste.
1684 "foot", "helix",
1685 # ── FUENTES ──────────────────────────────────────────────────────────────────────────────────
1686 # ⚠ Añadida el 2026-09-03 porque este perfil no traía NI UN FICHERO DE FUENTE. Medido recorriendo
1687 # los artefactos del cierre y contando `.ttf/.otf/.ttc/.pfb`: **gnome=0**, sway=22 (dejavu),
1688 # kde=1 (una que viene dentro de qtbase). `fontconfig` estaba, pero fontconfig es el MOTOR que
1689 # busca fuentes, no una fuente — tenerlo y creer que hay tipografía es el mismo error que
1690 # confundir un directorio vacío con un artefacto.
1691 # Se descubrió con `poppler-render-check`: un PDF con fuentes base-14 (Helvetica, Times…) NO
1692 # trae la tipografía adentro, la sustituye el visor por fontconfig, así que sin fuentes el
1693 # documento se ve VACÍO — y el visor no da error, que es lo peor.
1694 # `dejavu-fonts` es la única receta de fuentes del catálogo entero. Que KDE esté igual (1 fuente
1695 # de rebote, dentro de qtbase) es un hallazgo aparte y NO se toca acá: ese perfil no lleva zathura
1696 # y su árbol lo está trabajando otro frente.
1697 "dejavu-fonts",
1698 # ── LAS FUENTES DEL MUNDO (2026-10-19) ───────────────────────────────────────────────────────
1699 # `dejavu-fonts` cubre latino, griego y cirílico, y era LO ÚNICO que tenía la distro: medido con
1700 # `scripts/censo-distro.py`, chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés,
1701 # hebreo y los emoji salían como cuadraditos — con el tablero entero en verde, porque ninguna
1702 # otra métrica mide «¿alcanza para usarla?».
1703 #
1704 # Las tres hacen falta y ninguna sustituye a otra: son tres repositorios distintos de upstream.
1705 # 144 MB en total por imagen, y es el precio de no dejar afuera a la mayor parte de la humanidad.
1706 # noto-fonts 48 MB Regular+Bold hinted de TODAS las familias (321 ficheros)
1707 # noto-fonts-cjk 86 MB 2 .ttc; cada uno trae zh-Hans, zh-Hant, ja y ko a la vez
1708 # noto-fonts-emoji 10 MB NotoColorEmoji — fontconfig ya la prefiere de fábrica, sin `.conf`
1709 "noto-fonts", "noto-fonts-cjk", "noto-fonts-emoji",
1710 # ── ESCRIBIR, NO SÓLO LEER (2026-10-20) ──────────────────────────────────────────────────────
1711 # Las fuentes Noto del 19-oct hicieron que la distro LEA chino, japonés, coreano, árabe e hindi.
1712 # Esto es lo que falta para ESCRIBIRLOS: un teclado tiene cien teclas y esos sistemas no se
1713 # teclean uno-a-uno. `censo-distro.py` marcaba la categoría «escribir en esos idiomas» 5 de 6.
1714 #
1715 # Entran los TRES JUNTOS y el orden del razonamiento importa:
1716 # `fcitx5` el marco: daemon, protocolo y barra de candidatos. Habla
1717 # `zwp_text_input_v3` / `zwp_input_method_v2`, que `mirada-compositor` registra
1718 # (`setup.rs:600-602`) — sin eso ningún IME serviría en esta distro.
1719 # `fcitx5-hangul` el MOTOR coreano. Sin un motor, el marco es una barra que no convierte nada:
1720 # la figura de `crun` sin podman, y la razón por la que fcitx5 se construyó el
1721 # 19-oct y NO se declaró hasta hoy.
1722 # `libhangul` ⚠ y no es sólo dep de build. Su código va ESTÁTICO dentro del addon, pero el
1723 # diccionario hanja NO: la ruta `/usr/share/libhangul/hanja/hanja.txt` está
1724 # HORNEADA en la `.a` (medido con `strings`), así que sin este paquete en la
1725 # imagen la conversión hanja busca un fichero que no existe. Es la figura de
1726 # «un directorio vacío como sonda» por el otro lado: el código está, el dato no.
1727 #
1728 # ⚠ EL COREANO PRIMERO POR COSTE MEDIDO, no por preferencia: deps nuevas por motor —coreano 1
1729 # (`libhangul`), japonés 2 (`anthy` + `fcitx5-qt`), chino 6 (`libime`, `libpinyin`, `opencc`,
1730 # `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**). El chino además necesita que Qt salga de
1731 # `incoming-kde` al corpus. Los otros dos son campañas con su propio presupuesto.
1732 #
1733 # ⚠ PESO: 294 MB (fcitx5 284 + libhangul 6,6 + el motor 3,3). Y **el 87 % de los binarios de
1734 # fcitx5 son secciones `.debug_*`** —294 MB de 334, medido con readelf—, así que desnudado serían
1735 # ~40 MB. No es una anomalía suya: `foot` y `mpv` también van sin desnudar, es la norma del corpus
1736 # y su sitio es la campaña del split de debug (SDD 23), no esta línea.
1737 # Y el JAPONÉS (2026-10-20), por el mismo camino y a la primera: `anthy-unicode` convierte kana a
1738 # kanji y `fcitx5-anthy` lo enchufa al marco. ⚠ Costó UNA receta, no dos — en la nota de arriba
1739 # escribí «japonés 2 (anthy + fcitx5-qt)» leyendo los makedepends de Alpine, y su CMakeLists no
1740 # menciona Qt ni una vez. `anthy-unicode` viaja por su DICCIONARIO (4 ficheros), igual que
1741 # `libhangul` por el hanja: sin los datos el motor carga y no convierte nada.
1742 "fcitx5", "fcitx5-hangul", "libhangul", "fcitx5-anthy", "anthy-unicode",
1743 # Y el CHINO (2026-10-20), que cierra los tres grandes. ⚠ **La estimación de la nota de arriba era
1744 # FALSA, y es la TERCERA vez con el mismo defecto**: decía «chino 6 (`libime`, `libpinyin`,
1745 # `opencc`, `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**)» y que haría falta sacar Qt de
1746 # `incoming-kde` al corpus. Medido sobre los `CMakeLists` y no sobre los `makedepends` de Alpine:
1747 # **cero Qt, cero libpinyin, cero fcitx5-lua, cero qtwebengine.** Qt sólo aparecía detrás de
1748 # `ENABLE_GUI`, que es la ventana de configuración y se apaga; el navegador embebido, detrás de
1749 # `ENABLE_BROWSER`. Mismo error que ya está anotado dos párrafos arriba para el japonés.
1750 #
1751 # ⚠ **`libime` NO se declara acá, y eso es medido, no un olvido.** Se construye con
1752 # `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias de libc++), así que entra ENTERA
1753 # dentro de los addons: comprobado con `readelf -d` que **ningún** `.so` de
1754 # `fcitx5-chinese-addons` declara `libIME*`. Declararla metería 159 MB en cada imagen para nada.
1755 # Lo que sí viaja son sus DATOS, que el addon busca en ejecución.
1756 #
1757 # `fcitx5-chinese-addons` pinyin + tabla + helper de trazos + chttrans + puntuación +
1758 # ancho completo. 6 addons
1759 # `opencc` simplificado ↔ tradicional. Viaja porque `libchttrans.so` enlaza
1760 # `libopencc.so` de verdad (lo exige el guardián): sin él la mitad del
1761 # chino escrito queda afuera, y no con un error sino en silencio
1762 # `libime-lm` el modelo de lenguaje (`zh_CN.lm`, 35 MB). Sin esto el pinyin carga
1763 # y **no ordena candidatos**
1764 # `libime-dict` el diccionario de pinyin + la Extensión B de Unicode
1765 # `libime-table` las 8 tablas (wubi, cangjie, erbi…): quien escribe por FORMA y no
1766 # por pronunciación. Sin ellas el método por tabla existe y no carga nada
1767 # `fcitx5-pinyin-stroke` buscar un carácter por sus TRAZOS — el caso de quien no sabe cómo se
1768 # `fcitx5-pinyin-table` pronuncia, que es justo cuando un método por pinyin no sirve
1769 #
1770 # ⚠ PESO: 189 MB los siete. El gordo es el addon (105) y el modelo (35), y **el 90 % de libime es
1771 # `.debug_*`** (143 de 159 MB, sumado con readelf) — la norma del corpus, cuyo sitio es el SDD 23.
1772 #
1773 # ⚠ QUEDA AFUERA Y SE DICE: `ENABLE_GUI=Off` ⇒ **no hay ventana de configuración** (es Qt y no
1774 # tenemos `fcitx5-qt`), así que elegir diccionarios o disposición se hace editando ficheros. Y
1775 # `ENABLE_CLOUDPINYIN=Off` es DECISIÓN y no falta de dep: manda lo que teclea el usuario a un
1776 # servidor remoto.
1777 "fcitx5-chinese-addons", "opencc", "libime-lm", "libime-dict", "libime-table",
1778 "fcitx5-pinyin-stroke", "fcitx5-pinyin-table",
1779 # ── LA MÁQUINA HABLA (2026-10-20) ────────────────────────────────────────────────────────────
1780 # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**, y es el hueco que deja gente
1781 # afuera de verdad: sin voz, una distro no es incómoda para quien no ve — es INUSABLE. Y ninguna
1782 # otra métrica del repo lo ve: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la
1783 # máquina es muda.
1784 #
1785 # `espeak-ng` son **21 MB con 114 diccionarios de idioma**, por síntesis formante: sin modelos, sin
1786 # red y sin GPU. Instala cuatro nombres (`espeak-ng`, `espeak`, `speak-ng`, `speak`) porque los
1787 # scripts viejos invocan los dos primeros.
1788 #
1789 # Los tres van juntos y el orden es el de la cadena:
1790 # `espeak-ng` el sintetizador y su `libespeak-ng.so` (lo que `orca` cargaría con dlopen)
1791 # `pcaudiolib` la salida de audio. ⚠ Sin ella espeak-ng **compila igual** y sintetiza a `.wav`
1792 # sin reproducir: `espeak-ng "hola"` no haría ruido. Verificado en el artefacto —
1793 # la `.so` trae libpulse/libasound en NEEDED y exporta los `audio_object_*`
1794 # `sonic` cambia la velocidad sin cambiar el tono. ⚠ No es opcional aunque su
1795 # `find_library` no lleve REQUIRED: si no la encuentra, espeak-ng la CLONA de git
1796 # en tiempo de build, y el sandbox no tiene red
1797 #
1798 # ⚠ Lo que esto NO cierra: **`orca`**, el lector de pantalla, necesita `at-spi2-core` y
1799 # `py3-gobject`, los dos sólo en `incoming-gnome` — la misma pila de introspección que bloqueó a
1800 # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
1801 # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
1802 "espeak-ng", "pcaudiolib", "sonic",
1803 # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
1804 # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
1805 # distinto que las otras no tapan:
1806 #
1807 # `speech-dispatcher` el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
1808 # `dotconf` sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
1809 # Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
1810 # Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
1811 # detrás como módulos. `dotconf` es su parser de configuración, la única
1812 # dep que faltaba
1813 # `brltty` los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
1814 # de gusto a la voz: braille es lo que permite leer código, ortografía y
1815 # matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
1816 # además no oye. Con voz y sin braille se deja afuera a sordociegos por
1817 # completo
1818 # `liblouis` braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
1819 # carácter: legible, tres veces más largo, y nadie lee así un texto
1820 #
1821 # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
1822 #
1823 # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
1824 # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
1825 # bloqueada por DOS cosas distintas:
1826 # · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
1827 # dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
1828 # rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
1829 # Es una decisión de operador, no algo que se hace de paso
1830 # · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
1831 # en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
1832 #
1833 # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
1834 # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
1835 # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
1836 # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
1837 # pendiente y no un olvido.
1838 "speech-dispatcher", "dotconf", "brltty", "liblouis",
1839 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
1840 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
1841 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
1842 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
1843 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
1844 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
1845 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
1846 "dejavu-fonts-nerd",
1847 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
1848 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
1849 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
1850 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
1851 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
1852 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
1853 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
1854 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
1855 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
1856 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
1857 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
1858 # `xz-shared` se suma el 2026-09-05, cazada por `scripts/vigia-sonames.py`. Y es la MÁS grave de
1859 # todas las de esta clase encontradas hasta ahora: quien pide `liblzma.so.5` no es una herramienta
1860 # ni un binario suelto, es **`libadwaita-1.so.0`** — la librería contra la que enlaza TODA app
1861 # GTK4/GNOME de la imagen. Sin proveedor, el loader falla en cada una de ellas, no en una.
1862 # El `xz` canónico del corpus es `.a`; la receta `-shared` existía sólo en `incoming-kde`, y se
1863 # promueve al corpus igual que `bzip2-shared`: mismo hash desde las dos partes
1864 # (b3:174289d5) ⇒ un solo artefacto, cero rebuilds.
1865 # Sólo va acá: en cosmic y sway ese soname lo pide únicamente `python3`, que no viaja en la imagen.
1866 "xz-shared",
1867 # ── TEMA DE ICONOS Y DE CURSORES ─────────────────────────────────────────────────────────────
1868 # Añadido el 2026-09-03: medido sobre los artefactos del cierre, este perfil tenía **CERO** temas
1869 # de iconos — ni siquiera `hicolor`. Se ve en `docs/evidencia/gnome-shell-qemu-2026-09-03-libs-
1870 # compartidas.png`: la barra superior trae la fecha (texto) y el pill de espacios de trabajo, y la
1871 # DERECHA está vacía — los indicadores de red, volumen y batería son iconos, y no había ninguno
1872 # que dibujar. Mismo defecto que en KDE y por el mismo motivo: data de RUNTIME que no alcanza
1873 # ninguna arista de BUILD.
1874 #
1875 # `adwaita-icon-theme` estaba escrita y SELLADA desde el frente GNOME y no la declaraba nadie. Su
1876 # propia cabecera explica por qué la escribieron: mutter avisa `No cursor theme available` y **sin
1877 # tema de cursor no hay puntero visible** con el cursor por software que obliga virtio-gpu. Los
1878 # XCursor viven ahí adentro (`Adwaita/cursors/`), no en un paquete aparte.
1879 # `hicolor-icon-theme` va explícito aunque Adwaita lo declare `Inherits=hicolor`: la herencia se
1880 # resuelve en RUNTIME leyendo el `index.theme` del padre, así que el padre tiene que estar
1881 # INSTALADO. Desde hoy se resuelve contra el corpus (misma receta, mismo hash).
1882 "adwaita-icon-theme", "hicolor-icon-theme",
1883 # ── EXTENSIONES DEL SHELL ────────────────────────────────────────────────────────────────────
1884 # ⚠ Añadida el 2026-09-09: esta imagen traía la CÁSCARA de GNOME y ni una extensión. No es un
1885 # detalle estético — GNOME manda un shell deliberadamente mínimo y el ecosistema lo completa por
1886 # acá; sin ninguna, el escritorio es lo que el proyecto entrega y nadie usa tal cual.
1887 # El shell SÍ sabe cargarlas sin nada más: `ui/extensionSystem.js` va en el gresource sin opción
1888 # que lo apague y su gschema sellado trae `enabled-extensions`. Lo que faltaba era que hubiera
1889 # alguna, y va de RAÍZ por la lección de `foot` que este fichero ya aprendió tres veces.
1890 # ⚠ NO hace falta glib-networking ni TLS: eso sólo lo pide BAJARLAS desde extensions.gnome.org
1891 # con el descargador del shell. Empaquetada, se instala como cualquier receta.
1892 # La receta se construye desde el REPO del autor (tag v72), no desde el zip de EGO: hammer extrae
1893 # con `tar -x` y GNU tar no lee zip, y además el zip es un artefacto que construye EGO a partir
1894 # de ese mismo repo. El layout instalado se verificó CONTRA ese zip: idéntico, sin sobras ni
1895 # faltantes.
1896 # ⚠ La LISTA de activas no vive en ninguna receta: `enabled-extensions` es un ARRAY y un override
1897 # de GSettings lo escribe entero, así que dos recetas con el suyo no se suman — ganaría la última
1898 # y las otras quedarían instaladas y MUERTAS, sin error. La arma `scripts/gnome/gnome-start-qemu.sh`
1899 # enumerando los uuid presentes en /usr/share/gnome-shell/extensions antes de compilar los
1900 # esquemas. Una receta no puede ser ese sitio: hammer exige `[source]` en toda receta.
1901 "gnome-shell-extension-blur-my-shell",
1902 # `dash-to-panel` es la que más cambia el uso diario: junta dash, ventanas y bandeja en una barra.
1903 # `appindicator` devuelve los ICONOS DE BANDEJA que GNOME quitó — sin ella, las apps que usan
1904 # StatusNotifierItem corren y no tienen dónde mostrarse.
1905 "gnome-shell-extension-dash-to-panel", "gnome-shell-extension-appindicator",
1906 # ── Y LA APP QUE GNOME NO INSTALA Y TODO EL MUNDO NECESITA ───────────────────────────────────
1907 # `gnome-tweaks`: tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que GNOME
1908 # esconde, y el interruptor de las extensiones. Es una app de PYTHON, así que su receta declara
1909 # `python3` y `py3-gobject` como deps de RUNTIME: sin ellas en la imagen se instala y no arranca,
1910 # y ningún build puede delatarlo.
1911 "gnome-tweaks",
1912 # ── RAÍCES DE RUNTIME QUE EL GRAFO NO PUEDE VER (traídas acá el 2026-09-09) ───────────────────
1913 # ⚠ Estas once vivían SÓLO dentro de `scripts/gnome/hydrate-gnome.sh`, en una lista escrita a
1914 # mano. O sea DOS fuentes de verdad para «qué lleva la imagen de GNOME», y habían divergido en
1915 # los DOS sentidos: el script no conocía 21 paquetes que este perfil declara —entre ellos el
1916 # navegador, la terminal, el editor, el visor de PDF, las fuentes y las tres extensiones— y este
1917 # perfil no conocía estas once, sin las cuales el shell no arranca. Es exactamente el modo de
1918 # fallo que la cabecera de `scripts/hydrate-profile.py` describe y que `vigia-imagen.py` existe
1919 # para cazar.
1920 # Ninguna es dep de BUILD de nadie: el shell las carga por `imports.gi.*` al arrancar, y eso es
1921 # invisible para la clausura de deps. La justificación de cada una la traía el script:
1922 "accountsservice", # imports.gi.AccountsService
1923 "gi-foreign-typelibs", # DBus-1.0 / cairo-1.0, que Gdk y Atspi incluyen
1924 "gi-girepository-typelib", # lo pide el propio gjs desde su JS embebido
1925 "libgdm", # imports.gi.Gdm — el shell lo importa SIN condición al arrancar
1926 "geoclue", # imports.gi.Geoclue
1927 "libgweather", # imports.gi.GWeather
1928 "upower", # imports.gi.UPowerGlib
1929 "librsvg", # imports.gi.Rsvg
1930 "ibus", # imports.gi.IBus
1931 # Ni typelib ni librería: los GSCHEMAS de gnome-settings-daemon. El demonio está aparcado
1932 # (GTK3/X11) pero el shell lee sus esquemas al armar los quick settings, y si faltan la excepción
1933 # ocurre DENTRO de Main.start() ⇒ el panel no se arma y la pantalla queda NEGRA.
1934 "gsd-schemas",
1935 # `wireplumber` es lo que le da DISPOSITIVOS a PipeWire (que ya entra por la clausura): sin él hay
1936 # socket de audio y nada que suene.
1937 "wireplumber",
1938 # ── LOS DOS QUE LA IMAGEN INYECTABA A MANO Y NINGÚN PERFIL DECLARABA ─────────────────────────
1939 # Hallados el 2026-09-12 por `targets.py --servicios escritorio-gnome`, que los rechazó con «lo
1940 # declara `arje-logind-compat`, que NO pertenece al perfil». No era falso positivo: los dos
1941 # estaban en CERO perfiles y sin embargo `scripts/gnome/qemu-desktop-image.sh` los copia al
1942 # rootfs a mano, y el de COSMIC hace `exit 1` si falta logind-compat. O sea: dos binarios
1943 # imprescindibles, presentes en la imagen y ausentes del destino declarado — la misma forma del
1944 # agujero de `foot`, encontrada esta vez por una comprobación en vez de por una imagen inusable.
1945 "arje-logind-compat", "arje-polkit-compat",
1946 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
1947 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
1948 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
1949 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
1950 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
1951 # una sola copia.
1952 #
1953 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
1954 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
1955 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
1956 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
1957 # recetas: son tres perfiles sin «enable» ninguno.
1958 #
1959 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
1960 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
1961 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
1962 "cups", "bluez",
1963 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
1964 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
1965 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
1966 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
1967 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
1968 # ⚠ CORREGIDO EL MISMO DÍA: esta nota decía «NO declarar esto en escritorio-kde, allá plasma-nm ya
1969 # arrastra la variante de la cola». **Ya no hay variante de cola**: `incoming-kde/networkmanager`
1970 # se retiró y `networkmanager-qt` resuelve al padre, así que la distro tiene UN solo
1971 # NetworkManager y KDE pasó a tener WiFi (la suya iba con `-Dwifi=false`). Costó 2 rebuilds
1972 # —networkmanager-qt y plasma-nm—, medidos con `yupana radio` antes de tocar nada.
1973 # KDE sigue sin listarlo entre sus `paquetes` porque no hace falta: `plasma-nm` lo arrastra por
1974 # clausura. Lo que sí hacía falta era arrancarlo, y está en su lista de `servicios`.
1975 "networkmanager",
1976]
1977
1978# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
1979# Hoy estos ocho los lanza a mano `scripts/gnome/gnome-start-qemu.sh` con `&`, sin supervisión, sin
1980# backoff y sin el `CRASHED` real — o sea sin nada de lo que arje es PID 1 para dar. Declararlos acá
1981# no los mueve todavía: los pone en el mismo sitio que el resto del destino de la imagen, y hace que
1982# `scripts/targets.py --servicios escritorio-gnome` pueda contradecir a la realidad en vez de que la
1983# realidad viva sólo dentro de un script de 500 líneas.
1984#
1985# Los tres de SESIÓN (pipewire, pipewire-pulse, wireplumber) salen con AVISO a propósito: fuera de
1986# mirada nadie entrega Cards de sesión, así que están declarados y NO arrancados por arje. Un hueco
1987# escrito se ve; uno omitido, no.
1988servicios = [
1989 "dbus-system", "logind-compat", "polkit-compat", "accounts-daemon", "upowerd", "colord",
1990 "pipewire", "pipewire-pulse", "wireplumber",
1991 # 2026-09-13: los dos que nacieron con `cups` y `bluez`. Sin esta línea la imagen los traía
1992 # instalados y apagados, que es la mitad inútil de tenerlos.
1993 "cupsd", "bluetoothd",
1994 "NetworkManager", # 2026-09-13: el perfil no tenía gestor de red; ver el paquete arriba
1995]
1996
1997[perfil.escritorio-cosmic]
1998descripcion = "escritorio COSMIC (System76, Rust sobre smithay/iced) desde fuente — 4º escritorio"
1999cola = "incoming-cosmic"
2000# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
2001# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
2002# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
2003# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
2004# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
2005# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
2006# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
2007# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
2008# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
2009hereda = ["cli"]
2010# Las raíces son EXACTAMENTE lo que `cosmic-session/src/main.rs` levanta, más el gestor mismo y los
2011# datos que ningún `[deps]` alcanza. El resto es CLAUSURA.
2012#
2013# ── POR QUÉ ESTE PERFIL NO ES BUROCRACIA ────────────────────────────────────────────────────────
2014# Sin él, `yupana radio` sobre cualquier receta COMPARTIDA —libinput, libxkbcommon, libudev-zero,
2015# mesa, wayland— devuelve «IMÁGENES afectadas: (ninguna declarada)» para COSMIC, y el próximo que las
2016# toque MIDE DE MENOS. La campaña existe en el store y en el runbook, pero el ábaco no la ve: el radio
2017# de un re-hash es justo lo que el perfil convierte en dato.
2018#
2019# Nota de nombres, que acá no coinciden y ya costó una vez: el repo `cosmic-workspaces-epoch` produce
2020# el binario `cosmic-workspaces`, y el AppID tampoco lleva el sufijo. Se lista por el nombre de la
2021# RECETA, que es el del repo.
2022paquetes = [
2023 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
2024 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
2025 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
2026 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
2027 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
2028 # lecturas del modelo de composición.
2029 "desktop-file-utils",
2030 "cosmic-comp", "cosmic-session", "cosmic-settings-daemon",
2031 "cosmic-panel", "cosmic-launcher", "cosmic-app-library", "cosmic-workspaces-epoch",
2032 "cosmic-notifications", "cosmic-osd", "cosmic-bg", "cosmic-idle",
2033 # Los applets del panel. No es un componente que la sesión lance: lo spawnea el PANEL, por AppID,
2034 # y sin ellos arranca y no se dibuja. Un binario multiplexor con veinte symlinks.
2035 "cosmic-applets",
2036 # El MOTOR del lanzador, de otro repo y fuera del pin `epoch-N`: `cosmic-launcher` sólo dibuja y
2037 # lanza a `pop-launcher` como proceso hijo por PATH. Sin él, `Super` no abre nada.
2038 "pop-launcher",
2039 # Y el `qalc` al que el plugin `calc` le pasa la expresión: tercer nivel de hijo por PATH. Es la
2040 # primera raíz del perfil que existe para que una TECLA dé un resultado, no para que algo enlace.
2041 "libqalculate",
2042 # La primera APLICACIÓN con ventana del catálogo, y la primera entrada de la biblioteca que no es
2043 # `NoDisplay`. Hasta acá el perfil listaba escritorio; desde acá lista también lo que se abre.
2044 "cosmic-term",
2045 # La segunda: el gestor de ficheros. Sin `gvfs` (no hay glib compartida) ⇒ navegación local sí,
2046 # montajes remotos no.
2047 "cosmic-files",
2048 # La tercera: el panel de control. Habla con los daemons por zbus, así que NO arrastra
2049 # NetworkManager/udisks/accountsservice — sólo libdrm y dav1d sobre el juego fijo.
2050 "cosmic-settings",
2051 # La cuarta: el editor de texto, y la más barata de las cuatro — su árbol ya estaba compilado (usa
2052 # `cosmic-files` como crate, igual que la terminal). Su binario enlaza SÓLO `libxkbcommon` y
2053 # `libc.so`: dos NEEDED, el mínimo de todos los clientes de la suite.
2054 "cosmic-edit",
2055 # La quinta: la tienda. Con el backend `pkgar` —el único de los cuatro que no pide ni una torre de
2056 # C (flatpak) ni un demonio ausente (packagekit)— NAVEGA el catálogo instalado leyendo el AppStream
2057 # del sistema, pero NO instala: nada está cableado al `.swm` de hammer.
2058 "cosmic-store",
2059 # La sexta: la captura de pantalla. Cero C —el único `[deps] build = []` de la campaña— pero HOY
2060 # NO FUNCIONA: todo su trabajo es `Screenshot::request()` contra `org.freedesktop.portal.Desktop`,
2061 # y en la imagen no hay portal. Se lista igual para que el hueco sea visible.
2062 "cosmic-screenshot",
2063 # La CADENA DEL PORTAL, dos raíces porque son dos procesos que se encuentran por D-Bus en runtime:
2064 # el frontend registra `org.freedesktop.portal.Desktop` y enruta al backend, que implementa
2065 # Access/FileChooser/Screenshot/Settings/ScreenCast. Sin ellos no hay captura, ni selector de
2066 # ficheros de portal, ni compartir pantalla.
2067 "xdg-desktop-portal", "xdg-desktop-portal-cosmic",
2068 # Y la SONDA con que se mide esa cadena. No es escritorio: es el instrumento. Se lista como raíz
2069 # porque nadie la declara como dep y sin ella el perfil no incluiría con qué verificarse a sí
2070 # mismo — un handshake de portal sólo se puede ejercer DESDE la sesión, con bus y compositor vivos.
2071 "portal-probe",
2072 # El GESTOR DE SESIÓN de pipewire. Se lista como raíz porque ningún `[deps]` lo alcanza: es un
2073 # CLIENTE del demonio, no una librería de nadie. Trae consigo la glib COMPARTIDA (`glib-shared`),
2074 # que no es la estática que usa el resto de la cola — wireplumber produce `.so` y la estática del
2075 # corpus no es PIC. Construido y corriendo; NO desbloqueó ScreenCast (ver el runbook).
2076 "wireplumber",
2077 # Datos, no binarios: el tema de iconos (y el hicolor del que hereda), las tablas de xkb y una
2078 # tipografía. Ninguno es dep de build de nadie y sin ellos el escritorio arranca roto — sin puntero,
2079 # sin teclado o con tofu.
2080 "cosmic-icons", "hicolor-icon-theme", "xkeyboard-config", "dejavu-fonts",
2081 # ── LAS FUENTES DEL MUNDO (2026-10-19) ───────────────────────────────────────────────────────
2082 # `dejavu-fonts` cubre latino, griego y cirílico, y era LO ÚNICO que tenía la distro: medido con
2083 # `scripts/censo-distro.py`, chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés,
2084 # hebreo y los emoji salían como cuadraditos — con el tablero entero en verde, porque ninguna
2085 # otra métrica mide «¿alcanza para usarla?».
2086 #
2087 # Las tres hacen falta y ninguna sustituye a otra: son tres repositorios distintos de upstream.
2088 # 144 MB en total por imagen, y es el precio de no dejar afuera a la mayor parte de la humanidad.
2089 # noto-fonts 48 MB Regular+Bold hinted de TODAS las familias (321 ficheros)
2090 # noto-fonts-cjk 86 MB 2 .ttc; cada uno trae zh-Hans, zh-Hant, ja y ko a la vez
2091 # noto-fonts-emoji 10 MB NotoColorEmoji — fontconfig ya la prefiere de fábrica, sin `.conf`
2092 "noto-fonts", "noto-fonts-cjk", "noto-fonts-emoji",
2093 # ── ESCRIBIR, NO SÓLO LEER (2026-10-20) ──────────────────────────────────────────────────────
2094 # Las fuentes Noto del 19-oct hicieron que la distro LEA chino, japonés, coreano, árabe e hindi.
2095 # Esto es lo que falta para ESCRIBIRLOS: un teclado tiene cien teclas y esos sistemas no se
2096 # teclean uno-a-uno. `censo-distro.py` marcaba la categoría «escribir en esos idiomas» 5 de 6.
2097 #
2098 # Entran los TRES JUNTOS y el orden del razonamiento importa:
2099 # `fcitx5` el marco: daemon, protocolo y barra de candidatos. Habla
2100 # `zwp_text_input_v3` / `zwp_input_method_v2`, que `mirada-compositor` registra
2101 # (`setup.rs:600-602`) — sin eso ningún IME serviría en esta distro.
2102 # `fcitx5-hangul` el MOTOR coreano. Sin un motor, el marco es una barra que no convierte nada:
2103 # la figura de `crun` sin podman, y la razón por la que fcitx5 se construyó el
2104 # 19-oct y NO se declaró hasta hoy.
2105 # `libhangul` ⚠ y no es sólo dep de build. Su código va ESTÁTICO dentro del addon, pero el
2106 # diccionario hanja NO: la ruta `/usr/share/libhangul/hanja/hanja.txt` está
2107 # HORNEADA en la `.a` (medido con `strings`), así que sin este paquete en la
2108 # imagen la conversión hanja busca un fichero que no existe. Es la figura de
2109 # «un directorio vacío como sonda» por el otro lado: el código está, el dato no.
2110 #
2111 # ⚠ EL COREANO PRIMERO POR COSTE MEDIDO, no por preferencia: deps nuevas por motor —coreano 1
2112 # (`libhangul`), japonés 2 (`anthy` + `fcitx5-qt`), chino 6 (`libime`, `libpinyin`, `opencc`,
2113 # `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**). El chino además necesita que Qt salga de
2114 # `incoming-kde` al corpus. Los otros dos son campañas con su propio presupuesto.
2115 #
2116 # ⚠ PESO: 294 MB (fcitx5 284 + libhangul 6,6 + el motor 3,3). Y **el 87 % de los binarios de
2117 # fcitx5 son secciones `.debug_*`** —294 MB de 334, medido con readelf—, así que desnudado serían
2118 # ~40 MB. No es una anomalía suya: `foot` y `mpv` también van sin desnudar, es la norma del corpus
2119 # y su sitio es la campaña del split de debug (SDD 23), no esta línea.
2120 # Y el JAPONÉS (2026-10-20), por el mismo camino y a la primera: `anthy-unicode` convierte kana a
2121 # kanji y `fcitx5-anthy` lo enchufa al marco. ⚠ Costó UNA receta, no dos — en la nota de arriba
2122 # escribí «japonés 2 (anthy + fcitx5-qt)» leyendo los makedepends de Alpine, y su CMakeLists no
2123 # menciona Qt ni una vez. `anthy-unicode` viaja por su DICCIONARIO (4 ficheros), igual que
2124 # `libhangul` por el hanja: sin los datos el motor carga y no convierte nada.
2125 "fcitx5", "fcitx5-hangul", "libhangul", "fcitx5-anthy", "anthy-unicode",
2126 # Y el CHINO (2026-10-20), que cierra los tres grandes. ⚠ **La estimación de la nota de arriba era
2127 # FALSA, y es la TERCERA vez con el mismo defecto**: decía «chino 6 (`libime`, `libpinyin`,
2128 # `opencc`, `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**)» y que haría falta sacar Qt de
2129 # `incoming-kde` al corpus. Medido sobre los `CMakeLists` y no sobre los `makedepends` de Alpine:
2130 # **cero Qt, cero libpinyin, cero fcitx5-lua, cero qtwebengine.** Qt sólo aparecía detrás de
2131 # `ENABLE_GUI`, que es la ventana de configuración y se apaga; el navegador embebido, detrás de
2132 # `ENABLE_BROWSER`. Mismo error que ya está anotado dos párrafos arriba para el japonés.
2133 #
2134 # ⚠ **`libime` NO se declara acá, y eso es medido, no un olvido.** Se construye con
2135 # `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias de libc++), así que entra ENTERA
2136 # dentro de los addons: comprobado con `readelf -d` que **ningún** `.so` de
2137 # `fcitx5-chinese-addons` declara `libIME*`. Declararla metería 159 MB en cada imagen para nada.
2138 # Lo que sí viaja son sus DATOS, que el addon busca en ejecución.
2139 #
2140 # `fcitx5-chinese-addons` pinyin + tabla + helper de trazos + chttrans + puntuación +
2141 # ancho completo. 6 addons
2142 # `opencc` simplificado ↔ tradicional. Viaja porque `libchttrans.so` enlaza
2143 # `libopencc.so` de verdad (lo exige el guardián): sin él la mitad del
2144 # chino escrito queda afuera, y no con un error sino en silencio
2145 # `libime-lm` el modelo de lenguaje (`zh_CN.lm`, 35 MB). Sin esto el pinyin carga
2146 # y **no ordena candidatos**
2147 # `libime-dict` el diccionario de pinyin + la Extensión B de Unicode
2148 # `libime-table` las 8 tablas (wubi, cangjie, erbi…): quien escribe por FORMA y no
2149 # por pronunciación. Sin ellas el método por tabla existe y no carga nada
2150 # `fcitx5-pinyin-stroke` buscar un carácter por sus TRAZOS — el caso de quien no sabe cómo se
2151 # `fcitx5-pinyin-table` pronuncia, que es justo cuando un método por pinyin no sirve
2152 #
2153 # ⚠ PESO: 189 MB los siete. El gordo es el addon (105) y el modelo (35), y **el 90 % de libime es
2154 # `.debug_*`** (143 de 159 MB, sumado con readelf) — la norma del corpus, cuyo sitio es el SDD 23.
2155 #
2156 # ⚠ QUEDA AFUERA Y SE DICE: `ENABLE_GUI=Off` ⇒ **no hay ventana de configuración** (es Qt y no
2157 # tenemos `fcitx5-qt`), así que elegir diccionarios o disposición se hace editando ficheros. Y
2158 # `ENABLE_CLOUDPINYIN=Off` es DECISIÓN y no falta de dep: manda lo que teclea el usuario a un
2159 # servidor remoto.
2160 "fcitx5-chinese-addons", "opencc", "libime-lm", "libime-dict", "libime-table",
2161 "fcitx5-pinyin-stroke", "fcitx5-pinyin-table",
2162 # ── LA MÁQUINA HABLA (2026-10-20) ────────────────────────────────────────────────────────────
2163 # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**, y es el hueco que deja gente
2164 # afuera de verdad: sin voz, una distro no es incómoda para quien no ve — es INUSABLE. Y ninguna
2165 # otra métrica del repo lo ve: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la
2166 # máquina es muda.
2167 #
2168 # `espeak-ng` son **21 MB con 114 diccionarios de idioma**, por síntesis formante: sin modelos, sin
2169 # red y sin GPU. Instala cuatro nombres (`espeak-ng`, `espeak`, `speak-ng`, `speak`) porque los
2170 # scripts viejos invocan los dos primeros.
2171 #
2172 # Los tres van juntos y el orden es el de la cadena:
2173 # `espeak-ng` el sintetizador y su `libespeak-ng.so` (lo que `orca` cargaría con dlopen)
2174 # `pcaudiolib` la salida de audio. ⚠ Sin ella espeak-ng **compila igual** y sintetiza a `.wav`
2175 # sin reproducir: `espeak-ng "hola"` no haría ruido. Verificado en el artefacto —
2176 # la `.so` trae libpulse/libasound en NEEDED y exporta los `audio_object_*`
2177 # `sonic` cambia la velocidad sin cambiar el tono. ⚠ No es opcional aunque su
2178 # `find_library` no lleve REQUIRED: si no la encuentra, espeak-ng la CLONA de git
2179 # en tiempo de build, y el sandbox no tiene red
2180 #
2181 # ⚠ Lo que esto NO cierra: **`orca`**, el lector de pantalla, necesita `at-spi2-core` y
2182 # `py3-gobject`, los dos sólo en `incoming-gnome` — la misma pila de introspección que bloqueó a
2183 # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
2184 # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
2185 "espeak-ng", "pcaudiolib", "sonic",
2186 # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
2187 # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
2188 # distinto que las otras no tapan:
2189 #
2190 # `speech-dispatcher` el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
2191 # `dotconf` sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
2192 # Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
2193 # Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
2194 # detrás como módulos. `dotconf` es su parser de configuración, la única
2195 # dep que faltaba
2196 # `brltty` los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
2197 # de gusto a la voz: braille es lo que permite leer código, ortografía y
2198 # matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
2199 # además no oye. Con voz y sin braille se deja afuera a sordociegos por
2200 # completo
2201 # `liblouis` braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
2202 # carácter: legible, tres veces más largo, y nadie lee así un texto
2203 #
2204 # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
2205 #
2206 # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
2207 # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
2208 # bloqueada por DOS cosas distintas:
2209 # · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
2210 # dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
2211 # rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
2212 # Es una decisión de operador, no algo que se hace de paso
2213 # · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
2214 # en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
2215 #
2216 # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
2217 # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
2218 # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
2219 # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
2220 # pendiente y no un olvido.
2221 "speech-dispatcher", "dotconf", "brltty", "liblouis",
2222 # ⚠ «sin puntero» lo decía este comentario desde que se escribió y NO era cierto: `cosmic-icons`
2223 # instala iconos de aplicación, no cursores, así que este perfil llegó a 43/43 con el puntero
2224 # INVISIBLE. Lo cazó `vigia-imagen.py` el 2026-09-04. Los cursores son su propia raíz.
2225 "adwaita-cursors",
2226 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
2227 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
2228 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
2229 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
2230 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
2231 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
2232 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
2233 "dejavu-fonts-nerd",
2234 # Lo que `start-cosmic` ejecuta y ningún `[deps]` pide: bash de verdad y el bus de sesión.
2235 "bash", "dbus",
2236 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
2237 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
2238 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
2239 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
2240 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
2241 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
2242 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
2243 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
2244 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
2245 "mpv",
2246 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
2247 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
2248 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
2249 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
2250 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
2251 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
2252 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
2253 "atuq",
2254 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
2255 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
2256 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
2257 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
2258 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
2259 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
2260 # así que la imagen necesita además a alguien escuchando ese nombre.
2261 "libnotify",
2262 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
2263 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
2264 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
2265 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
2266 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
2267 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
2268 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
2269 "llama-cpp",
2270 "ia-modelo-chat",
2271 # ── LA BÓVEDA DEL NAVEGADOR (SDD 26 §7.sexies, decisión 1 — 2026-09-21) ──────────────────────
2272 # `atuq` trae la décima extensión desde el 2026-09-15 y el host pineado atiende `vault.*` desde
2273 # el 2026-09-16; aun así la función venía **APAGADA DE FÁBRICA en las cuatro imágenes**, sin un
2274 # solo error: `vault.status` contesta `{"ok":true,"locked":true}` porque el host NUNCA abre la
2275 # base (sled toma lock exclusivo y el proceso que lanza Gecko muere y revive con cada pestaña);
2276 # le habla a un DUEÑO por un socket, y el dueño no estaba declarado en ningún perfil. Las dos
2277 # recetas estaban selladas desde el 2026-09-18 con `perfiles: []` — sellado ≠ instalado, que es
2278 # la lección de `foot`, escrita QUINCE veces en este fichero antes de hoy (`grep 'lección de
2279 # .foot.'`) y aun así vuelta a pasar. Y «cerrada» es una respuesta EXITOSA:
2280 # ni un log, ni un reintento, ni la insignia la distinguen de un usuario que no desbloqueó la suya.
2281 # `boveda` la app DUEÑA de la base: la abre para su ventana y levanta el socket del
2282 # navegador en un hilo. Sin ella, `vault.match` no ofrece nada, nunca.
2283 # `shuma-pregunta` el diálogo de consentimiento que `PorDialogo` lanza por PATH. Sin él,
2284 # `Command::new` falla ⇒ TODO `vault.fill` se deniega, indistinguible de que
2285 # la persona haya dicho que no.
2286 # **Las dos o ninguna**: media bóveda es una que niega todo en silencio. Cuestan ~43 M por imagen
2287 # (22 M + 21 M medidos sobre los artefactos sellados), contra los ~1,25 GiB del §6.7 de acá arriba.
2288 # ⚠ La respuesta fue NO hasta el 2026-09-18, y no por el tamaño: ninguna app llimphi de escritorio
2289 # podía abrir ventana sin Vulkan (§7.septies), así que declararlas habría sido instalar una bóveda
2290 # que niega todo. Eso se arregló en llimphi y se MIDIÓ en metal: las seis etapas de
2291 # `scripts/test-atuq-boveda-metal.py` en verde —con el navegador de verdad, el diálogo a la vista
2292 # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes.
2293 "boveda",
2294 "shuma-pregunta",
2295 # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21)
2296 # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se
2297 # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que
2298 # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo
2299 # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de
2300 # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además
2301 # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las
2302 # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo:
2303 # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y
2304 # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual
2305 # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa.
2306 # `agora-cli` el sembrador: `identity new --name <nombre>` crea la identidad y `unlock` la
2307 # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado
2308 # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene
2309 # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá.
2310 # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va
2311 # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la
2312 # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta
2313 # «autenticación fallida» y NO re-siembra.
2314 # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide
2315 # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA,
2316 # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque
2317 # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como
2318 # «la que siembra el login» (comentario de `pacha-secretos`).
2319 # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN
2320 # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow`
2321 # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no
2322 # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos
2323 # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le
2324 # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos
2325 # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de
2326 # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker.
2327 "agora-cli",
2328 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
2329 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
2330 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
2331 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
2332 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
2333 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
2334 "swayimg",
2335 # ── Y SE PUEDE SUBIR EL VOLUMEN (2026-10-20) ────────────────────────────────────────────────
2336 # El censo marcaba «audio de escritorio» 5 de 5 ausente, con el problema exacto: «PipeWire suena y
2337 # no hay con qué subirle el volumen desde una ventana». O sea que la distro reproducía audio y
2338 # **no tenía ningún control de volumen** — ni gráfico, ni de consola, ni un atajo de teclado
2339 # posible, porque un atajo llama a un comando y el comando no existía.
2340 #
2341 # `pamixer` sube, baja, silencia y lista fuentes y sumideros: es lo que un compositor engancha a
2342 # las teclas de volumen. Su `cxxopts` NO se declara — es sólo una cabecera y no deja nada que viaje.
2343 #
2344 # ⚠ Habla el protocolo de PulseAudio y eso NO es un desajuste: `pipewire` publica esa capa
2345 # (`pipewire-pulse` + `libpipewire-module-protocol-pulse.so`, verificado en el artefacto) y la
2346 # biblioteca cliente `libpulse.so.0` la publica `pulseaudio`. Las dos ya viajan en estos cinco
2347 # perfiles, igual que `libsndfile`, así que esto cuesta sólo sus 7,5 MB.
2348 #
2349 # ⚠ Su cadena de runtime baja TRES niveles y es lo que hizo fallar el primer build:
2350 # `pamixer → libpulse.so → libpulsecommon.so → libsndfile.so.1`. Está documentada igual en
2351 # `recipes/espeak-ng.toml` — el sandbox sólo monta lo declarado, y yo no la consulté.
2352 "pamixer",
2353 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
2354 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
2355 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
2356 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
2357 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
2358 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
2359 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
2360 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
2361 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
2362 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
2363 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
2364 "zathura", "zathura-pdf-poppler",
2365 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
2366 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
2367 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
2368 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
2369 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
2370 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
2371 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
2372 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
2373 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
2374 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
2375 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
2376 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
2377 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
2378 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
2379 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
2380 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
2381 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
2382 # partes ⇒ un solo artefacto, cero rebuilds).
2383 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
2384 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
2385 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
2386 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
2387 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
2388 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
2389 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
2390 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
2391 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
2392 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
2393 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
2394 # `arje-logind-compat` (2026-09-12): mismo hallazgo que en `escritorio-gnome` — la imagen lo
2395 # inyecta a mano y `scripts/cosmic/qemu-desktop-image.sh` hace `exit 1` si falta, pero no estaba
2396 # en ningún perfil. COSMIC NO usa `arje-polkit-compat` (verificado: sus scripts no lo nombran),
2397 # así que va sólo este.
2398 "arje-logind-compat",
2399 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
2400 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
2401 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
2402 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
2403 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
2404 # una sola copia.
2405 #
2406 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
2407 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
2408 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
2409 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
2410 # recetas: son tres perfiles sin «enable» ninguno.
2411 #
2412 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
2413 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
2414 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
2415 "cups", "bluez",
2416 # `cliphist` (2026-09-12): historial de portapapeles. El portapapeles de Wayland muere con el
2417 # cliente que lo ofrece —cerrás la terminal y lo copiado se va—; esto lo persiste. Va SÓLO en
2418 # este perfil y en COSMIC: habla `wlr-data-control`, que KWin y mutter NO implementan (mismo
2419 # muro que `wf-recorder`). Declararlo donde no funciona sería peor que no tenerlo.
2420 "cliphist",
2421 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
2422 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
2423 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
2424 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
2425 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
2426 # ⚠ NO declarar esto en `escritorio-kde`: allá `plasma-nm` ya arrastra la variante de la cola, y
2427 # dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib.
2428 "networkmanager",
2429 # `upower` (2026-09-13): **COSMIC dibujaba un indicador de batería sin nadie que se la contara.**
2430 # El demonio no estaba en la clausura: la receta vivía en `incoming-kde` y en `incoming-gnome`,
2431 # las dos inalcanzables desde acá. La variante del corpus va con `-Dpolkit=disabled` —polkit en
2432 # upower sólo gobierna suspender/hibernar, que además está deprecado; reportar carga y tiempo
2433 # restante no pasa por ahí— y con eso la cadena cierra sin meter un polkit de verdad al corpus.
2434 "upower",
2435]
2436
2437# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
2438# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
2439# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
2440# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
2441# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
2442#
2443# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
2444# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
2445# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
2446# `perfiles` de los cinco `build-state*.json`, no a ojo.
2447#
2448# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
2449# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
2450#
2451# ⚠ AUSENCIA DESTAPADA POR EL CRUCE: **no hay `upowerd`**. COSMIC dibuja un indicador de batería y
2452# el demonio que lo alimenta no está en la clausura ⇒ en un portátil el applet no tiene datos.
2453# Deuda del perfil, no de esta lista.
2454servicios = [
2455 "dbus-system", "logind-compat", "cupsd", "bluetoothd",
2456 "pipewire", "pipewire-pulse", "wireplumber",
2457 "NetworkManager", # 2026-09-13
2458 "upowerd", # 2026-09-13: el indicador de batería deja de mirar al vacío
2459]
2460
2461[perfil.escritorio-sway]
2462descripcion = "WM Wayland ligero: sway sobre wlroots propio, sin X11 y sin systemd"
2463cola = "incoming-wlr"
2464hereda = ["cli"]
2465# Sellado entero el 2026-08-07. A diferencia de los otros tres escritorios, éste NO tiene una torre
2466# de C debajo: son una decena de binarios pequeños sobre wlroots, y por eso salió en una noche
2467# mientras KDE y GNOME fueron campañas de semanas.
2468#
2469# `hereda = ["cli"]` a propósito: un WM ligero sin userland debajo no se usa: hace falta la terminal
2470# y las herramientas.
2471#
2472# 2026-08-26: este comentario decía que `foot` «entra por la clausura de cli, así que no se lista
2473# acá». ERA FALSO y el grafo lo desmentía: `foot` está sellado en el corpus pero `perfil.cli` no lo
2474# lista entre sus raíces, así que ningún perfil lo arrastraba. El perfil daba 121/121 SIN EMULADOR DE
2475# TERMINAL — sellado completo y aun así inusable. Se detectó al hidratar la clausura para armar la
2476# imagen, no antes: la métrica de perfil mide la clausura de las raíces declaradas, y no puede ver lo
2477# que falta en la declaración. `foot` se lista ahora donde le toca, entre las raíces del escritorio.
2478#
2479# Sólo RAÍCES: la clausura —wlroots, mesa, wayland, libinput, seatd, cairo, pango, fcft…— la calcula
2480# el grafo siguiendo las aristas de deps. Escribirla a mano sería justo lo que targets.toml existe
2481# para no hacer.
2482paquetes = [
2483 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
2484 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
2485 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
2486 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
2487 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
2488 # lecturas del modelo de composición.
2489 "desktop-file-utils",
2490 # el compositor y sus utilidades propias
2491 "sway",
2492 # lo que hace que se USE y no sólo arranque
2493 "yambar", # barra de estado (yambar y no waybar: waybar exige gtkmm-3.0, o sea GTK3 + sus
2494 # bindings C++, que es deuda aparcada por diseño. Medido, no supuesto.)
2495 "foot", # emulador de terminal (misma familia que fcft/tllist, ya en la clausura)
2496 "fuzzel", # lanzador de aplicaciones
2497 # ── DATOS, no binarios: nadie los declara como dep de build, así que SÓLO entran como raíces ──
2498 # Ninguna receta depende de una fuente ni de un mapa de teclado para COMPILAR, de modo que la
2499 # clausura no puede alcanzarlos por más aristas que siga. Si no están acá, no están.
2500 "dejavu-fonts", # sin UNA fuente, foot muere con «monospace::size=8: failed to match font»
2501 # ── LAS FUENTES DEL MUNDO (2026-10-19) ───────────────────────────────────────────────────────
2502 # `dejavu-fonts` cubre latino, griego y cirílico, y era LO ÚNICO que tenía la distro: medido con
2503 # `scripts/censo-distro.py`, chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés,
2504 # hebreo y los emoji salían como cuadraditos — con el tablero entero en verde, porque ninguna
2505 # otra métrica mide «¿alcanza para usarla?».
2506 #
2507 # Las tres hacen falta y ninguna sustituye a otra: son tres repositorios distintos de upstream.
2508 # 144 MB en total por imagen, y es el precio de no dejar afuera a la mayor parte de la humanidad.
2509 # noto-fonts 48 MB Regular+Bold hinted de TODAS las familias (321 ficheros)
2510 # noto-fonts-cjk 86 MB 2 .ttc; cada uno trae zh-Hans, zh-Hant, ja y ko a la vez
2511 # noto-fonts-emoji 10 MB NotoColorEmoji — fontconfig ya la prefiere de fábrica, sin `.conf`
2512 "noto-fonts", "noto-fonts-cjk", "noto-fonts-emoji",
2513 # ── ESCRIBIR, NO SÓLO LEER (2026-10-20) ──────────────────────────────────────────────────────
2514 # Las fuentes Noto del 19-oct hicieron que la distro LEA chino, japonés, coreano, árabe e hindi.
2515 # Esto es lo que falta para ESCRIBIRLOS: un teclado tiene cien teclas y esos sistemas no se
2516 # teclean uno-a-uno. `censo-distro.py` marcaba la categoría «escribir en esos idiomas» 5 de 6.
2517 #
2518 # Entran los TRES JUNTOS y el orden del razonamiento importa:
2519 # `fcitx5` el marco: daemon, protocolo y barra de candidatos. Habla
2520 # `zwp_text_input_v3` / `zwp_input_method_v2`, que `mirada-compositor` registra
2521 # (`setup.rs:600-602`) — sin eso ningún IME serviría en esta distro.
2522 # `fcitx5-hangul` el MOTOR coreano. Sin un motor, el marco es una barra que no convierte nada:
2523 # la figura de `crun` sin podman, y la razón por la que fcitx5 se construyó el
2524 # 19-oct y NO se declaró hasta hoy.
2525 # `libhangul` ⚠ y no es sólo dep de build. Su código va ESTÁTICO dentro del addon, pero el
2526 # diccionario hanja NO: la ruta `/usr/share/libhangul/hanja/hanja.txt` está
2527 # HORNEADA en la `.a` (medido con `strings`), así que sin este paquete en la
2528 # imagen la conversión hanja busca un fichero que no existe. Es la figura de
2529 # «un directorio vacío como sonda» por el otro lado: el código está, el dato no.
2530 #
2531 # ⚠ EL COREANO PRIMERO POR COSTE MEDIDO, no por preferencia: deps nuevas por motor —coreano 1
2532 # (`libhangul`), japonés 2 (`anthy` + `fcitx5-qt`), chino 6 (`libime`, `libpinyin`, `opencc`,
2533 # `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**). El chino además necesita que Qt salga de
2534 # `incoming-kde` al corpus. Los otros dos son campañas con su propio presupuesto.
2535 #
2536 # ⚠ PESO: 294 MB (fcitx5 284 + libhangul 6,6 + el motor 3,3). Y **el 87 % de los binarios de
2537 # fcitx5 son secciones `.debug_*`** —294 MB de 334, medido con readelf—, así que desnudado serían
2538 # ~40 MB. No es una anomalía suya: `foot` y `mpv` también van sin desnudar, es la norma del corpus
2539 # y su sitio es la campaña del split de debug (SDD 23), no esta línea.
2540 # Y el JAPONÉS (2026-10-20), por el mismo camino y a la primera: `anthy-unicode` convierte kana a
2541 # kanji y `fcitx5-anthy` lo enchufa al marco. ⚠ Costó UNA receta, no dos — en la nota de arriba
2542 # escribí «japonés 2 (anthy + fcitx5-qt)» leyendo los makedepends de Alpine, y su CMakeLists no
2543 # menciona Qt ni una vez. `anthy-unicode` viaja por su DICCIONARIO (4 ficheros), igual que
2544 # `libhangul` por el hanja: sin los datos el motor carga y no convierte nada.
2545 "fcitx5", "fcitx5-hangul", "libhangul", "fcitx5-anthy", "anthy-unicode",
2546 # Y el CHINO (2026-10-20), que cierra los tres grandes. ⚠ **La estimación de la nota de arriba era
2547 # FALSA, y es la TERCERA vez con el mismo defecto**: decía «chino 6 (`libime`, `libpinyin`,
2548 # `opencc`, `fcitx5-lua`, `fcitx5-qt` y **qt6-qtwebengine**)» y que haría falta sacar Qt de
2549 # `incoming-kde` al corpus. Medido sobre los `CMakeLists` y no sobre los `makedepends` de Alpine:
2550 # **cero Qt, cero libpinyin, cero fcitx5-lua, cero qtwebengine.** Qt sólo aparecía detrás de
2551 # `ENABLE_GUI`, que es la ventana de configuración y se apaga; el navegador embebido, detrás de
2552 # `ENABLE_BROWSER`. Mismo error que ya está anotado dos párrafos arriba para el japonés.
2553 #
2554 # ⚠ **`libime` NO se declara acá, y eso es medido, no un olvido.** Se construye con
2555 # `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias de libc++), así que entra ENTERA
2556 # dentro de los addons: comprobado con `readelf -d` que **ningún** `.so` de
2557 # `fcitx5-chinese-addons` declara `libIME*`. Declararla metería 159 MB en cada imagen para nada.
2558 # Lo que sí viaja son sus DATOS, que el addon busca en ejecución.
2559 #
2560 # `fcitx5-chinese-addons` pinyin + tabla + helper de trazos + chttrans + puntuación +
2561 # ancho completo. 6 addons
2562 # `opencc` simplificado ↔ tradicional. Viaja porque `libchttrans.so` enlaza
2563 # `libopencc.so` de verdad (lo exige el guardián): sin él la mitad del
2564 # chino escrito queda afuera, y no con un error sino en silencio
2565 # `libime-lm` el modelo de lenguaje (`zh_CN.lm`, 35 MB). Sin esto el pinyin carga
2566 # y **no ordena candidatos**
2567 # `libime-dict` el diccionario de pinyin + la Extensión B de Unicode
2568 # `libime-table` las 8 tablas (wubi, cangjie, erbi…): quien escribe por FORMA y no
2569 # por pronunciación. Sin ellas el método por tabla existe y no carga nada
2570 # `fcitx5-pinyin-stroke` buscar un carácter por sus TRAZOS — el caso de quien no sabe cómo se
2571 # `fcitx5-pinyin-table` pronuncia, que es justo cuando un método por pinyin no sirve
2572 #
2573 # ⚠ PESO: 189 MB los siete. El gordo es el addon (105) y el modelo (35), y **el 90 % de libime es
2574 # `.debug_*`** (143 de 159 MB, sumado con readelf) — la norma del corpus, cuyo sitio es el SDD 23.
2575 #
2576 # ⚠ QUEDA AFUERA Y SE DICE: `ENABLE_GUI=Off` ⇒ **no hay ventana de configuración** (es Qt y no
2577 # tenemos `fcitx5-qt`), así que elegir diccionarios o disposición se hace editando ficheros. Y
2578 # `ENABLE_CLOUDPINYIN=Off` es DECISIÓN y no falta de dep: manda lo que teclea el usuario a un
2579 # servidor remoto.
2580 "fcitx5-chinese-addons", "opencc", "libime-lm", "libime-dict", "libime-table",
2581 "fcitx5-pinyin-stroke", "fcitx5-pinyin-table",
2582 # ── LA MÁQUINA HABLA (2026-10-20) ────────────────────────────────────────────────────────────
2583 # `censo-distro.py` marcaba «accesibilidad» **4 de 4 ausente**, y es el hueco que deja gente
2584 # afuera de verdad: sin voz, una distro no es incómoda para quien no ve — es INUSABLE. Y ninguna
2585 # otra métrica del repo lo ve: el tablero puede estar en 1158/1158 y clausura 10 de 10 mientras la
2586 # máquina es muda.
2587 #
2588 # `espeak-ng` son **21 MB con 114 diccionarios de idioma**, por síntesis formante: sin modelos, sin
2589 # red y sin GPU. Instala cuatro nombres (`espeak-ng`, `espeak`, `speak-ng`, `speak`) porque los
2590 # scripts viejos invocan los dos primeros.
2591 #
2592 # Los tres van juntos y el orden es el de la cadena:
2593 # `espeak-ng` el sintetizador y su `libespeak-ng.so` (lo que `orca` cargaría con dlopen)
2594 # `pcaudiolib` la salida de audio. ⚠ Sin ella espeak-ng **compila igual** y sintetiza a `.wav`
2595 # sin reproducir: `espeak-ng "hola"` no haría ruido. Verificado en el artefacto —
2596 # la `.so` trae libpulse/libasound en NEEDED y exporta los `audio_object_*`
2597 # `sonic` cambia la velocidad sin cambiar el tono. ⚠ No es opcional aunque su
2598 # `find_library` no lleve REQUIRED: si no la encuentra, espeak-ng la CLONA de git
2599 # en tiempo de build, y el sandbox no tiene red
2600 #
2601 # ⚠ Lo que esto NO cierra: **`orca`**, el lector de pantalla, necesita `at-spi2-core` y
2602 # `py3-gobject`, los dos sólo en `incoming-gnome` — la misma pila de introspección que bloqueó a
2603 # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
2604 # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
2605 "espeak-ng", "pcaudiolib", "sonic",
2606 # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
2607 # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
2608 # distinto que las otras no tapan:
2609 #
2610 # `speech-dispatcher` el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
2611 # `dotconf` sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
2612 # Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
2613 # Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
2614 # detrás como módulos. `dotconf` es su parser de configuración, la única
2615 # dep que faltaba
2616 # `brltty` los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
2617 # de gusto a la voz: braille es lo que permite leer código, ortografía y
2618 # matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
2619 # además no oye. Con voz y sin braille se deja afuera a sordociegos por
2620 # completo
2621 # `liblouis` braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
2622 # carácter: legible, tres veces más largo, y nadie lee así un texto
2623 #
2624 # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
2625 #
2626 # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
2627 # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
2628 # bloqueada por DOS cosas distintas:
2629 # · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
2630 # dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
2631 # rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
2632 # Es una decisión de operador, no algo que se hace de paso
2633 # · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
2634 # en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
2635 #
2636 # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
2637 # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
2638 # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
2639 # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
2640 # pendiente y no un olvido.
2641 "speech-dispatcher", "dotconf", "brltty", "liblouis",
2642 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
2643 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
2644 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
2645 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
2646 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
2647 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
2648 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
2649 "dejavu-fonts-nerd",
2650 "xkeyboard-config", # sin datos XKB: «xkbcommon: ERROR: failed to add default include path
2651 # /usr/share/X11/xkb», y sway no completa el seat
2652 # Cursores. Mismo caso que las fuentes y el XKB, y con el mismo modo de fallo silencioso: con el
2653 # cursor por software el compositor dibuja lo que le da el TEMA, y sin tema el puntero se mueve
2654 # invisible. Este perfil llegó a 173/173 sin uno. Trae además `/usr/share/icons/default`, que es
2655 # lo que buscan libXcursor y wlroots cuando nadie fijó `XCURSOR_THEME` — o sea, siempre acá.
2656 "adwaita-cursors",
2657 # ── LAS TRES `.so` QUE sway PIDE Y EL CORPUS SÓLO PRODUCE COMO `.a` ─────────────────────────
2658 # `sway` es `link = "dynamic"` a propósito (dlopea los drivers DRI de mesa) y sale con
2659 # `NEEDED libz.so.1 / libexpat.so.1 / libffi.so.8`. Las canónicas son `--disable-shared`, así que
2660 # ningún artefacto sellado producía esos `.so` y el rootfs hidratado los resolvía contra el
2661 # sysroot Alpine DEL LAB. Peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la
2662 # fuga era invisible para el store — el artefacto se daba por bueno y sólo arrancaba en una
2663 # máquina con Alpine debajo. Los otros nueve NEEDED de sway (pixman, drm, evdev, input, udev,
2664 # wayland-server, wlroots, xkbcommon, libc) sí los cubre la clausura, porque esas recetas ya son
2665 # `link = dynamic`. Como las tres son data de runtime y nadie las declara como dep de BUILD, van
2666 # de raíces por la misma razón que las fuentes y el XKB de arriba.
2667 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
2668 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
2669 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
2670 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
2671 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
2672 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
2673 # partes ⇒ un solo artefacto, cero rebuilds).
2674 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
2675 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
2676 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
2677 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
2678 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
2679 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
2680 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
2681 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
2682 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
2683 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
2684 "zlib-shared", "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
2685 "swaybg", # fondo de escritorio
2686 "swaylock", # bloqueo de pantalla (con PAM: linux-pam SÍ está en el corpus)
2687 "swayidle", # apagado por inactividad
2688 # el par de captura y el portapapeles, sin los cuales media docena de flujos no cierran
2689 "grim", "slurp", "wl-clipboard",
2690 # `wtype` (2026-10-19): teclear en Wayland desde un script. Estaba SELLADO y en ningún perfil
2691 # —uno de los 12 que `censo-distro.py` destapó—. Va acá y no en `cli` porque es del PROTOCOLO,
2692 # no de la terminal: sin compositor Wayland no hace nada. 80 KB, el más barato de los doce.
2693 # Completa el juego con grim/slurp/wl-clipboard: capturar, pegar y ESCRIBIR; sin él un script
2694 # que automatiza una app gráfica no puede entregarle texto.
2695 "wtype",
2696 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
2697 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
2698 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
2699 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
2700 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
2701 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
2702 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
2703 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
2704 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
2705 "mpv",
2706 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
2707 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
2708 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
2709 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
2710 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
2711 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
2712 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
2713 "atuq",
2714 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
2715 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
2716 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
2717 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
2718 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
2719 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
2720 # así que la imagen necesita además a alguien escuchando ese nombre.
2721 "libnotify",
2722 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
2723 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
2724 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
2725 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
2726 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
2727 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
2728 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
2729 "llama-cpp",
2730 "ia-modelo-chat",
2731 # ── LA BÓVEDA DEL NAVEGADOR (SDD 26 §7.sexies, decisión 1 — 2026-09-21) ──────────────────────
2732 # `atuq` trae la décima extensión desde el 2026-09-15 y el host pineado atiende `vault.*` desde
2733 # el 2026-09-16; aun así la función venía **APAGADA DE FÁBRICA en las cuatro imágenes**, sin un
2734 # solo error: `vault.status` contesta `{"ok":true,"locked":true}` porque el host NUNCA abre la
2735 # base (sled toma lock exclusivo y el proceso que lanza Gecko muere y revive con cada pestaña);
2736 # le habla a un DUEÑO por un socket, y el dueño no estaba declarado en ningún perfil. Las dos
2737 # recetas estaban selladas desde el 2026-09-18 con `perfiles: []` — sellado ≠ instalado, que es
2738 # la lección de `foot`, escrita QUINCE veces en este fichero antes de hoy (`grep 'lección de
2739 # .foot.'`) y aun así vuelta a pasar. Y «cerrada» es una respuesta EXITOSA:
2740 # ni un log, ni un reintento, ni la insignia la distinguen de un usuario que no desbloqueó la suya.
2741 # `boveda` la app DUEÑA de la base: la abre para su ventana y levanta el socket del
2742 # navegador en un hilo. Sin ella, `vault.match` no ofrece nada, nunca.
2743 # `shuma-pregunta` el diálogo de consentimiento que `PorDialogo` lanza por PATH. Sin él,
2744 # `Command::new` falla ⇒ TODO `vault.fill` se deniega, indistinguible de que
2745 # la persona haya dicho que no.
2746 # **Las dos o ninguna**: media bóveda es una que niega todo en silencio. Cuestan ~43 M por imagen
2747 # (22 M + 21 M medidos sobre los artefactos sellados), contra los ~1,25 GiB del §6.7 de acá arriba.
2748 # ⚠ La respuesta fue NO hasta el 2026-09-18, y no por el tamaño: ninguna app llimphi de escritorio
2749 # podía abrir ventana sin Vulkan (§7.septies), así que declararlas habría sido instalar una bóveda
2750 # que niega todo. Eso se arregló en llimphi y se MIDIÓ en metal: las seis etapas de
2751 # `scripts/test-atuq-boveda-metal.py` en verde —con el navegador de verdad, el diálogo a la vista
2752 # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes.
2753 "boveda",
2754 "shuma-pregunta",
2755 # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21)
2756 # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se
2757 # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que
2758 # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo
2759 # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de
2760 # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además
2761 # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las
2762 # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo:
2763 # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y
2764 # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual
2765 # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa.
2766 # `agora-cli` el sembrador: `identity new --name <nombre>` crea la identidad y `unlock` la
2767 # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado
2768 # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene
2769 # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá.
2770 # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va
2771 # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la
2772 # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta
2773 # «autenticación fallida» y NO re-siembra.
2774 # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide
2775 # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA,
2776 # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque
2777 # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como
2778 # «la que siembra el login» (comentario de `pacha-secretos`).
2779 # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN
2780 # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow`
2781 # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no
2782 # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos
2783 # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le
2784 # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos
2785 # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de
2786 # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker.
2787 "agora-cli",
2788 # `dunst` es LA OTRA MITAD, y sólo hace falta acá: KDE la atiende con plasma-workspace, GNOME con
2789 # gnome-shell y COSMIC con cosmic-notifications; sway no tenía a NADIE escuchando
2790 # `org.freedesktop.Notifications`, así que una página que pedía notificar mandaba el mensaje al bus
2791 # y se lo comía el silencio. Con el `.service` de D-Bus que trae la receta, el daemon lo levanta el
2792 # propio bus la primera vez que alguien pide ese nombre — sin systemd y sin acordarse de lanzarlo
2793 # en la config de sway.
2794 # ⚠ No es `mako` —que es lo que usa media casa wlroots— porque ese nombre YA ESTÁ OCUPADO en el
2795 # corpus por el motor de plantillas de Python de mesa: dos artefactos homónimos en la misma imagen
2796 # es la colisión que la granja ya nos cobró. El porqué largo está en la receta.
2797 "dunst",
2798 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
2799 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
2800 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
2801 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
2802 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
2803 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
2804 "swayimg",
2805 # ── Y SE PUEDE SUBIR EL VOLUMEN (2026-10-20) ────────────────────────────────────────────────
2806 # El censo marcaba «audio de escritorio» 5 de 5 ausente, con el problema exacto: «PipeWire suena y
2807 # no hay con qué subirle el volumen desde una ventana». O sea que la distro reproducía audio y
2808 # **no tenía ningún control de volumen** — ni gráfico, ni de consola, ni un atajo de teclado
2809 # posible, porque un atajo llama a un comando y el comando no existía.
2810 #
2811 # `pamixer` sube, baja, silencia y lista fuentes y sumideros: es lo que un compositor engancha a
2812 # las teclas de volumen. Su `cxxopts` NO se declara — es sólo una cabecera y no deja nada que viaje.
2813 #
2814 # ⚠ Habla el protocolo de PulseAudio y eso NO es un desajuste: `pipewire` publica esa capa
2815 # (`pipewire-pulse` + `libpipewire-module-protocol-pulse.so`, verificado en el artefacto) y la
2816 # biblioteca cliente `libpulse.so.0` la publica `pulseaudio`. Las dos ya viajan en estos cinco
2817 # perfiles, igual que `libsndfile`, así que esto cuesta sólo sus 7,5 MB.
2818 #
2819 # ⚠ Su cadena de runtime baja TRES niveles y es lo que hizo fallar el primer build:
2820 # `pamixer → libpulse.so → libpulsecommon.so → libsndfile.so.1`. Está documentada igual en
2821 # `recipes/espeak-ng.toml` — el sandbox sólo monta lo declarado, y yo no la consulté.
2822 "pamixer",
2823 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
2824 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
2825 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
2826 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
2827 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
2828 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
2829 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
2830 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
2831 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
2832 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
2833 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
2834 "zathura", "zathura-pdf-poppler",
2835 # ── GRABADOR DE PANTALLA — sólo acá, y la razón es de PROTOCOLO, no de gusto ──────────────────
2836 # `wf-recorder` captura por `wlr-screencopy`, que implementan los compositores wlroots. KWin y
2837 # mutter NO lo implementan (usan el portal/ScreenCast), así que ponerlo en esas dos imágenes
2838 # sería enviar una herramienta que no puede funcionar ahí — deuda fantasma con forma de app.
2839 # En COSMIC habría que VERIFICAR si cosmic-comp expone el protocolo antes de listarlo.
2840 # Es la app que cobra la promoción de pipewire al corpus (2026-09-03): antes grababa muda.
2841 "wf-recorder",
2842 # ── EL TEMA DE ICONOS BASE ───────────────────────────────────────────────────────────────────
2843 # Añadido el 2026-09-03 por el barrido que destapó que KDE y GNOME corrían SIN tema de iconos.
2844 # Acá el agujero es más chico y conviene decir exactamente cuánto: los cuatro programas gráficos
2845 # de esta imagen —foot, swayimg, mpv, wf-recorder— no piden iconos de tema, y `zathura` es GTK4,
2846 # que lleva sus iconos de UI COMPILADOS en el recurso de la librería. O sea que acá no hay una
2847 # captura con la barra vacía como en las otras dos.
2848 # Lo que sí falta es el FALLBACK del estándar: sin `hicolor/index.theme` instalado, los iconos que
2849 # una aplicación instala en `usr/share/icons/hicolor/...` son invisibles para cualquier buscador,
2850 # porque ese directorio no cuenta como tema. Es el piso, y cuesta un artefacto de datos.
2851 # Un tema COMPLETO (Adwaita, Breeze) sería una decisión aparte y NO gratis: los dos viven en colas
2852 # hermanas —`incoming-gnome` e `incoming-kde`— y desde acá no se alcanza una cola hermana, así que
2853 # pedirlo es promover uno al corpus. Queda escrito para que se elija, no para que se descubra.
2854 "hicolor-icon-theme",
2855 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
2856 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
2857 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
2858 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
2859 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
2860 # una sola copia.
2861 #
2862 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
2863 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
2864 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
2865 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
2866 # recetas: son tres perfiles sin «enable» ninguno.
2867 #
2868 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
2869 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
2870 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
2871 "cups", "bluez",
2872 # `cliphist` (2026-09-12): historial de portapapeles. El portapapeles de Wayland muere con el
2873 # cliente que lo ofrece —cerrás la terminal y lo copiado se va—; esto lo persiste. Va SÓLO en
2874 # este perfil y en COSMIC: habla `wlr-data-control`, que KWin y mutter NO implementan (mismo
2875 # muro que `wf-recorder`). Declararlo donde no funciona sería peor que no tenerlo.
2876 "cliphist",
2877 # `wlr-randr` (2026-09-13, barrido): sway no tenía CON QUÉ cambiar la resolución ni orientar una
2878 # pantalla. `swaymsg output` sirve si el compositor está vivo y configurado; esto es la
2879 # herramienta equivalente a xrandr para wlroots, y era otra hoja sellada sin perfil.
2880 "wlr-randr",
2881 # `wireplumber` (2026-09-13): **el audio de esta imagen no encaminaba nada.** `pipewire` está y
2882 # arranca, pero se construyó con `-Dsession-managers=[]`: sin gestor de sesión no hay política de
2883 # qué es entrada, qué es salida ni qué se enlaza con qué — el sink que ve la aplicación es
2884 # `auto_null`, o sea el nodo de descarte. Audio que parece vivo y no suena.
2885 # Vive en el CORPUS —y no en esta cola— porque desde acá no se alcanza una cola hermana: las
2886 # copias de `incoming-gnome` e `incoming-cosmic` eran invisibles para este perfil. Ver su receta:
2887 # hay tres variantes vivas a propósito y consolidarlas es el trabajo siguiente.
2888 "wireplumber",
2889 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
2890 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
2891 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
2892 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
2893 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
2894 # ⚠ NO declarar esto en `escritorio-kde`: allá `plasma-nm` ya arrastra la variante de la cola, y
2895 # dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib.
2896 "networkmanager",
2897]
2898
2899# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
2900# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
2901# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
2902# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
2903# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
2904#
2905# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
2906# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
2907# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
2908# `perfiles` de los cinco `build-state*.json`, no a ojo.
2909#
2910# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
2911# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
2912#
2913# ⚠ MISMA AUSENCIA QUE KDE: **no hay `wireplumber`**. `pipewire` sin gestor de sesión no encamina
2914# nada. Y tampoco hay `upowerd` ni `logind-compat` — en un WM ligero eso es defendible (seatd hace
2915# el seat y `yambar` puede leer la batería de sysfs), pero es una decisión, no un hecho dado.
2916servicios = [
2917 "dbus-system", "cupsd", "bluetoothd",
2918 "pipewire", "pipewire-pulse",
2919 # 2026-09-13: los dos que se destrabaron promoviendo al corpus lo que vivía en colas hermanas.
2920 "wireplumber", "NetworkManager",
2921]
2922
2923[perfil.servidor]
2924descripcion = "la caja que SIRVE: sshd, servidor web y las herramientas de operar una máquina remota sin pantalla"
2925hereda = ["cli"]
2926# Nace el 2026-09-09 del frente «servidor de producción» (SDD 27). Existe porque las cuatro cosas que
2927# ese frente quiere —instalar takana puro en una caja remota, servir el repo, que la caja se actualice
2928# a sí misma, y migrar gioser— empiezan todas por la misma pregunta: QUÉ se instala. Hasta hoy eso no
2929# estaba declarado en ningún lado. El `sshd` del producto, por ejemplo, entra por
2930# `product-userland-from-repo.sh` y NO por este manifiesto: dos listas para lo mismo y una sola
2931# versionada como destino, que es exactamente lo que este fichero existe para no tener.
2932#
2933# ⚠ LA LECCIÓN DE `foot`, APLICADA ANTES DE PAGARLA: `escritorio-sway` daba 121/121 sellado y era
2934# inusable porque a la declaración le faltaba el emulador de terminal. La métrica mide la clausura de
2935# lo DECLARADO y no puede ver lo que falta en la declaración. Un servidor tiene su propia versión de
2936# ese agujero, y no es el software de servir: es la HORA (sin reloj, TLS y las firmas fallan), el
2937# CORTAFUEGOS y el DISPARADOR PERIÓDICO. Por eso van declarados abajo aunque todavía no existan.
2938#
2939# ⚠ LAS CUATRO ÚLTIMAS RAÍCES NO RESUELVEN A NINGUNA RECETA — a propósito, y hay que saberlo.
2940# Quedan `wanted` en el grafo, y `wanted` NO es `debt`: `drenaje.json` seguirá diciendo `deuda=0`
2941# ([[gnome-sin-fuentes]] trampa 1). Se declaran igual porque una deuda escrita y contada se ve, y una
2942# omitida no: un `perfil.servidor` sin ellas saldría N/N —completo y verde— describiendo un servidor
2943# sin hora, sin cortafuegos y sin latido. Cuenta esperada al escribirlas: **5 `wanted`**. Si al mirar
2944# `build-state.json` aparece un sexto, es un error de tipeo en un nombre, no una receta nueva.
2945paquetes = [
2946 # ── BAJAR PRIVILEGIOS (2026-10-22) ───────────────────────────────────────────────────────────
2947 # `setuidgid` lo invocan las cards de servicio de este perfil por ruta absoluta: lo necesitan
2948 # `gitea`, `minga`, `pacha`, `pacha-secretos` y `shuma-daemon`, porque esos daemons se niegan a
2949 # correr como root y arje-zero encarna sus cards COMO root. Hasta hoy lo daba el applet de
2950 # busybox y era el ÚLTIMO sin dueño propio; ahora es un binario de 330 KB, estático y sólo libc.
2951 "setuidgid",
2952 # ── lo que existe y está sellado ──────────────────────────────────────────────────────────────
2953 # `openssh`: el acceso. Es la raíz que convierte «la caja arrancó» en «puedo entrar»; sin ella una
2954 # instalación remota deja una máquina viva e inalcanzable, que es peor que una que no arrancó.
2955 "openssh",
2956 # `caddy`: sirve `dist/repo` (índice firmado + los `.tkn`/`.swm`). TLS automático, un binario, cero
2957 # dependencias — y es el mismo que ya sirve los 19 dominios de gioser, así que la migración no
2958 # cambia de servidor web además de cambiar de sistema.
2959 "caddy",
2960 # `gitea` (2026-09-14): el servidor git que hoy corre en gioser y que la mudanza tiene que levantar
2961 # del otro lado. Entra al perfil porque es el PASO 1 del plan de mudanza visto por el lado bueno:
2962 # los 26 repos que sólo existen en la máquina que se borra dejan de estar solos en cuanto su origen
2963 # vive acá. Sus datos son 2,0 G de ficheros y una sqlite (`DB_TYPE = sqlite3`), así que mudarlos es
2964 # copiar un directorio — lo que NO es trivial es el binario, ver el encabezado de `recipes/gitea.toml`.
2965 # Su `[[service]]` está en la receta y el perfil lo arranca (abajo, en `servicios`).
2966 "gitea",
2967 # `shuma-daemon` / `shuma-gateway` (2026-09-16): la consola remota que sirve `/shuma/*` en
2968 # `sergio.gioser.net`. Construidos desde el monorepo de tawasuyu por decisión del usuario, en vez
2969 # de mudar el binario glibc de gioser —cuyo inodo, además, ya estaba BORRADO del disco.
2970 "shuma-daemon", "shuma-gateway",
2971 # ── los daemons propios del usuario (2026-09-16) ──────────────────────────────────────────────
2972 # Decisión suya: «viven todos los tawasuyu». Se construyen desde el monorepo, no se copian — tres
2973 # de ellos corrían en gioser desde un INODO BORRADO. Entran al perfil como PAQUETES todos; cuáles
2974 # ARRANCAN solos es otra pregunta y está más abajo, en `servicios`.
2975 "matilda", "tupu", "sandokan-watch", "pacha", "pacha-secretos",
2976 "willay-daemon", "willay-crosscheck", "tejido", "thasnuna",
2977 # ── ⚠ NO, `agora-cli` NO VA ACÁ — y la tentación es fuerte (SDD 26 §7.terdecies, 2026-09-21) ──
2978 # Los cuatro perfiles de ESCRITORIO declaran `agora-cli` desde el §7.duodecies, porque es el único
2979 # binario del corpus que siembra `pacha_llavero::SEED_IDENTIDAD`. Y `pacha` y `pacha-secretos`, que
2980 # están acá arriba, leen esa misma seed: sin ella el Secret Service corre en modo memoria —los
2981 # secretos NO sobreviven al reinicio— y `pacha dotfiles` no cifra. Parece el mismo hueco y parece
2982 # que se cierra igual. **No se cierra**, y copiar la decisión del escritorio dejaría la función
2983 # PARECIENDO arreglada, que es peor que el hueco.
2984 #
2985 # Lo medido, que es la premisa y no la conclusión:
2986 # scripts/targets.py --service-paths servidor ⇒ incluye recipes/pacha-secretos.toml
2987 # takana service-cards recipes/pacha-secretos.toml ⇒ Card scope=system, "lifecycle":"daemon"
2988 # o sea que la Card entra al `genesis` y **arje arranca el daemon EN EL ARRANQUE**, con
2989 # `setuidgid pacha`. De ahí se sigue, sin medir ningún llavero:
2990 # 1. en el arranque no hay nadie logueado ⇒ no hay seed que sembrar todavía: un sembrador en la
2991 # imagen no llega a tiempo, por construcción;
2992 # 2. `abrir_almacen()` corre UNA sola vez en `main()` y el resultado queda en `Estado`: aunque el
2993 # operador desbloquee después, el daemon ya decidió y no vuelve a mirar;
2994 # 3. y `agora-cli unlock` en una sesión ssh siembra en el llavero de ESA sesión, no en el del
2995 # daemon (`KEY_SPEC_SESSION_KEYRING` es de sesión por diseño).
2996 # En el escritorio el sembrador sirve porque la persona está delante; acá no hay persona en el
2997 # momento que importa. **Misma raíz, dos respuestas.**
2998 #
2999 # Lo que sí lo cerraría, nombrado y NO decidido —`pacha-llavero` ya lo tiene previsto: «kernel hoy;
3000 # mañana PAM/TPM/passphrase+Argon2/greeter»—: un backend de llavero que un daemon pueda usar al
3001 # arrancar (fichero con permisos recortados, TPM sellado), o que el daemon vuelva a mirar en vez de
3002 # decidir una vez. Hasta entonces el almacén es efímero —lo que su propia receta ya decía— y desde
3003 # el pin `cd9acd0d9` al menos lo DICE con el motivo correcto y lo publica por D-Bus (`Reason`, al
3004 # lado de `Persistent`).
3005 # `curl`/`wget`: bajar del mirror y del repo. `takana install --repo https://…` los necesita del
3006 # lado del cliente, y son también la única forma de diagnosticar un origen caído desde la caja.
3007 "curl", "wget",
3008 # `tmux`: un build largo por SSH que muere con la sesión es un build perdido. En una caja sin
3009 # pantalla es infraestructura, no comodidad.
3010 "tmux",
3011 # `arjectl` (2026-09-14): el control en caliente del init. Sin esto una caja sólo se administra
3012 # REINICIÁNDOLA, y se descubrió pagándolo: en la primera imagen con gitea, la card falló por falta
3013 # de config, el backoff de `restart` se agotó y no había forma de pedirle a PID 1 que reintentara
3014 # (SDD 28 §6.11). `status`, `list-units`, `start`, `stop`, `restart` sobre el bus de arje.
3015 # ⚠ Va acá y NO en `base` sólo porque este frente es el que lo pagó; el argumento para subirlo es
3016 # fuerte —TODA imagen de takana corre arje-zero como PID 1 y ninguna se puede operar sin él— y es
3017 # una línea. Se deja como decisión a tomar, no como olvido.
3018 "arjectl",
3019 # `minga` (2026-09-14): **la ventana oficial al mundo**. Decisión del usuario: el árbol del código
3020 # y la cola de compilación pasan a minga, y git queda como espejo de compatibilidad. Va con su
3021 # daemon P2P escuchando (`servicios`, abajo) — no es un CLI suelto, es el punto de entrada.
3022 # ⚠ Su card NO genera la identidad: el keypair lo crea el usuario con `minga init` y la guarda
3023 # sale 78 si el repo no existe. Una caja que se inventa su identidad soberana al arrancar es peor
3024 # que una caja sin peer.
3025 "minga",
3026 # `squid` (2026-09-15): el proxy de salida de gioser, que es lo que la mudanza tiene que PRODUCIR
3027 # como receta y no sólo mover (§6.2). Es un servicio con gente encima —tres usuarios autenticados
3028 # y 40 IPs en la ACL— y hasta hoy corría sobre binarios de Artix que nadie sabría reconstruir.
3029 # Su config (el `squid.conf`, el `passwd` y las dos ACL) es dato del SITIO y viaja con la mudanza,
3030 # no con el paquete: el servicio arranca sólo si está, y si no sale 78 diciéndolo.
3031 "squid",
3032 # ── EL COMPILADOR DE RUST, QUE DESDE HOY ES NUESTRO (2026-09-16, SDD 31) ─────────────────────
3033 # Acá vivía `rust-toolchain-bin`: el tarball oficial de rust-lang, 799 M de bytes AJENOS sellados
3034 # tal cual (`foreign`). Era el escalón 1 —desbloquear hoy— y cumplió: con él se construyó el
3035 # escalón 2, y el escalón 2 ya compila, enlaza y corre. Sale del perfil.
3036 #
3037 # Los tres que entran son el mismo hecho —«esta caja compila Rust»— partido en tres piezas:
3038 # · `rust` el compilador y cargo, construidos por la granja desde fuente (353 M).
3039 # · `lld21` el enlazador. NO es opcional: una caja takana **no tiene `cc`**, y sin
3040 # enlazador `rustc` compila objetos y no produce un ejecutable.
3041 #
3042 # ⚠ Acá estuvo 20 minutos un tercer paquete, `cargo-config`, que publicaba `/etc/cargo/config.toml`
3043 # con las flags del enlazador. **Cargo NO LEE ESA RUTA** —sólo `$CARGO_HOME/config.toml`, los
3044 # `.cargo/config.toml` del proyecto y sus padres, y lo que se le pase con `--config`— así que el
3045 # paquete habría instalado un fichero que nadie abre. La prueba que lo destapó fue la única que
3046 # valía: `cargo build` en un proyecto nuevo, sin flags y sin rutas, en la caja. Y el arreglo no era
3047 # mover el fichero: **el que enlaza es `rustc`, y rustc no lee la config de cargo** ⇒ las flags van
3048 # DENTRO del compilador, en el spec del triple (`recipes/rust-alpine-target.patch`).
3049 #
3050 # ⚠ Va en `servidor` y NO en `base` por tamaño y por sentido: una imagen de escritorio no compila
3051 # nada; la caja que sirve el repo y construye, sí. Y sigue siendo el paso 2 de tres: el 3 es la
3052 # cadena mrustc del selfhost, que es lo único que quita la dependencia del LAB para arrancar.
3053 "rust", "lld21",
3054 # `netup` (2026-09-10): el cliente DHCP propio que levanta la red al arrancar. Entra como RAÍZ
3055 # aunque su binario YA venga dentro del `product-rootfs`, y por una razón concreta: el del producto
3056 # está congelado en el artefacto sellado del bootstrap, así que un arreglo de netup no llega nunca
3057 # a la imagen — pasó el mismo día con la ruta on-link que Hetzner necesita. Declarado acá, la
3058 # hidratación del perfil lo proyecta ENCIMA y la imagen lleva el vigente.
3059 "netup",
3060
3061 # ── declarado y NO existe todavía: la deuda del perfil, contada ───────────────────────────────
3062 # `takana`: el propio takana NO tiene receta. El corpus construye 869 recetas y no la suya, así que
3063 # hoy el binario sólo existe como `cargo build --release` sobre un clon del repo. Es EL bloqueante
3064 # de «que el servidor sirva sus paquetes a su propio host»: el host necesita `takana` instalado para
3065 # consumirlos, y no puede instalarlo desde el repo porque no está en el repo.
3066 "takana",
3067 # ── LA HORA: `ntpd-rs`, y `chrony` SALE (2026-09-22) ─────────────────────────────────────────
3068 # Sin NTP, un reloj que deriva rompe la validación de certificados TLS y la de firmas con
3069 # expiración — y falla con errores que no dicen «la hora», dicen «firma inválida». Eso no cambia;
3070 # lo que cambia es quién lo hace.
3071 #
3072 # `chrony` era la hoja C más barata de cambiar del corpus: 0 consumidores y declarada sólo acá, así
3073 # que tocarla no invalida a NADIE. Y lo que se gana es concreto: el demonio que ajusta el reloj del
3074 # sistema **como root** deja de ser C. `ntpd-rs` (Trifecta Tech / Tweede Golf) está en Debian y
3075 # Fedora y se escribió para este nicho exacto.
3076 #
3077 # ⚠ **Lo que NO se gana, dicho para que nadie lo lea al revés: NTS sigue apagado.** `chrony` lo
3078 # apagaba porque NTS necesita nettle o gnutls y ninguna está en el corpus; acá el muro es el mismo
3079 # un piso más arriba — el TLS de NTS lo hace `rustls`, que necesita un proveedor de criptografía
3080 # compilado (`aws-lc-rs` o `ring`) **y los dos son C**. El build va con
3081 # `--no-default-features --features rustcrypto`: la criptografía del PROTOCOLO (AES-SIV, MD5) sí es
3082 # pura-Rust, el NTS-KE queda sin proveedor. O sea: NTP clásico, igual que antes.
3083 #
3084 # `chrony` se queda en el CATÁLOGO, que no es la imagen — su receta documenta media docena de
3085 # lecciones sobre el `configure` que prueba el entorno, y borrarla sería tirar eso.
3086 "ntpd-rs",
3087 # `nftables`: el cortafuegos. `dev.gioser.net` está hoy expuesto a internet con fuerza bruta a root
3088 # en el journal; una caja nueva no debería nacer así.
3089 "nftables",
3090 # `cronie`: el disparador periódico. El latido de la granja son tres líneas de crontab, y en una
3091 # distro sin systemd no hay timers: o hay cron, o el latido se vuelve una tarjeta de arje-zero. Es
3092 # una decisión pendiente, y se declara para que se decida en vez de descubrirse.
3093 "cronie",
3094 # `logrotate`: en una caja que sirve, el log de acceso crece hasta llenar el disco. Caddy rota el
3095 # suyo por configuración, pero nada más lo hace.
3096 "logrotate",
3097 # `wireguard-tools` (2026-09-12): el kernel de esta distro YA trae WireGuard (SDD 22), y hasta hoy
3098 # no había con qué configurarlo — la caja podía hablar el protocolo y no había forma de decirle
3099 # con quién. Es la misma figura que el cortafuegos y el reloj de arriba: capacidad presente en el
3100 # kernel y ausente en el userland.
3101 "wireguard-tools",
3102 # ── OPERAR INFRA AJENA (2026-10-19) ──────────────────────────────────────────────────────────
3103 # Los dos estaban SELLADOS y en ningún perfil, de los 12 que destapó `censo-distro.py`.
3104 #
3105 # ⚠ VAN EN `servidor` Y NO EN `cli`, y es una decisión de PESO medida: `cli` lo heredan los
3106 # cuatro escritorios y `servidor`, así que declararlos allá metía **147 MB** (kubectl 60 +
3107 # helm 87) en cada imagen de escritorio para operar el Kubernetes de otra persona. Acá los usa
3108 # quien administra, que es quien los quiere.
3109 #
3110 # Y `kubectl` paga una deuda vieja que su propia receta nombra: el catálogo tenía `kubectl-neat`
3111 # y `kubectl-tree` SELLADOS e INERTES —son subcomandos que despacha `kubectl`, y `kubectl` no
3112 # estaba—. ⚠ Declarar kubectl no los activa por sí solo: los dos siguen fuera de todo perfil, y
3113 # eso es su propia línea de trabajo. Lo que cambia es que ahora hay dónde declararlos.
3114 "kubectl", "helm",
3115 # `valkey` (2026-10-19): la caché en memoria. El censo marcaba «bases de datos de servidor» 3/4
3116 # y su propia receta cuenta cómo se vio el hueco: `scripts/mudanza/planear.py` agrupa el software
3117 # del origen en familias y la familia «caché en memoria» salía SIN NADA — ni redis, ni valkey, ni
3118 # memcached—, o sea que un servidor de origen que use redis no tenía con qué reemplazarse.
3119 # Trae los `redis-*` además de los `valkey-*`, así que un cliente viejo no se entera.
3120 # ⚠ Sellado en el worker y traído al hub hoy, igual que `typst`.
3121 # ⚠ NO se habilita como servicio en este commit: levantar una caché en memoria por defecto es
3122 # otra decisión (memoria reservada, bind, persistencia) y va en `servicios`, que es el «enable»
3123 # de abajo. Declararlo en `paquetes` sólo lo INSTALA.
3124 "valkey",
3125]
3126
3127# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
3128# `paquetes` dice qué se INSTALA; esto dice qué se LEVANTA. Son dos hechos distintos y hasta hoy la
3129# distro sólo sabía escribir el primero: el `sshd` de la imagen del producto arrancaba porque su
3130# Card estaba escrita a mano en una constante de Rust (`takana_bootstrap::SSHD_SERVICE_CARD`), no
3131# porque alguien lo hubiera declarado en ningún sitio.
3132#
3133# Cada entrada es el `label` de un `[[service]]` declarado por una receta DEL PERFIL.
3134# `scripts/targets.py --servicios servidor` resuelve label → receta → exec, y además hace la
3135# comprobación INVERSA: avisa de los paquetes de la imagen que TRAEN un demonio que nadie arranca.
3136# Es la versión servicios de la lección de `foot` — la métrica mide la clausura de lo declarado y no
3137# puede ver lo que falta en la declaración. Comprobado antes de escribir esta línea: el resolutor
3138# avisaba «`openssh` está en la imagen y TRAE este servicio, pero el perfil no lo arranca».
3139# `gitea` (2026-09-14): el servidor git de la mudanza. Se habilita acá y el CÓMO lo dice su receta.
3140# ⚠ Su card comprueba dos cosas del SITIO antes de encarnar —`/etc/gitea/app.ini` y el usuario
3141# `gitea` en `/etc/passwd`— y sale con 78 si falta alguna. Es a propósito: las dos vienen con los
3142# datos de la mudanza, y un gitea que arranca sin su config ofrece el asistente de «crear
3143# administrador» a quien pase. Hasta que la mudanza traiga ambas, este servicio NO levanta y lo dice.
3144# ⚠ `chronyd` → `ntpd-rs` (2026-09-22): la hora la da ahora el demonio en Rust. El label cambia con
3145# el paquete porque un label es el nombre del SERVICIO, no de la función: dejar `chronyd` apuntando a
3146# ntpd-rs haría que `arjectl status chronyd` mostrara algo que no es chrony.
3147# ⚠ `crond` y `ntpd-rs` (2026-09-15 / 2026-09-22) son las dos CAPACIDADES que un servidor necesita y que ninguna
3148# métrica reclama: el latido y la hora. Sus paquetes ya estaban declarados arriba y sellados —lo que
3149# faltaba era el eslabón que los ARRANCA, que es este. Una caja con `cronie` instalado y sin `crond`
3150# corriendo se ve idéntica a una con latido, y no rota un log ni respalda nada.
3151# ⚠ `shuma-daemon` y `shuma-gateway` (2026-09-16): la consola de `sergio.gioser.net`, que es el
3152# ÚLTIMO vhost que quedaba atado a gioser (SDD 28 §6.34). Entran como PAR: el gateway sin el daemon
3153# es un puente a ninguna parte, y por eso los dos labels van juntos o no va ninguno.
3154# La cuenta `shuma` la declara `recipes/shuma-daemon.toml` con `[[user]]`, sin privilegio y con
3155# `shell = /bin/sh` —no `/bin/false`—, porque este demonio existe para abrir PTYs: con la shell
3156# inerte el servicio arranca, se supervisa, y cada pestaña muere al instante.
3157# ⚠ CUATRO DE LOS NUEVE DAEMONS PROPIOS **NO** ESTÁN ACÁ, Y ES LO IMPORTANTE DE ESTA LÍNEA:
3158# · `sandokan-watch` — en takana no hay `auth.log` ni journalctl: arrancaría verde SIN VIGILAR.
3159# · `tejido` y `willay-crosscheck` — comparten la identidad libp2p (`device.seed`) con el tejido
3160# que TODAVÍA CORRE en gioser. Dos máquinas con la misma semilla son el MISMO PeerId: no son dos
3161# réplicas, son dos impostores mutuos. Se encienden en el mismo movimiento en que el viejo se
3162# apaga — es un INTERCAMBIO, no una convivencia, y por eso no van al arranque automático.
3163# · `thasnuna` — su INSTALAR.md pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es
3164# glibc: pide jaula qorpa.
3165# Sus Cards están instaladas en `cards.d` (se pueden encarnar a pedido) y sus guardas salen 78
3166# diciendo qué falta. La distinción `cards.d` vs `genesis` es exactamente ésta.
3167servicios = ["sshd", "gitea", "caddy", "minga", "squid", "crond", "ntpd-rs", "shuma-daemon", "shuma-gateway",
3168 "matilda", "tupu", "pacha", "pacha-secretos", "willay-daemon"]
3169
3170# ════════════════════════════════════════════════════════════════════════════════════════════════
3171[perfil.servidor-minimo]
3172descripcion = "el mismo servidor, sin la cadena de desarrollo ni el toolbox: lo que se instala cuando el medio tiene que caber en RAM"
3173hereda = ["base"]
3174# Nace el 2026-09-23 de una pregunta del usuario mirando el tamaño del instalador: **el archivo
3175# único carga el sistema entero en memoria** —es lo que le permite soltar el disco para poder
3176# formatearlo— así que cada MiB del rootfs es RAM exigida en la máquina de quien instala. Con
3177# `servidor` (3,0 GiB) eso pide ~7,6 GiB y el generador se niega en las máquinas chicas.
3178#
3179# ⚠ **La diferencia con `servidor` NO es «menos servidor»: es menos HERRAMIENTA.** Las raíces de
3180# servicio son las mismas una por una —sshd, caddy, gitea, cron, hora, cortafuegos, los daemons
3181# propios—; lo que sale es la cadena con la que se COMPILA y el toolbox interactivo:
3182#
3183# · no hereda `cli` (54 raíces: difftastic 117 MiB, rclone 105, mise 74, dwarves 66, zellij 48,
3184# nushell 43, mold 41, syncthing 39, helix, yazi, btop, atuin… herramientas de trabajar EN la
3185# caja, no de que la caja sirva).
3186# · sin `rust` (364 MiB) ni `lld21` (109): un compilador en una imagen de servidor es una
3187# decisión, y acá se decide que no. El que lo necesite instala `servidor` por USB, o los pone
3188# después desde el repo — que es justamente para lo que el repo existe.
3189#
3190# ⚠ Y NO es «el servidor de verdad». `servidor` sigue siendo el destino completo y es el que va en
3191# la imagen USB, donde no hay límite de memoria. Éste existe por una restricción del MEDIO, no por
3192# una opinión sobre qué debe llevar un servidor. Si algún día el instalador deja de cargar el
3193# sistema en RAM, este perfil deja de tener motivo.
3194paquetes = [
3195 # `consola` también acá, y no es un descuido del adelgazado: son SIETE ficheros de
3196 # configuración y ni un binario, y un servidor mínimo sin `LANG`, con el prompt pelado y la
3197 # campana sonando en cada Tab es justo el peor caso de los que hay. Todas sus menciones de
3198 # herramientas van tras un `command -v`, así que acá —donde no hay starship ni eza— da lo que
3199 # sí se puede dar: UTF-8, `EDITOR`, `PAGER`, historial con memoria, `inputrc` y LS_COLORS.
3200 "consola",
3201 # ── BAJAR PRIVILEGIOS (2026-10-22) ───────────────────────────────────────────────────────────
3202 # `setuidgid` lo invocan las cards de servicio de este perfil por ruta absoluta: lo necesitan
3203 # `gitea`, `minga`, `pacha`, `pacha-secretos` y `shuma-daemon`, porque esos daemons se niegan a
3204 # correr como root y arje-zero encarna sus cards COMO root. Hasta hoy lo daba el applet de
3205 # busybox y era el ÚLTIMO sin dueño propio; ahora es un binario de 330 KB, estático y sólo libc.
3206 "setuidgid",
3207 # ── el acceso y la red ────────────────────────────────────────────────────────────────────────
3208 "openssh", "curl", "wget", "wireguard-tools", "nftables", "netup", "tmux",
3209 # ── lo que sirve ──────────────────────────────────────────────────────────────────────────────
3210 "caddy", "gitea", "squid",
3211 # ── operar la caja ────────────────────────────────────────────────────────────────────────────
3212 "takana", "arjectl", "cronie", "logrotate", "ntpd-rs",
3213 # ── los daemons propios (los mismos que `servidor`, con sus mismas advertencias) ───────────────
3214 "pacha", "pacha-secretos", "minga", "tejido", "thasnuna", "tupu", "matilda",
3215 "shuma-daemon", "shuma-gateway", "sandokan-watch", "willay-daemon", "willay-crosscheck",
3216]
3217
3218[perfil.metal-tigerlake]
3219descripcion = "capa de HARDWARE de la laptop del usuario: firmware y microcódigo del metal real"
3220cola = "corpus"
3221# ── POR QUÉ ES UN PERFIL Y NO PAQUETES SUELTOS EN CADA ESCRITORIO ───────────────────────────────
3222# Esto no es un escritorio ni un toolbox: es **lo que hace falta para que ESTE metal arranque con
3223# todo su hardware vivo**. Se compone con cualquiera de los cuatro escritorios en vez de duplicarse
3224# en los cuatro — que es exactamente el error que el SDD 27 §2 describe («hoy el repo mezcla las
3225# tres capas en una sola lista por escritorio»).
3226#
3227# ── LA MÁQUINA, MEDIDA ─────────────────────────────────────────────────────────────────────────
3228# `lspci -nn` + `/proc/cpuinfo` sobre el metal real, 2026-09-16:
3229#
3230# CPU 11th Gen Intel Core i7-11370H · family 6 · model 140 (0x8c) · stepping 1
3231# GPU Intel TigerLake-LP GT2 [Iris Xe] [8086:9a49] ⇒ i915 / xe
3232# WiFi Intel Wi-Fi 6 AX201 [8086:a0f0] ⇒ iwlwifi
3233# Audio Intel 500 Series HD Audio [8086:a0c8] ⇒ snd_sof_pci_intel_tgl
3234# Disco NVMe ⇒ nvme (sin firmware)
3235#
3236# ── POR QUÉ HASTA HOY NO EXISTÍA ───────────────────────────────────────────────────────────────
3237# Porque el firmware se COPIABA DEL HOST: `scripts/metal-firmware.sh` lee `/lib/firmware` de la
3238# Artix que ya está instalada. Eso alcanza para un USB de pruebas y NO alcanza para un instalador —
3239# un instalador que sólo se puede construir desde la distro a la que viene a reemplazar no es un
3240# instalador. El `TODO(soberanía)` de ese script es justamente esto.
3241#
3242# ── NO ENTRA EN NINGÚN ESCRITORIO POR DEFECTO ──────────────────────────────────────────────────
3243# Es específico de un modelo de laptop. Se pide explícitamente al armar la imagen:
3244#
3245# scripts/targets.py cli metal-tigerlake escritorio-kde
3246#
3247# Otro metal = otro perfil de estos, con su propia receta `firmware-<plataforma>` derivada del mismo
3248# `linux-firmware`. Ese es el punto de haberlo partido en dos recetas.
3249paquetes = [
3250 # Los blobs de las tres familias de este hardware, recortados del linux-firmware pineado.
3251 # Arrastra por deps a `linux-firmware` (648 MB de tarball) y a `sof-firmware`, que NO van en la
3252 # imagen: sólo viaja el recorte de ~25 MB. Es la misma economía que `atuq` sobre `firefox`.
3253 "firmware-tigerlake",
3254 # Microcódigo de la CPU. Va aparte del firmware porque se carga por otro camino: el árbol plano
3255 # sirve para la carga tardía, y el `GenuineIntel.bin` que la receta deja en
3256 # `/usr/share/intel-ucode/` es el que el armador de la imagen debe prepender al initramfs como
3257 # cpio SIN comprimir para la carga TEMPRANA — la que llega a tiempo para las mitigaciones.
3258 "intel-ucode",
3259]