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…
el TLS arreglado en la caja, la distro con nombre, y el ENOSPC que no era de la raíz
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[perfil.base]
23descripcion = "userland foundational C: la distro arranca y se usa (shell, VCS, privilegios, red, disco, TLS, proceso, editor, docs)"
24# Lift verbatim de BASE_SYSTEM_PKGS (scripts/product-userland-from-repo.sh). De-Alpinizado y firmado,
25# tandas base-system-1..3.
26paquetes = [
27 # `bash-completion` (2026-09-09): el tabulador de las herramientas del sistema. Entra como raíz
28 # porque NADIE lo alcanza por deps de build — los paquetes que instalan completados sólo preguntan
29 # por su `.pc` y, si no está, se saltan la instalación en silencio. Es un hueco de RUNTIME puro,
30 # de los que ninguna métrica de deuda puede ver. Salió del triaje de la frontera con 5 consumidores.
31 "bash", "bash-completion", "git", "sudo", "doas", "util-linux", "shadow", "kbd", "procps-ng",
32 "iproute2", "iputils", "dhcpcd", "e2fsprogs", "dosfstools", "parted", "rsync",
33 "sed", "tzdata", "vim", "dbus", "ca-certificates", "gnupg", "lsof", "strace",
34 "pciutils", "usbutils", "mandoc",
35 # `musl-shared` (2026-09-11): EL CARGADOR DINÁMICO. Va en `base` y no en un perfil concreto porque
36 # sin él cualquier binario dinámico de la clausura queda INERTE — presente y sin poder ejecutarse.
37 # Medido en la imagen del perfil `servidor`: **23 binarios de 726** pedían
38 # `/lib/ld-musl-x86_64.so.1` y NINGÚN artefacto del corpus lo publicaba; funcionaban en el hub sólo
39 # porque el rootfs Alpine del LAB lo presta. Entre ellos, `python3` y `perl` — o sea TODO el
40 # instrumental del proyecto. El síntoma era `sh: python3: not found` con `command -v python3`
41 # contestando `/usr/bin/python3`: no falta, no arranca. Ver SDD 28 §6.4.
42 "musl-shared",
43 # `zlib-shared` (2026-09-11): el único hueco que quedó tras publicar el cargador. Con
44 # `musl-shared`, 22 de los 23 binarios inertes de la imagen del servidor pasaron a correr; el 23º
45 # era `sqlite3`, que muere con `Error loading shared library libz.so.1`. La zlib canónica es
46 # `--disable-shared` y ningún artefacto publicaba ese soname — otra vez el rootfs del lab tapando
47 # el agujero. La variante ya existía en el corpus; sólo faltaba declararla.
48 "zlib-shared",
49 # `libffi-shared` (2026-09-13): el TERCER hueco de la misma familia, y lo encontró ampliar el
50 # alcance de un vigía, no una imagen rota. `vigia-sonames.py` corría por defecto SÓLO sobre los
51 # cinco perfiles de escritorio (`p.startswith("escritorio")`), así que `base`, `cli` y `servidor`
52 # —el objetivo de la mudanza— nunca se miraron. Apuntándolo a ellos:
53 #
54 # == servidor 147 nodos · 183 sonames provistos · 1 sin proveedor
55 # FALTA libffi.so.8 ← lo piden: python3
56 #
57 # O sea que `python3` volvía a ser INERTE en las tres imágenes, por el mismo mecanismo exacto que
58 # `musl-shared` y `zlib-shared` arreglaron dos días antes: la receta canónica de libffi es
59 # `--disable-shared`, ningún artefacto del cierre publica ese soname, y en el lab el agujero lo
60 # tapa el rootfs de Alpine. La variante `libffi-shared` YA existía y ya estaba declarada en
61 # `escritorio-mirada` y en otro perfil — sólo faltaba acá.
62 "libffi-shared",
63 # `zstd-cli` (2026-09-11): la distro comprime TODO con zstd —la imagen del lab
64 # (`lab-image.tar.zst`), el respaldo al Storage Box, el `dd` remoto de una instalación— y la imagen
65 # no traía con qué descomprimirlo: **un takana recién instalado no puede desempacar su propio
66 # laboratorio** (`tar: unrecognized option: zstd`, el tar del rootfs es el de busybox).
67 #
68 # ⚠ Es `zstd-cli` y NO `zstd`. La receta canónica construye **sólo `lib/`** —lo dice su cabecera— y
69 # su artefacto no tiene `usr/bin`: declararla acá no habría arreglado nada. Se vio al aplicarla con
70 # `takana upgrade` en la caja: «✓ generación 1 aplicada, +8 añadidos» y `zstd` seguía ausente,
71 # porque los 8 ficheros eran headers y `libzstd.a`. Ampliar la canónica re-hashearía a sus 13
72 # consumidores (mesa entre ellos), así que va como variante. Ver SDD 28 §6.13.
73 "zstd-cli",
74 # ── LA CAPACIDAD QUE FALTABA: WiFi (2026-09-13, barrido de perfiles) ─────────────────────────
75 # La imagen NO podía asociarse a una red inalámbrica. `dhcpcd`, ahí arriba, resuelve la IP de un
76 # cable; el handshake WPA2 de 4 vías lo hace un supplicant en userspace, y busybox NO lo trae.
77 # La receta existía desde el frente de metal —su cabecera dice literalmente «para asociar el WiFi
78 # del medio live al AP»— y no estaba declarada en NINGÚN perfil: sellada, reproducible, y en cero
79 # imágenes. Es la figura de `foot` otra vez, y esta vez el precio era quedarse sin red.
80 # Va en `base` por el mismo argumento que `dhcpcd`: es la capa que convierte «la máquina arrancó»
81 # en «la máquina tiene red», y un portátil sin WiFi es una máquina sin red aunque el resto esté.
82 # ⚠ Son TRES binarios (`wpa_supplicant`, `wpa_cli`, `wpa_passphrase`) y nada más. NO trae gestor
83 # de red: `networkmanager` existe SÓLO en `incoming-kde`, así que GNOME, COSMIC y sway no lo
84 # alcanzan (cola hermana). Eso es una decisión aparte, escrita en el plan del barrido.
85 "wpa_supplicant",
86]
87
88[perfil.cli]
89descripcion = "la capa CLI moderna (Rust) encima de base — el userland real del producto"
90hereda = ["base"]
91# Lift verbatim de CLI_RUST_PKGS (scripts/product-userland-from-repo.sh).
92#
93# ⚠ 2026-09-12: ESTA LISTA ERA UN LIFT DE UN SCRIPT, NO UNA DECLARACIÓN DE LO QUE HAY. Medido
94# contra los cinco grafos: de las 892 recetas selladas del corpus, **649 no están en NINGÚN
95# perfil** — o sea que el 73% del catálogo está sellado y no viaja en ninguna imagen. No es deuda
96# de build (`drenaje.json` dice 0) ni la métrica de perfil lo puede ver: mide la clausura de lo
97# DECLARADO. Es la lección de `foot` a escala de catálogo, y se descubrió al ir a declarar recetas
98# nuevas y notar que las viejas tampoco estaban.
99#
100# El barrido completo de las 649 es su propia unidad de trabajo (hay 363 recetas Go que son
101# herramientas de desarrollo importadas en tanda y NO todas deben entrar en una imagen). Lo que se
102# declara abajo es el subconjunto que ya estaba sellado y que un usuario de esta capa espera
103# encontrar — cero recetas nuevas, cero builds: sólo dejan de ser invisibles.
104paquetes = [
105 # `os-release` (2026-09-14): la IDENTIDAD del sistema, que no existía. Se descubrió al meter
106 # fastfetch: la caja decía «Host: vServer» y ni una palabra de takana, porque `/etc/os-release` no
107 # estaba. Lo lee todo el que quiera saber en qué distro corre — sin él, la distro es anónima para
108 # cualquier programa que no sea suyo.
109 "os-release",
110 # `fastfetch` (2026-09-14, pedido del usuario): la tarjeta de presentación del sistema. En una
111 # distro propia es lo primero que contesta «¿qué estoy corriendo?» sin leer tres ficheros de
112 # `/proc`, y en una caja recién instalada distingue «arrancó» de «arrancó lo que yo creía».
113 "fastfetch",
114 "bat", "fd", "ripgrep", "jq", "zoxide", "dust", "procs", "htop", "less", "tree",
115 "hyperfine", "delta", "eza", "sd", "tokei", "fzf", "skim", "git-cliff", "gitui",
116 "starship",
117 # ── SELLADAS DESDE HACE MESES Y EN NINGUNA IMAGEN (2026-09-12) ───────────────────────────────
118 # editor, multiplexor, gestor de ficheros e historial de shell: las cuatro piezas con las que se
119 # VIVE en una terminal. `helix` estaba declarada sólo en escritorio-gnome, que es donde menos
120 # sentido tiene — es la capa CLI la que la necesita.
121 "helix", "zellij", "yazi", "atuin",
122 # VCS más allá de git: jj (jujutsu) como VCS moderno, lazygit de TUI, difftastic para diffs
123 # que entienden sintaxis.
124 "jujutsu", "lazygit", "difftastic",
125 # el cinturón de quien construye cosas
126 "just", "direnv", "watchexec", "mise",
127 # red y datos: cliente HTTP, cifrado de ficheros, copia a remoto y respaldo
128 "xh", "age", "rclone", "restic",
129 # sistema: los dos monitores y los dos contadores de disco
130 "bottom", "btop", "duf", "ncdu",
131 # ── NUEVAS DE LA TANDA DEL 2026-09-12 ───────────────────────────────────────────────────────
132 # shell moderna (INTERACTIVA, no /bin/sh: su lenguaje no es POSIX), correo en terminal,
133 # reproductor de música y sincronización entre máquinas.
134 "nushell", "aerc", "cmus", "syncthing",
135 # herramientas de quien CONSTRUYE con la distro, no de quien la usa: `sccache` ya estaba sellada
136 # y sin perfil, `mold` es de hoy. Van acá y no en `base` porque un servidor pelado no las quiere.
137 "sccache", "mold",
138 # ── BARRIDO DE PERFILES (2026-09-13): selladas hace meses y en ninguna imagen ────────────────
139 # Criterio para entrar acá, y está escrito porque el barrido encontró 620 hojas sin perfil y
140 # meterlas todas habría sido peor que no mirar: entra lo que un usuario de esta capa ESPERA
141 # encontrar y hoy no está. Lo demás queda en el catálogo y se instala por nombre — ver
142 # `docs/plan-barrido-perfiles-y-servicios.md`, §la política.
143 "nano", # el editor que teclea quien no sabe salir de vim; `base` sólo trae vim
144 "tig", # git en TUI; el corpus tenía lazygit y gitui y ninguno declarado hasta hoy
145 "patch", # aplicar un diff a mano. Userland clásico que simplemente faltaba
146 "socat", # el destornillador de red: sockets, puertos, diagnóstico
147 "pigz", # gzip en paralelo
148 "miller", # `jq` para CSV/TSV — el hueco de datos tabulares
149 "dwarves", # `pahole`: inspeccionar el BTF/DWARF del kernel. Esta distro compila el suyo
150]
151
152[perfil.escritorio-mirada]
153descripcion = "rootfs slim del USB mirada: compositor Wayland propio sobre mesa software"
154# NO hereda base: es una imagen slim a propósito. El ORDEN importa (libs primero, binarios mirada
155# al final) y se preserva. `scripts/mirada-usb.sh` deriva su PKGS de acá (`targets.py
156# escritorio-mirada`), así que esta lista ES la del USB — no una copia que pueda divergir.
157#
158# ── Las tres `-shared` (2026-09-05) ──────────────────────────────────────────────────────────────
159# Cazadas por `scripts/vigia-sonames.py`: `mesa-swrast` y `wayland` salen con `NEEDED libexpat.so.1`,
160# `libz.so.1` y `libffi.so.8`, y las recetas canónicas de expat/zlib/libffi son `--disable-shared`.
161# Ningún artefacto del cierre publicaba esos SONAME ⇒ el rootfs los resolvía contra el sysroot Alpine
162# DEL LAB y la imagen sólo arrancaba donde hubiera Alpine debajo.
163#
164# Es la MISMA fuga que los otros cuatro perfiles ya cerraron el 2026-09-03; mirada quedó fuera porque
165# su lista nació como copia literal del script y nadie la revisó. Van de raíces —y no de `[deps]`—
166# porque son data de RUNTIME: nadie las declara como dep de BUILD, así que la clausura no las alcanza
167# por ninguna arista. Las tres viven en `corpus`, la misma cola, y ya están selladas ⇒ CERO rebuilds.
168#
169# NO se suman `bzip2-shared` ni `ncurses-shared`, que sí llevan los otros perfiles: acá esos sonames
170# los pide sólo `python3`, que es HERRAMIENTA DE BUILD y no viaja en la imagen. Lo accionable del
171# vigía es lo que pide un componente de runtime, y por eso imprime siempre quién pide cada cosa.
172paquetes = [
173 "expat-shared", "zlib-shared", "libffi-shared",
174 "mesa-swrast", "wayland", "libdrm", "seatd", "libinput", "libudev-zero",
175 "libxkbcommon", "pixman", "linux-pam", "mtdev", "libevdev",
176 "mirada-compositor", "mirada-greeter", "mirada-ctl",
177]
178
179[perfil.escritorio-kde]
180descripcion = "escritorio KDE Plasma 6 desde fuente"
181cola = "incoming-kde"
182# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
183# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
184# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
185# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
186# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
187# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
188# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
189# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
190# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
191hereda = ["cli"]
192# Raíces del rootfs de metal (docs/HANDOFF-noche-kde-2026-07-14.md:135). El resto del escritorio
193# —137 artefactos hidratados— es CLAUSURA de estas 7, no lista a mano.
194paquetes = [
195 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
196 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
197 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
198 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
199 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
200 # lecturas del modelo de composición.
201 "desktop-file-utils",
202 "plasma-workspace", "kwin", "kscreenlocker", "breeze", "frameworkintegration",
203 "qqc2-desktop-style", "plasma-integration",
204 # Huecos de RUNTIME confirmados por el triaje de la frontera (2026-07-22). Ninguno bloquea un
205 # build —todas las recetas que los piden ya están selladas— pero sin ellos el escritorio arranca
206 # roto: sin tipos de fichero, sin apps X11, sin diálogos de autorización y con los controles QML
207 # en un estilo genérico. Ver docs/state/frontera-triaje.toml para el porqué de cada uno.
208 "shared-mime-info", "xwayland", "polkit-qt-1", "qqc2-breeze-style",
209 # ⚠ Añadido el 2026-09-03 arrancando la imagen en QEMU. NO sale de la clausura de las 7 raíces
210 # porque no es dep de BUILD de nada: es dep de RUNTIME de plasmashell, que lo pide por D-Bus y
211 # **ABORTA** si no contesta —`kde.plasmashell: Aborting shell load: The activity manager daemon
212 # (kactivitymanagerd) is not running.`— dejando kwin vivo y la pantalla EN NEGRO. El síntoma miente:
213 # `STATUS: kwin=1 plasmashell=1` durante minutos, con los dos procesos vivos y nada pintado.
214 # `plasma-start-qemu.sh` ya lo lanzaba (línea 188); lo que faltaba era que el paquete ENTRARA.
215 # Misma figura que las raíces de runtime de GNOME (imports.gi.*): el cierre de BUILD no es el
216 # cierre de RUNTIME, y lo que se invoca por bus es invisible al grafo de deps.
217 "kactivitymanagerd",
218 # ── CAPA 2: EL ESCRITORIO USABLE ─────────────────────────────────────────────────────────────
219 # ⚠ Añadidas el 2026-09-03. **Las 22 ya estaban SELLADAS en esta cola y no las declaraba nadie**,
220 # así que no cuestan un build: sólo entran a la clausura. Se descubrió listando los `(cola,nombre)`
221 # cuyo artefacto instala un `usr/share/applications/*.desktop` y que `yupana.membresia()` no
222 # asigna a ningún perfil — salieron 23, y 22 son de acá.
223 #
224 # POR QUÉ FALTABAN, que no es lo mismo que «se decidió que no fueran». Las 7 raíces de arriba son
225 # el set MÍNIMO del arranque en metal, tomado de `docs/HANDOFF-noche-kde-2026-07-14.md:135`. Ese
226 # mismo documento nombra lo que faltaba como el trabajo siguiente: *«Capa 2 KF6 / apps núcleo:
227 # importar+construir konsole/dolphin/kate vía granja (extiende el escritorio usable)»*. Se
228 # construyeron y se sellaron; lo que nunca ocurrió fue DECLARARLAS. La métrica no lo podía ver
229 # porque mide lo declarado — la misma figura que dejó a sway en 121/121 sin emulador de terminal.
230 #
231 # Sin esto la imagen es compositor + shell + tema y nada más: **sin terminal no se puede salir de
232 # un fallo, y sin plasma-nm/plasma-pa/powerdevil no hay red, ni audio, ni gestión de energía.**
233 #
234 # Plomería de escritorio — esto no son "apps", es lo que hace que Plasma funcione:
235 "plasma-desktop", "plasma-nm", "plasma-pa", "powerdevil", "kscreen",
236 "systemsettings", "kinfocenter", "kmenuedit", "kwalletmanager",
237 # Núcleo usable: terminal, ficheros, editor, documentos, archivos comprimidos, captura, imágenes.
238 # `konsole` es el que cierra el agujero de «no hay terminal».
239 "konsole", "dolphin", "kate", "okular", "ark", "spectacle", "gwenview",
240 # Accesorios que ya estaban construidos; entran porque no cuesta nada y completan el menú.
241 "kcalc", "kfind", "filelight", "kcharselect", "kruler", "kdf",
242 # ── FUENTES ──────────────────────────────────────────────────────────────────────────────────
243 # Añadida el 2026-09-03 por el mismo recuento que destapó el hueco de GNOME: contando
244 # `.ttf/.otf/.ttc/.pfb` sobre los artefactos del cierre, este perfil tenía **UNA sola**, y de
245 # rebote — viene dentro de `qtbase`, no porque nadie la haya pedido. `fontconfig` estaba, pero
246 # fontconfig es el MOTOR que busca fuentes, no una fuente.
247 # Depender de la que trae qtbase es apoyarse en un detalle de empaquetado de Qt que puede
248 # desaparecer en cualquier versión, y encima deja a todo lo que NO es Qt sin nada.
249 "dejavu-fonts",
250 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
251 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
252 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
253 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
254 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
255 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
256 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
257 "dejavu-fonts-nerd",
258 # ⚠ `xwayland` sigue acá como RAÍZ pero su receta NO se va a escribir: lo provee una imagen ajena
259 # enjaulada, declarado en ../state/qorpa-ajenos.toml (ADR 0015 D5 capa 3, qorpa). El Xwayland vive
260 # DENTRO de la imagen —Arch y Fedora ya lo traen y se cuelga de kwin por el socket— así que el
261 # hueco se disuelve sin receta y sin tocar la postura Wayland-only. Queda como raíz A PROPÓSITO:
262 # la imagen lo NECESITA, y sacarlo de acá lo volvería invisible en vez de contado aparte. El grafo
263 # ya lo reporta con clase `ajeno` («+ 1 ajenas»), nunca sumado a las nativas. Los otros tres ya
264 # están sellados (2026-09-02/03).
265 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
266 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
267 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
268 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
269 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
270 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
271 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
272 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
273 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
274 "mpv",
275 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
276 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
277 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
278 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
279 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
280 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
281 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
282 "atuq",
283 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
284 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
285 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
286 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
287 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
288 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
289 # así que la imagen necesita además a alguien escuchando ese nombre.
290 "libnotify",
291 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
292 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
293 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
294 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
295 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
296 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
297 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
298 "llama-cpp",
299 "ia-modelo-chat",
300 # ── CAPTURA Y STREAMING ──────────────────────────────────────────────────────────────────────
301 # `obs-studio` (2026-09-04). A diferencia de mpv, ésta vive en ESTA COLA y no en el corpus, y no
302 # es preferencia: su frontend es Qt6 y las 13 recetas Qt viven sólo en `incoming-kde`. Un qtbase
303 # de corpus tendría otro hash con el MISMO nombre de artefacto ⇒ dos Qt peleando por
304 # `/usr/lib/libQt6Core.so`. ⇒ **por ahora OBS es de KDE y de nadie más**; el día que exista un Qt
305 # de corpus (`yupana radio qtbase` = 119, campaña propia) puede subir y listarse en las cuatro.
306 #
307 # Su renderer va PORTADO a EGL (`obs-glad-egl-loader.patch`): el glad de upstream hacía
308 # `dlopen("libGL.so.1")` + `dlsym("glXGetProcAddressARB")`, o sea GLX en runtime, que esta distro
309 # no tiene ni quiere.
310 #
311 # ⚠ Entra con tres huecos escritos en su receta: sin webcam (`ENABLE_V4L2=OFF`, `libv4l2` no lo
312 # publica ninguna cola), sin salida MPEG-TS (SRT/RIST ausentes) y sin scripting (falta `swig`).
313 # Grabar, capturar la pantalla y publicar por RTMP —el uso real— no pasan por ninguno de los tres.
314 "obs-studio",
315 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
316 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
317 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
318 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
319 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
320 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
321 "swayimg",
322 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
323 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
324 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
325 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
326 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
327 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
328 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
329 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
330 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
331 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
332 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
333 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
334 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
335 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
336 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
337 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
338 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
339 # partes ⇒ un solo artefacto, cero rebuilds).
340 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
341 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
342 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
343 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
344 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
345 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
346 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
347 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
348 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
349 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
350 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
351 # ── TEMA DE ICONOS ───────────────────────────────────────────────────────────────────────────
352 # Añadido el 2026-09-03 releyendo la captura que dio el arranque por bueno
353 # (`docs/evidencia/plasma6-qemu-2026-09-03-kactivitymanagerd.png`): el panel salía con el reloj y
354 # NADA MÁS, y se leyó como «el panel arranca». No era el panel — era que ninguno de sus applets
355 # tenía icono que dibujar. `kickoff`, `systemtray`, `showdesktop` y `trash` SON un icono y poco
356 # más, así que sin tema quedan de ancho cero; el reloj se ve porque dibuja TEXTO. Los `.so` de los
357 # applets estaban todos instalados (`/usr/lib/qt6/plugins/plasma/applets/`), o sea que el síntoma
358 # no distingue «no cargó» de «cargó y no tiene qué pintar».
359 # El log lo decía en UNA línea que nadie leyó: `kf.iconthemes: Icon theme "breeze" not found.`
360 #
361 # `breeze`, que ya estaba de raíz, es el tema de WIDGETS y de CURSORES: instala
362 # `usr/share/icons/Breeze_Light` y `usr/share/icons/breeze_cursors`, que son punteros de ratón, no
363 # iconos de aplicación. Los ~11.000 iconos viven en `breeze-icons`, receta APARTE que estaba
364 # sellada desde siempre y que no declaraba NINGÚN perfil. Misma figura que las fuentes y que
365 # `foot`: es data de RUNTIME, no la alcanza ninguna arista de BUILD, y la clausura no la puede
366 # echar de menos porque mide lo declarado.
367 #
368 # `hicolor-icon-theme` es el FALLBACK obligatorio del estándar freedesktop: cada aplicación
369 # instala sus iconos en `usr/share/icons/hicolor/...` y todo buscador termina ahí cuando el tema
370 # activo no tiene lo pedido. Sin su `index.theme` el directorio existe pero NO es un tema y la
371 # búsqueda no lo recorre. Vive en el corpus desde hoy (promovida con hash IDÉNTICO
372 # b3:98ad44a5 desde `incoming-gnome`/`incoming-cosmic`, 0 rebuilds en sus 4 consumidores) para que
373 # la alcancen las cuatro imágenes.
374 "breeze-icons", "hicolor-icon-theme",
375 # ── ENERGÍA: sin esto la imagen NO TIENE BATERÍA (añadido 2026-09-09) ─────────────────────────
376 # ⚠ El perfil declaraba `powerdevil` —el gestor de energía de Plasma— pero NO a upower, que es
377 # con quien powerdevil habla para leer la batería y aplicar perfiles de energía. Medido: `upower`
378 # no estaba ni en esta cola ni en el corpus, sólo en la de GNOME, y las colas no se ven entre sí.
379 # En un portátil eso es un escritorio sin indicador de batería, y el arranque no falla ⇒ no se ve
380 # hasta usarlo. Es el mismo tipo de hueco que las fuentes de GNOME o el «abrir con» vacío.
381 # La receta es una VARIANTE con `-Dintrospection=disabled`: GNOME la necesita encendida porque su
382 # shell hace `imports.gi.UPowerGlib`, pero powerdevil habla D-Bus con `org.freedesktop.UPower` y
383 # no toca el typelib. Apagarla evita copiar cuatro recetas más de introspección a esta cola.
384 "upower",
385 # ── PORTALES: sin esto NO SE COMPARTE PANTALLA (añadido 2026-09-09) ───────────────────────────
386 # ⚠ Segundo hueco que dejaba a KDE por debajo de una instalación estándar. Medido recorriendo los
387 # `.service` de D-Bus de TODO el store: los únicos backends de portal sellados eran los de COSMIC
388 # y GNOME. Ninguno de KDE — y esta cola no tenía ni el frontend. Consecuencia concreta: la imagen
389 # trae `obs-studio` con su plugin linux-pipewire, que captura pantalla POR EL PORTAL, así que en
390 # Plasma no tenía con quién hablar mientras en los otros dos ese frente estaba cerrado.
391 # Van los DOS porque la cadena es de tres: cliente → org.freedesktop.portal.Desktop [frontend] →
392 # org.freedesktop.impl.portal.* [backend]. El frontend comparte artefacto con el de COSMIC (hash
393 # idéntico ⇒ un solo directorio en el store, cero builds).
394 # ⚠ El backend va SIN el portal de impresión, y no es un recorte por comodidad: no hay ninguna
395 # receta de cups en el catálogo ⇒ qtbase se construyó sin CUPS ⇒ no existe `qcups_p.h` y
396 # `src/print.cpp` no compila. Un portal de impresión sin con qué imprimir es código muerto. El
397 # backend tampoco lo ANUNCIA ya en su `kde.portal` (verificado: 0 apariciones de Print), que es lo
398 # que evita que el frontend enrute a un portal que no contesta.
399 "xdg-desktop-portal", "xdg-desktop-portal-kde",
400 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
401 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
402 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
403 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
404 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
405 # una sola copia.
406 #
407 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
408 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
409 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
410 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
411 # recetas: son tres perfiles sin «enable» ninguno.
412 #
413 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
414 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
415 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
416 "cups", "bluez",
417 # `wireplumber` (2026-09-13): **el audio de esta imagen no encaminaba nada.** `pipewire` está y
418 # arranca, pero se construyó con `-Dsession-managers=[]`: sin gestor de sesión no hay política de
419 # qué es entrada, qué es salida ni qué se enlaza con qué — el sink que ve la aplicación es
420 # `auto_null`, o sea el nodo de descarte. Audio que parece vivo y no suena.
421 # Vive en el CORPUS —y no en esta cola— porque desde acá no se alcanza una cola hermana: las
422 # copias de `incoming-gnome` e `incoming-cosmic` eran invisibles para este perfil. Ver su receta:
423 # hay tres variantes vivas a propósito y consolidarlas es el trabajo siguiente.
424 "wireplumber",
425]
426
427# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
428# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
429# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
430# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
431# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
432#
433# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
434# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
435# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
436# `perfiles` de los cinco `build-state*.json`, no a ojo.
437#
438# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
439# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
440#
441# ⚠ DOS AUSENCIAS QUE ESTE CRUCE DESTAPÓ Y QUE NO SE ARREGLAN ACÁ:
442# · **no hay `wireplumber`** en la clausura. `pipewire` sin gestor de sesión no encamina nada:
443# los nodos existen y nadie los conecta. GNOME y COSMIC sí lo tienen; KDE y sway, no.
444# · **no hay `logind-compat`**. KWin pide una sesión de logind para tomar el seat; acá la cubre
445# `seatd`/el propio `plasma-workspace`, pero conviene saber que el servicio no está.
446# Las dos son deuda del perfil, no de esta lista. Se escriben para que se vean.
447servicios = [
448 "dbus-system", "upowerd", "cupsd", "bluetoothd",
449 "pipewire", "pipewire-pulse",
450 # 2026-09-13: con `wireplumber` declarado arriba, el audio pasa de «acepta clientes» a «encamina».
451 "wireplumber",
452 # `NetworkManager` llega por la clausura de `plasma-nm` y hasta hoy NADIE lo arrancaba: el applet
453 # de red de Plasma se dibujaba sobre un demonio apagado. Con la cola retirada es el del corpus,
454 # o sea el que sí maneja WiFi.
455 "NetworkManager",
456]
457
458[perfil.escritorio-gnome]
459descripcion = "escritorio GNOME (Wayland-nativo) desde fuente — 3er escritorio, X11 al tacho"
460cola = "incoming-gnome"
461# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
462# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
463# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
464# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
465# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
466# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
467# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
468# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
469# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
470hereda = ["cli"]
471# Raíces de una sesión GNOME que bootea a un shell usable. El resto es CLAUSURA. Hoy son HUECOS
472# (cero recetas en incoming-gnome): build-state SALTA este perfil hasta que su cola se cargue
473# (--gnome), así que declararlo NO reporta raíces fantasma — sólo lo hace un objetivo de primera
474# clase que el sistema conoce. La frontera de autoría se descubre con `seed-gnome.py` (bootstrap por
475# metadata mientras no haya recetas); OJO: ese cierre SOBREESTIMA ~5-10× (ver docs/state/seed-gnome.json
476# y la memoria frente-gnome). El palo largo / keystone es spidermonkey(mozjs) → gjs → gnome-shell.
477#
478# ── SIN gnome-session / gnome-settings-daemon / gdm: APARCADAS POR DISEÑO (2026-08-07) ───────────
479# Estaban acá y hacían que el perfil reportara 124/127 con 3 en `never` PARA SIEMPRE, que se lee
480# como trabajo pendiente. No lo es: las tres mueren en GTK3, y GTK3 es una de las tres deudas que
481# el frente GNOME aparcó a propósito (GTK3 / X11 / PAM). El diagnóstico completo, verificado contra
482# el `meson.build` de cada tag, está en el encabezado de cada receta — que sigue en el repo, con su
483# análisis intacto, por si algún día se autora el stack GTK3.
484#
485# NO son tres problemas, es UNO. Y no bloquean el escritorio: gnome-shell NO depende de
486# gnome-session ni de g-s-d, ni en build ni para arrancar. El camino vivo es mutter → gnome-shell,
487# lanzado por arje. El único que pedía gnome-session era gdm.
488#
489# ⚠ La contradicción costó: `targets.toml` las reclamaba como raíces mientras las recetas decían
490# «aparcadas», así que el grafo reportaba deuda fantasma y el worker las reintentaba en cada ciclo.
491# Dos documentos del repo diciendo cosas opuestas, y el que se mira primero es el grafo.
492# Si algún día se autora GTK3, se vuelven a añadir acá y el objetivo reaparece solo.
493paquetes = [
494 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
495 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
496 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
497 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
498 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
499 # lecturas del modelo de composición.
500 "desktop-file-utils",
501 "mutter", "gnome-shell", "gjs",
502 "gsettings-desktop-schemas", "gnome-desktop", "xdg-desktop-portal-gnome",
503 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
504 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
505 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
506 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
507 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
508 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
509 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
510 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
511 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
512 "mpv",
513 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
514 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
515 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
516 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
517 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
518 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
519 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
520 "atuq",
521 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
522 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
523 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
524 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
525 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
526 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
527 # así que la imagen necesita además a alguien escuchando ese nombre.
528 "libnotify",
529 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
530 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
531 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
532 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
533 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
534 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
535 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
536 "llama-cpp",
537 "ia-modelo-chat",
538 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
539 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
540 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
541 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
542 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
543 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
544 "swayimg",
545 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
546 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
547 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
548 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
549 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
550 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
551 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
552 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
553 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
554 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
555 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
556 "zathura", "zathura-pdf-poppler",
557 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
558 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
559 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
560 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
561 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
562 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
563 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
564 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
565 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
566 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
567 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
568 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
569 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
570 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
571 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
572 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
573 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
574 # partes ⇒ un solo artefacto, cero rebuilds).
575 # ── ⚠ TERMINAL Y EDITOR: ESTA IMAGEN NO TENÍA NI UNO NI OTRO ─────────────────────────────────
576 # Medido el 2026-09-03 cruzando la membresía de los cuatro perfiles contra una lista de emuladores
577 # de terminal, editores y gestores de ficheros:
578 #
579 # escritorio-cosmic term=cosmic-term editor=cosmic-edit archivos=cosmic-files
580 # escritorio-sway term=foot editor=vim archivos=—
581 # escritorio-gnome term=— editor=— archivos=—
582 # escritorio-kde term=— editor=— archivos=—
583 #
584 # Es la lección de `foot` otra vez y a mayor escala: sway llegó a 121/121 SIN emulador de terminal
585 # porque nadie lo declaraba. Un escritorio sin terminal no es uno incompleto: es uno del que no se
586 # puede salir cuando algo falla.
587 #
588 # `foot` y `helix` viven en el CORPUS —no en una cola—, así que desde acá se alcanzan y no hacen
589 # falta recetas nuevas: las dos ya estaban selladas y sin declarar por NADIE. foot es Wayland puro
590 # sobre `xdg-shell` (no pide protocolos de wlroots), así que corre bajo mutter igual que bajo sway;
591 # su único NEEDED es `libc.so`. helix es TUI y corre dentro de la terminal.
592 #
593 # ⚠ ESTO ESTABA VIEJO, corregido el 2026-09-09. Decía que KDE quedaba fuera del arreglo y que «su
594 # perfil son 13 raíces de ARRANQUE EN METAL, no la imagen completa», con konsole/kate/dolphin y
595 # otras 19 apps selladas y sin declarar. Ya no: el frente KDE lo reordenó y hoy su perfil son **48
596 # raíces** con konsole, dolphin, kate, okular, ark, spectacle, gwenview, kcalc, las fuentes y las
597 # `*-shared` de runtime declaradas. De su cola quedan 8 recetas fuera del perfil, y son las que
598 # corresponde: libICE, libSM, libXtst y los cinco eslabones que sólo servían a xwayland
599 # (libfontenc, libXfont2, libxkbfile, libxshmfence, xkbcomp), huérfanos desde que la receta se
600 # retiró.
601 #
602 # Lo que a KDE le queda no es este arreglo sino OTRO, medido el mismo día: su imagen no se hidrata
603 # desde el perfil sino de un rootfs YA ARMADO (`kde-qemu-rootfs`, 8,4 G) que nadie re-deriva, así
604 # que se queda atrás a medida que el perfil crece. Comprobado contra ese rootfs: de las 18 hojas
605 # que el perfil declara están 15 — faltan `obs-studio` y `atuq`, justo las dos que se añadieron
606 # después de aquella hidratación. Re-hidratarlo con `scripts/hydrate-profile.py escritorio-kde`
607 # es decisión del frente KDE, no de éste.
608 "foot", "helix",
609 # ── FUENTES ──────────────────────────────────────────────────────────────────────────────────
610 # ⚠ Añadida el 2026-09-03 porque este perfil no traía NI UN FICHERO DE FUENTE. Medido recorriendo
611 # los artefactos del cierre y contando `.ttf/.otf/.ttc/.pfb`: **gnome=0**, sway=22 (dejavu),
612 # kde=1 (una que viene dentro de qtbase). `fontconfig` estaba, pero fontconfig es el MOTOR que
613 # busca fuentes, no una fuente — tenerlo y creer que hay tipografía es el mismo error que
614 # confundir un directorio vacío con un artefacto.
615 # Se descubrió con `poppler-render-check`: un PDF con fuentes base-14 (Helvetica, Times…) NO
616 # trae la tipografía adentro, la sustituye el visor por fontconfig, así que sin fuentes el
617 # documento se ve VACÍO — y el visor no da error, que es lo peor.
618 # `dejavu-fonts` es la única receta de fuentes del catálogo entero. Que KDE esté igual (1 fuente
619 # de rebote, dentro de qtbase) es un hallazgo aparte y NO se toca acá: ese perfil no lleva zathura
620 # y su árbol lo está trabajando otro frente.
621 "dejavu-fonts",
622 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
623 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
624 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
625 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
626 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
627 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
628 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
629 "dejavu-fonts-nerd",
630 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
631 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
632 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
633 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
634 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
635 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
636 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
637 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
638 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
639 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
640 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
641 # `xz-shared` se suma el 2026-09-05, cazada por `scripts/vigia-sonames.py`. Y es la MÁS grave de
642 # todas las de esta clase encontradas hasta ahora: quien pide `liblzma.so.5` no es una herramienta
643 # ni un binario suelto, es **`libadwaita-1.so.0`** — la librería contra la que enlaza TODA app
644 # GTK4/GNOME de la imagen. Sin proveedor, el loader falla en cada una de ellas, no en una.
645 # El `xz` canónico del corpus es `.a`; la receta `-shared` existía sólo en `incoming-kde`, y se
646 # promueve al corpus igual que `bzip2-shared`: mismo hash desde las dos partes
647 # (b3:174289d5) ⇒ un solo artefacto, cero rebuilds.
648 # Sólo va acá: en cosmic y sway ese soname lo pide únicamente `python3`, que no viaja en la imagen.
649 "xz-shared",
650 # ── TEMA DE ICONOS Y DE CURSORES ─────────────────────────────────────────────────────────────
651 # Añadido el 2026-09-03: medido sobre los artefactos del cierre, este perfil tenía **CERO** temas
652 # de iconos — ni siquiera `hicolor`. Se ve en `docs/evidencia/gnome-shell-qemu-2026-09-03-libs-
653 # compartidas.png`: la barra superior trae la fecha (texto) y el pill de espacios de trabajo, y la
654 # DERECHA está vacía — los indicadores de red, volumen y batería son iconos, y no había ninguno
655 # que dibujar. Mismo defecto que en KDE y por el mismo motivo: data de RUNTIME que no alcanza
656 # ninguna arista de BUILD.
657 #
658 # `adwaita-icon-theme` estaba escrita y SELLADA desde el frente GNOME y no la declaraba nadie. Su
659 # propia cabecera explica por qué la escribieron: mutter avisa `No cursor theme available` y **sin
660 # tema de cursor no hay puntero visible** con el cursor por software que obliga virtio-gpu. Los
661 # XCursor viven ahí adentro (`Adwaita/cursors/`), no en un paquete aparte.
662 # `hicolor-icon-theme` va explícito aunque Adwaita lo declare `Inherits=hicolor`: la herencia se
663 # resuelve en RUNTIME leyendo el `index.theme` del padre, así que el padre tiene que estar
664 # INSTALADO. Desde hoy se resuelve contra el corpus (misma receta, mismo hash).
665 "adwaita-icon-theme", "hicolor-icon-theme",
666 # ── EXTENSIONES DEL SHELL ────────────────────────────────────────────────────────────────────
667 # ⚠ Añadida el 2026-09-09: esta imagen traía la CÁSCARA de GNOME y ni una extensión. No es un
668 # detalle estético — GNOME manda un shell deliberadamente mínimo y el ecosistema lo completa por
669 # acá; sin ninguna, el escritorio es lo que el proyecto entrega y nadie usa tal cual.
670 # El shell SÍ sabe cargarlas sin nada más: `ui/extensionSystem.js` va en el gresource sin opción
671 # que lo apague y su gschema sellado trae `enabled-extensions`. Lo que faltaba era que hubiera
672 # alguna, y va de RAÍZ por la lección de `foot` que este fichero ya aprendió tres veces.
673 # ⚠ NO hace falta glib-networking ni TLS: eso sólo lo pide BAJARLAS desde extensions.gnome.org
674 # con el descargador del shell. Empaquetada, se instala como cualquier receta.
675 # La receta se construye desde el REPO del autor (tag v72), no desde el zip de EGO: hammer extrae
676 # con `tar -x` y GNU tar no lee zip, y además el zip es un artefacto que construye EGO a partir
677 # de ese mismo repo. El layout instalado se verificó CONTRA ese zip: idéntico, sin sobras ni
678 # faltantes.
679 # ⚠ La LISTA de activas no vive en ninguna receta: `enabled-extensions` es un ARRAY y un override
680 # de GSettings lo escribe entero, así que dos recetas con el suyo no se suman — ganaría la última
681 # y las otras quedarían instaladas y MUERTAS, sin error. La arma `scripts/gnome/gnome-start-qemu.sh`
682 # enumerando los uuid presentes en /usr/share/gnome-shell/extensions antes de compilar los
683 # esquemas. Una receta no puede ser ese sitio: hammer exige `[source]` en toda receta.
684 "gnome-shell-extension-blur-my-shell",
685 # `dash-to-panel` es la que más cambia el uso diario: junta dash, ventanas y bandeja en una barra.
686 # `appindicator` devuelve los ICONOS DE BANDEJA que GNOME quitó — sin ella, las apps que usan
687 # StatusNotifierItem corren y no tienen dónde mostrarse.
688 "gnome-shell-extension-dash-to-panel", "gnome-shell-extension-appindicator",
689 # ── Y LA APP QUE GNOME NO INSTALA Y TODO EL MUNDO NECESITA ───────────────────────────────────
690 # `gnome-tweaks`: tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que GNOME
691 # esconde, y el interruptor de las extensiones. Es una app de PYTHON, así que su receta declara
692 # `python3` y `py3-gobject` como deps de RUNTIME: sin ellas en la imagen se instala y no arranca,
693 # y ningún build puede delatarlo.
694 "gnome-tweaks",
695 # ── RAÍCES DE RUNTIME QUE EL GRAFO NO PUEDE VER (traídas acá el 2026-09-09) ───────────────────
696 # ⚠ Estas once vivían SÓLO dentro de `scripts/gnome/hydrate-gnome.sh`, en una lista escrita a
697 # mano. O sea DOS fuentes de verdad para «qué lleva la imagen de GNOME», y habían divergido en
698 # los DOS sentidos: el script no conocía 21 paquetes que este perfil declara —entre ellos el
699 # navegador, la terminal, el editor, el visor de PDF, las fuentes y las tres extensiones— y este
700 # perfil no conocía estas once, sin las cuales el shell no arranca. Es exactamente el modo de
701 # fallo que la cabecera de `scripts/hydrate-profile.py` describe y que `vigia-imagen.py` existe
702 # para cazar.
703 # Ninguna es dep de BUILD de nadie: el shell las carga por `imports.gi.*` al arrancar, y eso es
704 # invisible para la clausura de deps. La justificación de cada una la traía el script:
705 "accountsservice", # imports.gi.AccountsService
706 "gi-foreign-typelibs", # DBus-1.0 / cairo-1.0, que Gdk y Atspi incluyen
707 "gi-girepository-typelib", # lo pide el propio gjs desde su JS embebido
708 "libgdm", # imports.gi.Gdm — el shell lo importa SIN condición al arrancar
709 "geoclue", # imports.gi.Geoclue
710 "libgweather", # imports.gi.GWeather
711 "upower", # imports.gi.UPowerGlib
712 "librsvg", # imports.gi.Rsvg
713 "ibus", # imports.gi.IBus
714 # Ni typelib ni librería: los GSCHEMAS de gnome-settings-daemon. El demonio está aparcado
715 # (GTK3/X11) pero el shell lee sus esquemas al armar los quick settings, y si faltan la excepción
716 # ocurre DENTRO de Main.start() ⇒ el panel no se arma y la pantalla queda NEGRA.
717 "gsd-schemas",
718 # `wireplumber` es lo que le da DISPOSITIVOS a PipeWire (que ya entra por la clausura): sin él hay
719 # socket de audio y nada que suene.
720 "wireplumber",
721 # ── LOS DOS QUE LA IMAGEN INYECTABA A MANO Y NINGÚN PERFIL DECLARABA ─────────────────────────
722 # Hallados el 2026-09-12 por `targets.py --servicios escritorio-gnome`, que los rechazó con «lo
723 # declara `arje-logind-compat`, que NO pertenece al perfil». No era falso positivo: los dos
724 # estaban en CERO perfiles y sin embargo `scripts/gnome/qemu-desktop-image.sh` los copia al
725 # rootfs a mano, y el de COSMIC hace `exit 1` si falta logind-compat. O sea: dos binarios
726 # imprescindibles, presentes en la imagen y ausentes del destino declarado — la misma forma del
727 # agujero de `foot`, encontrada esta vez por una comprobación en vez de por una imagen inusable.
728 "arje-logind-compat", "arje-polkit-compat",
729 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
730 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
731 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
732 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
733 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
734 # una sola copia.
735 #
736 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
737 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
738 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
739 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
740 # recetas: son tres perfiles sin «enable» ninguno.
741 #
742 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
743 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
744 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
745 "cups", "bluez",
746 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
747 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
748 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
749 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
750 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
751 # ⚠ CORREGIDO EL MISMO DÍA: esta nota decía «NO declarar esto en escritorio-kde, allá plasma-nm ya
752 # arrastra la variante de la cola». **Ya no hay variante de cola**: `incoming-kde/networkmanager`
753 # se retiró y `networkmanager-qt` resuelve al padre, así que la distro tiene UN solo
754 # NetworkManager y KDE pasó a tener WiFi (la suya iba con `-Dwifi=false`). Costó 2 rebuilds
755 # —networkmanager-qt y plasma-nm—, medidos con `yupana radio` antes de tocar nada.
756 # KDE sigue sin listarlo entre sus `paquetes` porque no hace falta: `plasma-nm` lo arrastra por
757 # clausura. Lo que sí hacía falta era arrancarlo, y está en su lista de `servicios`.
758 "networkmanager",
759]
760
761# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
762# Hoy estos ocho los lanza a mano `scripts/gnome/gnome-start-qemu.sh` con `&`, sin supervisión, sin
763# backoff y sin el `CRASHED` real — o sea sin nada de lo que arje es PID 1 para dar. Declararlos acá
764# no los mueve todavía: los pone en el mismo sitio que el resto del destino de la imagen, y hace que
765# `scripts/targets.py --servicios escritorio-gnome` pueda contradecir a la realidad en vez de que la
766# realidad viva sólo dentro de un script de 500 líneas.
767#
768# Los tres de SESIÓN (pipewire, pipewire-pulse, wireplumber) salen con AVISO a propósito: fuera de
769# mirada nadie entrega Cards de sesión, así que están declarados y NO arrancados por arje. Un hueco
770# escrito se ve; uno omitido, no.
771servicios = [
772 "dbus-system", "logind-compat", "polkit-compat", "accounts-daemon", "upowerd", "colord",
773 "pipewire", "pipewire-pulse", "wireplumber",
774 # 2026-09-13: los dos que nacieron con `cups` y `bluez`. Sin esta línea la imagen los traía
775 # instalados y apagados, que es la mitad inútil de tenerlos.
776 "cupsd", "bluetoothd",
777 "NetworkManager", # 2026-09-13: el perfil no tenía gestor de red; ver el paquete arriba
778]
779
780[perfil.escritorio-cosmic]
781descripcion = "escritorio COSMIC (System76, Rust sobre smithay/iced) desde fuente — 4º escritorio"
782cola = "incoming-cosmic"
783# ⚠ HEREDA `cli` (y por transitividad `base`) desde el 2026-09-09. Antes no heredaba nada, y la
784# consecuencia estaba medida y no escrita: **de los 27 paquetes de `base`, la clausura de este
785# perfil alcanzaba 2**. Comprobado contra el rootfs de la imagen: sin `bash`, sin `sudo`/`doas`, sin
786# `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** — o sea sin con qué pedir una IP. Lo que había
787# (`ip`, `mount`, `fsck`) eran APPLETS DE BUSYBOX: `/sbin/ip → ../bin/busybox`.
788# No era una decisión: era que la composición entre perfiles no estaba declarada. `escritorio-sway`
789# ya heredaba `cli` desde el 2026-08-07 y nadie lo replicó en los otros tres.
790# El mecanismo YA EXISTÍA en `scripts/targets.py` —transitivo, con detección de ciclos y orden
791# preservado—, así que esto es una línea, no una implementación. Ver SDD 27.
792hereda = ["cli"]
793# Las raíces son EXACTAMENTE lo que `cosmic-session/src/main.rs` levanta, más el gestor mismo y los
794# datos que ningún `[deps]` alcanza. El resto es CLAUSURA.
795#
796# ── POR QUÉ ESTE PERFIL NO ES BUROCRACIA ────────────────────────────────────────────────────────
797# Sin él, `yupana radio` sobre cualquier receta COMPARTIDA —libinput, libxkbcommon, libudev-zero,
798# mesa, wayland— devuelve «IMÁGENES afectadas: (ninguna declarada)» para COSMIC, y el próximo que las
799# toque MIDE DE MENOS. La campaña existe en el store y en el runbook, pero el ábaco no la ve: el radio
800# de un re-hash es justo lo que el perfil convierte en dato.
801#
802# Nota de nombres, que acá no coinciden y ya costó una vez: el repo `cosmic-workspaces-epoch` produce
803# el binario `cosmic-workspaces`, y el AppID tampoco lleva el sufijo. Se lista por el nombre de la
804# RECETA, que es el del repo.
805paquetes = [
806 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
807 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
808 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
809 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
810 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
811 # lecturas del modelo de composición.
812 "desktop-file-utils",
813 "cosmic-comp", "cosmic-session", "cosmic-settings-daemon",
814 "cosmic-panel", "cosmic-launcher", "cosmic-app-library", "cosmic-workspaces-epoch",
815 "cosmic-notifications", "cosmic-osd", "cosmic-bg", "cosmic-idle",
816 # Los applets del panel. No es un componente que la sesión lance: lo spawnea el PANEL, por AppID,
817 # y sin ellos arranca y no se dibuja. Un binario multiplexor con veinte symlinks.
818 "cosmic-applets",
819 # El MOTOR del lanzador, de otro repo y fuera del pin `epoch-N`: `cosmic-launcher` sólo dibuja y
820 # lanza a `pop-launcher` como proceso hijo por PATH. Sin él, `Super` no abre nada.
821 "pop-launcher",
822 # Y el `qalc` al que el plugin `calc` le pasa la expresión: tercer nivel de hijo por PATH. Es la
823 # primera raíz del perfil que existe para que una TECLA dé un resultado, no para que algo enlace.
824 "libqalculate",
825 # La primera APLICACIÓN con ventana del catálogo, y la primera entrada de la biblioteca que no es
826 # `NoDisplay`. Hasta acá el perfil listaba escritorio; desde acá lista también lo que se abre.
827 "cosmic-term",
828 # La segunda: el gestor de ficheros. Sin `gvfs` (no hay glib compartida) ⇒ navegación local sí,
829 # montajes remotos no.
830 "cosmic-files",
831 # La tercera: el panel de control. Habla con los daemons por zbus, así que NO arrastra
832 # NetworkManager/udisks/accountsservice — sólo libdrm y dav1d sobre el juego fijo.
833 "cosmic-settings",
834 # La cuarta: el editor de texto, y la más barata de las cuatro — su árbol ya estaba compilado (usa
835 # `cosmic-files` como crate, igual que la terminal). Su binario enlaza SÓLO `libxkbcommon` y
836 # `libc.so`: dos NEEDED, el mínimo de todos los clientes de la suite.
837 "cosmic-edit",
838 # La quinta: la tienda. Con el backend `pkgar` —el único de los cuatro que no pide ni una torre de
839 # C (flatpak) ni un demonio ausente (packagekit)— NAVEGA el catálogo instalado leyendo el AppStream
840 # del sistema, pero NO instala: nada está cableado al `.swm` de hammer.
841 "cosmic-store",
842 # La sexta: la captura de pantalla. Cero C —el único `[deps] build = []` de la campaña— pero HOY
843 # NO FUNCIONA: todo su trabajo es `Screenshot::request()` contra `org.freedesktop.portal.Desktop`,
844 # y en la imagen no hay portal. Se lista igual para que el hueco sea visible.
845 "cosmic-screenshot",
846 # La CADENA DEL PORTAL, dos raíces porque son dos procesos que se encuentran por D-Bus en runtime:
847 # el frontend registra `org.freedesktop.portal.Desktop` y enruta al backend, que implementa
848 # Access/FileChooser/Screenshot/Settings/ScreenCast. Sin ellos no hay captura, ni selector de
849 # ficheros de portal, ni compartir pantalla.
850 "xdg-desktop-portal", "xdg-desktop-portal-cosmic",
851 # Y la SONDA con que se mide esa cadena. No es escritorio: es el instrumento. Se lista como raíz
852 # porque nadie la declara como dep y sin ella el perfil no incluiría con qué verificarse a sí
853 # mismo — un handshake de portal sólo se puede ejercer DESDE la sesión, con bus y compositor vivos.
854 "portal-probe",
855 # El GESTOR DE SESIÓN de pipewire. Se lista como raíz porque ningún `[deps]` lo alcanza: es un
856 # CLIENTE del demonio, no una librería de nadie. Trae consigo la glib COMPARTIDA (`glib-shared`),
857 # que no es la estática que usa el resto de la cola — wireplumber produce `.so` y la estática del
858 # corpus no es PIC. Construido y corriendo; NO desbloqueó ScreenCast (ver el runbook).
859 "wireplumber",
860 # Datos, no binarios: el tema de iconos (y el hicolor del que hereda), las tablas de xkb y una
861 # tipografía. Ninguno es dep de build de nadie y sin ellos el escritorio arranca roto — sin puntero,
862 # sin teclado o con tofu.
863 "cosmic-icons", "hicolor-icon-theme", "xkeyboard-config", "dejavu-fonts",
864 # ⚠ «sin puntero» lo decía este comentario desde que se escribió y NO era cierto: `cosmic-icons`
865 # instala iconos de aplicación, no cursores, así que este perfil llegó a 43/43 con el puntero
866 # INVISIBLE. Lo cazó `vigia-imagen.py` el 2026-09-04. Los cursores son su propia raíz.
867 "adwaita-cursors",
868 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
869 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
870 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
871 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
872 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
873 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
874 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
875 "dejavu-fonts-nerd",
876 # Lo que `start-cosmic` ejecuta y ningún `[deps]` pide: bash de verdad y el bus de sesión.
877 "bash", "dbus",
878 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
879 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
880 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
881 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
882 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
883 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
884 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
885 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
886 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
887 "mpv",
888 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
889 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
890 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
891 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
892 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
893 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
894 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
895 "atuq",
896 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
897 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
898 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
899 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
900 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
901 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
902 # así que la imagen necesita además a alguien escuchando ese nombre.
903 "libnotify",
904 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
905 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
906 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
907 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
908 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
909 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
910 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
911 "llama-cpp",
912 "ia-modelo-chat",
913 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
914 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
915 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
916 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
917 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
918 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
919 "swayimg",
920 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
921 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
922 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
923 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
924 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
925 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
926 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
927 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
928 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
929 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
930 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
931 "zathura", "zathura-pdf-poppler",
932 # ── LAS `.so` QUE mesa Y wayland PIDEN EN RUNTIME Y NINGÚN ARTEFACTO DEL CIERRE PROVEE ────────
933 # Medido el 2026-09-03 recorriendo los NEEDED de todo el cierre de esta imagen contra los SONAME
934 # que ese mismo cierre publica: `libEGL.so.1` pide `libexpat.so.1` y `libwayland-client.so.0` pide
935 # `libffi.so.8`, y las recetas canónicas de expat y libffi son `--disable-shared`. O sea que el
936 # rootfs hidratado los resolvía contra el **sysroot Alpine DEL LAB**.
937 # Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la fuga es invisible para
938 # el store — el artefacto se da por bueno y sólo arranca en una máquina con Alpine debajo.
939 # No es un descubrimiento nuevo: es EXACTAMENTE lo que el perfil `escritorio-sway` ya documentó
940 # para `sway`, sólo que ahí se encontró a mano al armar la imagen y estas tres nunca se revisaron.
941 # Van de raíces (y no de `[deps]`) por lo mismo que las fuentes y el XKB: son data de RUNTIME y
942 # nadie las declara como dep de BUILD, así que la clausura no puede alcanzarlas por ninguna arista.
943 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
944 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
945 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
946 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
947 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
948 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
949 # partes ⇒ un solo artefacto, cero rebuilds).
950 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
951 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
952 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
953 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
954 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
955 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
956 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
957 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
958 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
959 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
960 "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
961 # `arje-logind-compat` (2026-09-12): mismo hallazgo que en `escritorio-gnome` — la imagen lo
962 # inyecta a mano y `scripts/cosmic/qemu-desktop-image.sh` hace `exit 1` si falta, pero no estaba
963 # en ningún perfil. COSMIC NO usa `arje-polkit-compat` (verificado: sus scripts no lo nombran),
964 # así que va sólo este.
965 "arje-logind-compat",
966 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
967 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
968 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
969 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
970 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
971 # una sola copia.
972 #
973 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
974 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
975 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
976 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
977 # recetas: son tres perfiles sin «enable» ninguno.
978 #
979 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
980 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
981 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
982 "cups", "bluez",
983 # `cliphist` (2026-09-12): historial de portapapeles. El portapapeles de Wayland muere con el
984 # cliente que lo ofrece —cerrás la terminal y lo copiado se va—; esto lo persiste. Va SÓLO en
985 # este perfil y en COSMIC: habla `wlr-data-control`, que KWin y mutter NO implementan (mismo
986 # muro que `wf-recorder`). Declararlo donde no funciona sería peor que no tenerlo.
987 "cliphist",
988 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
989 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
990 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
991 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
992 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
993 # ⚠ NO declarar esto en `escritorio-kde`: allá `plasma-nm` ya arrastra la variante de la cola, y
994 # dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib.
995 "networkmanager",
996 # `upower` (2026-09-13): **COSMIC dibujaba un indicador de batería sin nadie que se la contara.**
997 # El demonio no estaba en la clausura: la receta vivía en `incoming-kde` y en `incoming-gnome`,
998 # las dos inalcanzables desde acá. La variante del corpus va con `-Dpolkit=disabled` —polkit en
999 # upower sólo gobierna suspender/hibernar, que además está deprecado; reportar carga y tiempo
1000 # restante no pasa por ahí— y con eso la cadena cierra sin meter un polkit de verdad al corpus.
1001 "upower",
1002]
1003
1004# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
1005# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
1006# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
1007# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
1008# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
1009#
1010# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
1011# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
1012# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
1013# `perfiles` de los cinco `build-state*.json`, no a ojo.
1014#
1015# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
1016# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
1017#
1018# ⚠ AUSENCIA DESTAPADA POR EL CRUCE: **no hay `upowerd`**. COSMIC dibuja un indicador de batería y
1019# el demonio que lo alimenta no está en la clausura ⇒ en un portátil el applet no tiene datos.
1020# Deuda del perfil, no de esta lista.
1021servicios = [
1022 "dbus-system", "logind-compat", "cupsd", "bluetoothd",
1023 "pipewire", "pipewire-pulse", "wireplumber",
1024 "NetworkManager", # 2026-09-13
1025 "upowerd", # 2026-09-13: el indicador de batería deja de mirar al vacío
1026]
1027
1028[perfil.escritorio-sway]
1029descripcion = "WM Wayland ligero: sway sobre wlroots propio, sin X11 y sin systemd"
1030cola = "incoming-wlr"
1031hereda = ["cli"]
1032# Sellado entero el 2026-08-07. A diferencia de los otros tres escritorios, éste NO tiene una torre
1033# de C debajo: son una decena de binarios pequeños sobre wlroots, y por eso salió en una noche
1034# mientras KDE y GNOME fueron campañas de semanas.
1035#
1036# `hereda = ["cli"]` a propósito: un WM ligero sin userland debajo no se usa: hace falta la terminal
1037# y las herramientas.
1038#
1039# 2026-08-26: este comentario decía que `foot` «entra por la clausura de cli, así que no se lista
1040# acá». ERA FALSO y el grafo lo desmentía: `foot` está sellado en el corpus pero `perfil.cli` no lo
1041# lista entre sus raíces, así que ningún perfil lo arrastraba. El perfil daba 121/121 SIN EMULADOR DE
1042# TERMINAL — sellado completo y aun así inusable. Se detectó al hidratar la clausura para armar la
1043# imagen, no antes: la métrica de perfil mide la clausura de las raíces declaradas, y no puede ver lo
1044# que falta en la declaración. `foot` se lista ahora donde le toca, entre las raíces del escritorio.
1045#
1046# Sólo RAÍCES: la clausura —wlroots, mesa, wayland, libinput, seatd, cairo, pango, fcft…— la calcula
1047# el grafo siguiendo las aristas de deps. Escribirla a mano sería justo lo que targets.toml existe
1048# para no hacer.
1049paquetes = [
1050 # `desktop-file-utils` (2026-09-09): `update-desktop-database` construye la caché
1051 # MIME→APLICACIÓN. Con `shared-mime-info` el escritorio ya sabe QUÉ TIPO es un fichero; sin
1052 # ésta sigue sin saber CON QUÉ abrirlo — el menú «abrir con» sale vacío y no hay aplicación
1053 # por defecto para ningún tipo. Va como RAÍZ en los CUATRO escritorios y no en `base`,
1054 # porque kde/gnome/cosmic NO heredan base: declararla acá funciona con cualquiera de las dos
1055 # lecturas del modelo de composición.
1056 "desktop-file-utils",
1057 # el compositor y sus utilidades propias
1058 "sway",
1059 # lo que hace que se USE y no sólo arranque
1060 "yambar", # barra de estado (yambar y no waybar: waybar exige gtkmm-3.0, o sea GTK3 + sus
1061 # bindings C++, que es deuda aparcada por diseño. Medido, no supuesto.)
1062 "foot", # emulador de terminal (misma familia que fcft/tllist, ya en la clausura)
1063 "fuzzel", # lanzador de aplicaciones
1064 # ── DATOS, no binarios: nadie los declara como dep de build, así que SÓLO entran como raíces ──
1065 # Ninguna receta depende de una fuente ni de un mapa de teclado para COMPILAR, de modo que la
1066 # clausura no puede alcanzarlos por más aristas que siga. Si no están acá, no están.
1067 "dejavu-fonts", # sin UNA fuente, foot muere con «monospace::size=8: failed to match font»
1068 # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
1069 # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
1070 # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
1071 # Vera obliga a renombrar una fuente modificada, así que la parcheada se llama
1072 # «DejaVuSansM Nerd Font Mono» y NO puede responder a «DejaVu Sans Mono» — sustituirla rompería
1073 # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
1074 # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
1075 "dejavu-fonts-nerd",
1076 "xkeyboard-config", # sin datos XKB: «xkbcommon: ERROR: failed to add default include path
1077 # /usr/share/X11/xkb», y sway no completa el seat
1078 # Cursores. Mismo caso que las fuentes y el XKB, y con el mismo modo de fallo silencioso: con el
1079 # cursor por software el compositor dibuja lo que le da el TEMA, y sin tema el puntero se mueve
1080 # invisible. Este perfil llegó a 173/173 sin uno. Trae además `/usr/share/icons/default`, que es
1081 # lo que buscan libXcursor y wlroots cuando nadie fijó `XCURSOR_THEME` — o sea, siempre acá.
1082 "adwaita-cursors",
1083 # ── LAS TRES `.so` QUE sway PIDE Y EL CORPUS SÓLO PRODUCE COMO `.a` ─────────────────────────
1084 # `sway` es `link = "dynamic"` a propósito (dlopea los drivers DRI de mesa) y sale con
1085 # `NEEDED libz.so.1 / libexpat.so.1 / libffi.so.8`. Las canónicas son `--disable-shared`, así que
1086 # ningún artefacto sellado producía esos `.so` y el rootfs hidratado los resolvía contra el
1087 # sysroot Alpine DEL LAB. Peor que una dep faltante: el lab NO entra en `hash_inputs`, así que la
1088 # fuga era invisible para el store — el artefacto se daba por bueno y sólo arrancaba en una
1089 # máquina con Alpine debajo. Los otros nueve NEEDED de sway (pixman, drm, evdev, input, udev,
1090 # wayland-server, wlroots, xkbcommon, libc) sí los cubre la clausura, porque esas recetas ya son
1091 # `link = dynamic`. Como las tres son data de runtime y nadie las declara como dep de BUILD, van
1092 # de raíces por la misma razón que las fuentes y el XKB de arriba.
1093 # `bzip2-shared` se suma el 2026-09-03, cazada por `scripts/vigia-sonames.py` en los TRES perfiles
1094 # a la vez: `freetype-shared` sale con `NEEDED libbz2.so.1` y el `bzip2` canónico del corpus es
1095 # `.a`. Es la misma fuga al sysroot del lab que expat y libffi, y venía de antes que zathura —
1096 # sólo que hasta ahora nadie cargaba freetype-shared en RUNTIME. Se vio de verdad al probar el
1097 # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
1098 # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
1099 # partes ⇒ un solo artefacto, cero rebuilds).
1100 # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
1101 # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
1102 # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
1103 # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
1104 # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
1105 # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
1106 # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
1107 # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
1108 # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
1109 # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
1110 "zlib-shared", "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
1111 "swaybg", # fondo de escritorio
1112 "swaylock", # bloqueo de pantalla (con PAM: linux-pam SÍ está en el corpus)
1113 "swayidle", # apagado por inactividad
1114 # el par de captura y el portapapeles, sin los cuales media docena de flujos no cierran
1115 "grim", "slurp", "wl-clipboard",
1116 # ── LA PRIMERA APP DE USUARIO FINAL QUE COMPARTEN LOS CUATRO ESCRITORIOS ──────────────────────
1117 # `mpv` vive en el CORPUS, no en esta cola, justamente para poder listarse en las cuatro: una
1118 # receta resuelve sibling-first y después el catálogo padre, así que desde acá se alcanza.
1119 # Va de raíz por la lección que este mismo fichero ya aprendió con `foot` en el perfil de sway:
1120 # una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la métrica de
1121 # clausura no lo puede ver porque mide lo declarado. Sellado ≠ instalado.
1122 # Los tres huecos con que entró (ALSA sin PipeWire, sin Lua/OSC, sin vaapi) están CERRADOS al
1123 # 2026-09-04: pipewire nativo con ALSA de respaldo, `-Dlua=lua5.2`, y `-Dvaapi=enabled` con `libva`
1124 # subida al corpus. Además su ffmpeg ya va con x86asm ⇒ decodifica con SIMD.
1125 "mpv",
1126 # ── EL NAVEGADOR (SDD 26) ────────────────────────────────────────────────────────────────────
1127 # `atuq` vive en el CORPUS por el mismo motivo que mpv: se resuelve sibling-first y después el
1128 # catálogo padre, así que desde las cuatro colas se alcanza. Va de raíz por la lección de `foot`
1129 # que este fichero ya aprendió: una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA
1130 # IMAGEN, y la clausura no lo puede ver porque mide lo declarado.
1131 # Arrastra la cadena GTK3 en sus variantes `-shared` — pango/cairo/gdk-pixbuf/harfbuzz—: con las
1132 # estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia y el navegador no llegaba a pintar.
1133 "atuq",
1134 # `libnotify` va PEGADO a atuq y por eso mismo: `libxul.so` la abre por `dlopen("libnotify.so.4")`
1135 # cuando una página pide permiso para notificar, y como no es un `NEEDED` de ningún ELF, ninguna
1136 # herramienta del repo la reclamaba — ni el vigía de sonames, que da CERO. La encontró
1137 # `scripts/test-atuq-rootfs.py --dlopen` (SDD 26 §6.10). Sin esta línea la receta está sellada y
1138 # en ninguna imagen, que es la lección de `foot` escrita cuatro renglones más arriba.
1139 # ⚠ Resuelve la mitad: libnotify no trae daemon, manda `org.freedesktop.Notifications` por D-Bus,
1140 # así que la imagen necesita además a alguien escuchando ese nombre.
1141 "libnotify",
1142 # ── LA IA LOCAL (SDD 26 §6.7) ────────────────────────────────────────────────────────────────
1143 # El motor y el modelo van de raíz porque son lo que hace que la barra lateral conteste. `atuq`
1144 # los busca en el path de producción (`/usr/bin/llama-server`, `/usr/share/takana/ia/modelo.gguf`)
1145 # y, si no están, el panel lo DICE — no se rompe, pero la función no existe.
1146 # ⚠ CUESTAN ~1,25 GiB EN CADA IMAGEN (199 M el motor + 1,04 GiB el modelo), y esto es la decisión
1147 # de que las cuatro lo lleven: una función del navegador que sólo existe en una imagen es una
1148 # trampa, porque el panel está en todas. Bajarlo de acá es bajarlo de la imagen, no de la receta.
1149 "llama-cpp",
1150 "ia-modelo-chat",
1151 # `dunst` es LA OTRA MITAD, y sólo hace falta acá: KDE la atiende con plasma-workspace, GNOME con
1152 # gnome-shell y COSMIC con cosmic-notifications; sway no tenía a NADIE escuchando
1153 # `org.freedesktop.Notifications`, así que una página que pedía notificar mandaba el mensaje al bus
1154 # y se lo comía el silencio. Con el `.service` de D-Bus que trae la receta, el daemon lo levanta el
1155 # propio bus la primera vez que alguien pide ese nombre — sin systemd y sin acordarse de lanzarlo
1156 # en la config de sway.
1157 # ⚠ No es `mako` —que es lo que usa media casa wlroots— porque ese nombre YA ESTÁ OCUPADO en el
1158 # corpus por el motor de plantillas de Python de mesa: dos artefactos homónimos en la misma imagen
1159 # es la colisión que la granja ya nos cobró. El porqué largo está en la receta.
1160 "dunst",
1161 # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ─────────────────────────────────────────────────
1162 # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con
1163 # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de
1164 # escritorio — las tres mesa van `-Dglx=disabled -Dglvnd=false` ⇒ no hay `libGL.so` ni `gl.pc` en
1165 # ninguna cola. swayimg rasteriza a `wl_shm` y no toca GL; el porqué largo está en su receta.
1166 # Trae de yapa backend DRM: muestra imágenes en una TTY pelada, sin compositor.
1167 "swayimg",
1168 # ── LECTOR DE DOCUMENTOS ─────────────────────────────────────────────────────────────────────
1169 # ⚠ NO ESTÁ EN `escritorio-kde`, y no es un olvido. `zathura-pdf-poppler` necesita `poppler-glib`,
1170 # que es una receta NUEVA del corpus (la que ya existía va `-DENABLE_GLIB=OFF` y vive en la cola
1171 # de KDE). Las dos instalan `/usr/lib/libpoppler.so.146` y son artefactos DISTINTOS ⇒ una imagen
1172 # que hidrate las dos pone dos ficheros en la misma ruta y gana el que se proyecte último, que es
1173 # el cuadro de las dos glib. KDE ya trae `okular`, que arrastra la poppler de Qt6.
1174 # Si alguna vez se quiere zathura en KDE, la salida NO es listarla acá: es jubilar una de las dos
1175 # popplers. Escrito también en `recipes/poppler-glib.toml` y `recipes/zathura.toml`.
1176 # El plugin va de raíz APARTE del visor a propósito: `zathura` no lo declara en `[deps]` de
1177 # runtime —lo carga con dlopen— así que sin esta línea la imagen traería un visor que abre la
1178 # ventana y dice «unknown file type». Sellado ≠ instalado, otra vez.
1179 "zathura", "zathura-pdf-poppler",
1180 # ── GRABADOR DE PANTALLA — sólo acá, y la razón es de PROTOCOLO, no de gusto ──────────────────
1181 # `wf-recorder` captura por `wlr-screencopy`, que implementan los compositores wlroots. KWin y
1182 # mutter NO lo implementan (usan el portal/ScreenCast), así que ponerlo en esas dos imágenes
1183 # sería enviar una herramienta que no puede funcionar ahí — deuda fantasma con forma de app.
1184 # En COSMIC habría que VERIFICAR si cosmic-comp expone el protocolo antes de listarlo.
1185 # Es la app que cobra la promoción de pipewire al corpus (2026-09-03): antes grababa muda.
1186 "wf-recorder",
1187 # ── EL TEMA DE ICONOS BASE ───────────────────────────────────────────────────────────────────
1188 # Añadido el 2026-09-03 por el barrido que destapó que KDE y GNOME corrían SIN tema de iconos.
1189 # Acá el agujero es más chico y conviene decir exactamente cuánto: los cuatro programas gráficos
1190 # de esta imagen —foot, swayimg, mpv, wf-recorder— no piden iconos de tema, y `zathura` es GTK4,
1191 # que lleva sus iconos de UI COMPILADOS en el recurso de la librería. O sea que acá no hay una
1192 # captura con la barra vacía como en las otras dos.
1193 # Lo que sí falta es el FALLBACK del estándar: sin `hicolor/index.theme` instalado, los iconos que
1194 # una aplicación instala en `usr/share/icons/hicolor/...` son invisibles para cualquier buscador,
1195 # porque ese directorio no cuenta como tema. Es el piso, y cuesta un artefacto de datos.
1196 # Un tema COMPLETO (Adwaita, Breeze) sería una decisión aparte y NO gratis: los dos viven en colas
1197 # hermanas —`incoming-gnome` e `incoming-kde`— y desde acá no se alcanza una cola hermana, así que
1198 # pedirlo es promover uno al corpus. Queda escrito para que se elija, no para que se descubra.
1199 "hicolor-icon-theme",
1200 # ── LAS DOS CAPACIDADES QUE FALTABAN EN LOS CUATRO ESCRITORIOS (2026-09-12) ──────────────────
1201 # `cups` y `bluez` eran el punto 8 de PUBLICABLE (`docs/20`), y no son «una app más»: un
1202 # escritorio que no imprime y no habla con un auricular no es un escritorio. Las dos viven en el
1203 # CORPUS —no en esta cola— por lo mismo que `mpv` y `atuq`: una receta resuelve sibling-first y
1204 # después el catálogo padre, así que desde acá se alcanzan y sirven a los cuatro perfiles con
1205 # una sola copia.
1206 #
1207 # ⚠ LA MITAD QUE ESTO NO PAGA, ESCRITA PARA QUE SE VEA. Las dos recetas declaran su `[[service]]`
1208 # (`cupsd`, `bluetoothd`) y **este perfil no los arranca**: de los cuatro escritorios, sólo
1209 # `escritorio-gnome` tiene una lista `servicios`. O sea que la imagen va a traer cupsd y
1210 # bluetoothd instalados y apagados. Es deuda declarada, no olvido — y es más grande que estas dos
1211 # recetas: son tres perfiles sin «enable» ninguno.
1212 #
1213 # ⚠ Y para cups hay un segundo tramo, anotado en su receta: `gtk3` está sellada con
1214 # `-Dprint_backends=file`, así que una app GTK3 seguirá viendo un solo destino («a fichero»)
1215 # hasta que gtk3 se re-selle. La pieza está, el sistema la ignora, y ninguna métrica lo dice.
1216 "cups", "bluez",
1217 # `cliphist` (2026-09-12): historial de portapapeles. El portapapeles de Wayland muere con el
1218 # cliente que lo ofrece —cerrás la terminal y lo copiado se va—; esto lo persiste. Va SÓLO en
1219 # este perfil y en COSMIC: habla `wlr-data-control`, que KWin y mutter NO implementan (mismo
1220 # muro que `wf-recorder`). Declararlo donde no funciona sería peor que no tenerlo.
1221 "cliphist",
1222 # `wlr-randr` (2026-09-13, barrido): sway no tenía CON QUÉ cambiar la resolución ni orientar una
1223 # pantalla. `swaymsg output` sirve si el compositor está vivo y configurado; esto es la
1224 # herramienta equivalente a xrandr para wlroots, y era otra hoja sellada sin perfil.
1225 "wlr-randr",
1226 # `wireplumber` (2026-09-13): **el audio de esta imagen no encaminaba nada.** `pipewire` está y
1227 # arranca, pero se construyó con `-Dsession-managers=[]`: sin gestor de sesión no hay política de
1228 # qué es entrada, qué es salida ni qué se enlaza con qué — el sink que ve la aplicación es
1229 # `auto_null`, o sea el nodo de descarte. Audio que parece vivo y no suena.
1230 # Vive en el CORPUS —y no en esta cola— porque desde acá no se alcanza una cola hermana: las
1231 # copias de `incoming-gnome` e `incoming-cosmic` eran invisibles para este perfil. Ver su receta:
1232 # hay tres variantes vivas a propósito y consolidarlas es el trabajo siguiente.
1233 "wireplumber",
1234 # `networkmanager` (2026-09-13): este perfil no tenía CON QUÉ gestionar una red. La receta existía
1235 # sólo en `incoming-kde` —inalcanzable desde acá— y además estaba construida con `-Dwifi=false`,
1236 # porque allá se escribió para dar `libnm.so` a plasma-nm y nada más. La variante del CORPUS va
1237 # con `-Dwifi=true` y con `nmcli` (el artefacto de KDE no lo trae, comprobado sobre el store), y
1238 # delega el handshake WPA2 en el `wpa_supplicant` que `base` declara desde hoy.
1239 # ⚠ NO declarar esto en `escritorio-kde`: allá `plasma-nm` ya arrastra la variante de la cola, y
1240 # dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib.
1241 "networkmanager",
1242]
1243
1244# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
1245# ⚠ ESTE PERFIL NO TENÍA ESTA LISTA HASTA EL 2026-09-13, y eso NO se leía como un hueco: se leía
1246# como un perfil completo. `paquetes` dice qué se INSTALA y `servicios` dice qué se LEVANTA; sin la
1247# segunda, la imagen salía con sus demonios instalados y **ninguno arrancado**, y la métrica de
1248# clausura daba N/N igual. De los cuatro escritorios sólo `escritorio-gnome` la tenía.
1249#
1250# Cada entrada es el `label` de un `[[service]]` declarado por una receta de la CLAUSURA de este
1251# perfil (no de sus raíces: la mitad viven en deps). La lista es exactamente lo que la clausura
1252# ofrece hoy — se computó cruzando los `[[service]]` del corpus y las cuatro colas contra el campo
1253# `perfiles` de los cinco `build-state*.json`, no a ojo.
1254#
1255# ⚠ Declarar NO es hacer funcionar. Los de `scope = "session"` salen con AVISO a propósito, como en
1256# GNOME: fuera de `mirada` nadie entrega Cards de sesión todavía. Un hueco escrito se ve.
1257#
1258# ⚠ MISMA AUSENCIA QUE KDE: **no hay `wireplumber`**. `pipewire` sin gestor de sesión no encamina
1259# nada. Y tampoco hay `upowerd` ni `logind-compat` — en un WM ligero eso es defendible (seatd hace
1260# el seat y `yambar` puede leer la batería de sysfs), pero es una decisión, no un hecho dado.
1261servicios = [
1262 "dbus-system", "cupsd", "bluetoothd",
1263 "pipewire", "pipewire-pulse",
1264 # 2026-09-13: los dos que se destrabaron promoviendo al corpus lo que vivía en colas hermanas.
1265 "wireplumber", "NetworkManager",
1266]
1267
1268[perfil.servidor]
1269descripcion = "la caja que SIRVE: sshd, servidor web y las herramientas de operar una máquina remota sin pantalla"
1270hereda = ["cli"]
1271# Nace el 2026-09-09 del frente «servidor de producción» (SDD 27). Existe porque las cuatro cosas que
1272# ese frente quiere —instalar takana puro en una caja remota, servir el repo, que la caja se actualice
1273# a sí misma, y migrar gioser— empiezan todas por la misma pregunta: QUÉ se instala. Hasta hoy eso no
1274# estaba declarado en ningún lado. El `sshd` del producto, por ejemplo, entra por
1275# `product-userland-from-repo.sh` y NO por este manifiesto: dos listas para lo mismo y una sola
1276# versionada como destino, que es exactamente lo que este fichero existe para no tener.
1277#
1278# ⚠ LA LECCIÓN DE `foot`, APLICADA ANTES DE PAGARLA: `escritorio-sway` daba 121/121 sellado y era
1279# inusable porque a la declaración le faltaba el emulador de terminal. La métrica mide la clausura de
1280# lo DECLARADO y no puede ver lo que falta en la declaración. Un servidor tiene su propia versión de
1281# ese agujero, y no es el software de servir: es la HORA (sin reloj, TLS y las firmas fallan), el
1282# CORTAFUEGOS y el DISPARADOR PERIÓDICO. Por eso van declarados abajo aunque todavía no existan.
1283#
1284# ⚠ LAS CUATRO ÚLTIMAS RAÍCES NO RESUELVEN A NINGUNA RECETA — a propósito, y hay que saberlo.
1285# Quedan `wanted` en el grafo, y `wanted` NO es `debt`: `drenaje.json` seguirá diciendo `deuda=0`
1286# ([[gnome-sin-fuentes]] trampa 1). Se declaran igual porque una deuda escrita y contada se ve, y una
1287# omitida no: un `perfil.servidor` sin ellas saldría N/N —completo y verde— describiendo un servidor
1288# sin hora, sin cortafuegos y sin latido. Cuenta esperada al escribirlas: **5 `wanted`**. Si al mirar
1289# `build-state.json` aparece un sexto, es un error de tipeo en un nombre, no una receta nueva.
1290paquetes = [
1291 # ── lo que existe y está sellado ──────────────────────────────────────────────────────────────
1292 # `openssh`: el acceso. Es la raíz que convierte «la caja arrancó» en «puedo entrar»; sin ella una
1293 # instalación remota deja una máquina viva e inalcanzable, que es peor que una que no arrancó.
1294 "openssh",
1295 # `caddy`: sirve `dist/repo` (índice firmado + los `.tkn`/`.swm`). TLS automático, un binario, cero
1296 # dependencias — y es el mismo que ya sirve los 19 dominios de gioser, así que la migración no
1297 # cambia de servidor web además de cambiar de sistema.
1298 "caddy",
1299 # `gitea` (2026-09-14): el servidor git que hoy corre en gioser y que la mudanza tiene que levantar
1300 # del otro lado. Entra al perfil porque es el PASO 1 del plan de mudanza visto por el lado bueno:
1301 # los 26 repos que sólo existen en la máquina que se borra dejan de estar solos en cuanto su origen
1302 # vive acá. Sus datos son 2,0 G de ficheros y una sqlite (`DB_TYPE = sqlite3`), así que mudarlos es
1303 # copiar un directorio — lo que NO es trivial es el binario, ver el encabezado de `recipes/gitea.toml`.
1304 # Su `[[service]]` está en la receta y el perfil lo arranca (abajo, en `servicios`).
1305 "gitea",
1306 # `curl`/`wget`: bajar del mirror y del repo. `takana install --repo https://…` los necesita del
1307 # lado del cliente, y son también la única forma de diagnosticar un origen caído desde la caja.
1308 "curl", "wget",
1309 # `tmux`: un build largo por SSH que muere con la sesión es un build perdido. En una caja sin
1310 # pantalla es infraestructura, no comodidad.
1311 "tmux",
1312 # `arjectl` (2026-09-14): el control en caliente del init. Sin esto una caja sólo se administra
1313 # REINICIÁNDOLA, y se descubrió pagándolo: en la primera imagen con gitea, la card falló por falta
1314 # de config, el backoff de `restart` se agotó y no había forma de pedirle a PID 1 que reintentara
1315 # (SDD 28 §6.11). `status`, `list-units`, `start`, `stop`, `restart` sobre el bus de arje.
1316 # ⚠ Va acá y NO en `base` sólo porque este frente es el que lo pagó; el argumento para subirlo es
1317 # fuerte —TODA imagen de takana corre arje-zero como PID 1 y ninguna se puede operar sin él— y es
1318 # una línea. Se deja como decisión a tomar, no como olvido.
1319 "arjectl",
1320 # `minga` (2026-09-14): **la ventana oficial al mundo**. Decisión del usuario: el árbol del código
1321 # y la cola de compilación pasan a minga, y git queda como espejo de compatibilidad. Va con su
1322 # daemon P2P escuchando (`servicios`, abajo) — no es un CLI suelto, es el punto de entrada.
1323 # ⚠ Su card NO genera la identidad: el keypair lo crea el usuario con `minga init` y la guarda
1324 # sale 78 si el repo no existe. Una caja que se inventa su identidad soberana al arrancar es peor
1325 # que una caja sin peer.
1326 "minga",
1327 # `netup` (2026-09-10): el cliente DHCP propio que levanta la red al arrancar. Entra como RAÍZ
1328 # aunque su binario YA venga dentro del `product-rootfs`, y por una razón concreta: el del producto
1329 # está congelado en el artefacto sellado del bootstrap, así que un arreglo de netup no llega nunca
1330 # a la imagen — pasó el mismo día con la ruta on-link que Hetzner necesita. Declarado acá, la
1331 # hidratación del perfil lo proyecta ENCIMA y la imagen lleva el vigente.
1332 "netup",
1333
1334 # ── declarado y NO existe todavía: la deuda del perfil, contada ───────────────────────────────
1335 # `takana`: el propio takana NO tiene receta. El corpus construye 869 recetas y no la suya, así que
1336 # hoy el binario sólo existe como `cargo build --release` sobre un clon del repo. Es EL bloqueante
1337 # de «que el servidor sirva sus paquetes a su propio host»: el host necesita `takana` instalado para
1338 # consumirlos, y no puede instalarlo desde el repo porque no está en el repo.
1339 "takana",
1340 # `chrony`: la hora. Sin NTP, un reloj que deriva rompe la validación de certificados TLS y la de
1341 # firmas con expiración — y falla con errores que no dicen «la hora», dicen «firma inválida».
1342 "chrony",
1343 # `nftables`: el cortafuegos. `dev.gioser.net` está hoy expuesto a internet con fuerza bruta a root
1344 # en el journal; una caja nueva no debería nacer así.
1345 "nftables",
1346 # `cronie`: el disparador periódico. El latido de la granja son tres líneas de crontab, y en una
1347 # distro sin systemd no hay timers: o hay cron, o el latido se vuelve una tarjeta de arje-zero. Es
1348 # una decisión pendiente, y se declara para que se decida en vez de descubrirse.
1349 "cronie",
1350 # `logrotate`: en una caja que sirve, el log de acceso crece hasta llenar el disco. Caddy rota el
1351 # suyo por configuración, pero nada más lo hace.
1352 "logrotate",
1353 # `wireguard-tools` (2026-09-12): el kernel de esta distro YA trae WireGuard (SDD 22), y hasta hoy
1354 # no había con qué configurarlo — la caja podía hablar el protocolo y no había forma de decirle
1355 # con quién. Es la misma figura que el cortafuegos y el reloj de arriba: capacidad presente en el
1356 # kernel y ausente en el userland.
1357 "wireguard-tools",
1358]
1359
1360# ── QUÉ ARRANCA (el «enable»; SDD 30 §3) ────────────────────────────────────────────────────────
1361# `paquetes` dice qué se INSTALA; esto dice qué se LEVANTA. Son dos hechos distintos y hasta hoy la
1362# distro sólo sabía escribir el primero: el `sshd` de la imagen del producto arrancaba porque su
1363# Card estaba escrita a mano en una constante de Rust (`takana_bootstrap::SSHD_SERVICE_CARD`), no
1364# porque alguien lo hubiera declarado en ningún sitio.
1365#
1366# Cada entrada es el `label` de un `[[service]]` declarado por una receta DEL PERFIL.
1367# `scripts/targets.py --servicios servidor` resuelve label → receta → exec, y además hace la
1368# comprobación INVERSA: avisa de los paquetes de la imagen que TRAEN un demonio que nadie arranca.
1369# Es la versión servicios de la lección de `foot` — la métrica mide la clausura de lo declarado y no
1370# puede ver lo que falta en la declaración. Comprobado antes de escribir esta línea: el resolutor
1371# avisaba «`openssh` está en la imagen y TRAE este servicio, pero el perfil no lo arranca».
1372# `gitea` (2026-09-14): el servidor git de la mudanza. Se habilita acá y el CÓMO lo dice su receta.
1373# ⚠ Su card comprueba dos cosas del SITIO antes de encarnar —`/etc/gitea/app.ini` y el usuario
1374# `gitea` en `/etc/passwd`— y sale con 78 si falta alguna. Es a propósito: las dos vienen con los
1375# datos de la mudanza, y un gitea que arranca sin su config ofrece el asistente de «crear
1376# administrador» a quien pase. Hasta que la mudanza traiga ambas, este servicio NO levanta y lo dice.
1377servicios = ["sshd", "gitea", "caddy", "minga"]