# Runbook — COSMIC, el cuarto escritorio Estado al 2026-08-03: **COSMIC COMPONE EN QEMU Y UN CLIENTE PINTA.** `cosmic-comp` arranca sobre el kernel de hammer con arje-zero como PID1, toma el DRM, expone `wayland-1` y presenta frames; y `cosmic-bg` se conecta, entrega su buffer y el fondo aparece **con el color exacto que se le configuró** — `(0.13,0.29,0.53)` → `(33,74,135)`. O sea que la cadena cliente→compositor→KMS funciona de punta a punta. Este documento se escribe mientras se hace, no después. ![cosmic-bg pintando en QEMU](../evidencia/cosmic-bg-qemu-2026-08-03.png) Que el color sea **el que se pidió** y no uno cualquiera es lo que hace de esto una medición y no una impresión: descarta que se esté viendo un buffer sin inicializar o el borrado del compositor. El paso anterior —el compositor solo, gris `(39,41,42)` y el cursor, 134 colores— quedó en [`cosmic-comp-qemu-2026-08-03.png`](../evidencia/cosmic-comp-qemu-2026-08-03.png), porque la diferencia entre las dos imágenes es exactamente la pregunta que el modo `bare` separa. COSMIC es el escritorio de System76, en Rust sobre **smithay** (compositor) e **iced/libcosmic** (clientes). Es el cuarto de hammer, tras [mirada](mirada-usb-nvidia.md), [KDE Plasma 6](kde-qemu-desktop.md) y [GNOME](gnome-qemu-desktop.md). ## Por qué NO se parece a los dos anteriores KDE y GNOME costaron lo que costaron por la misma razón: una torre de C con Qt o glib/GObject/introspección debajo, donde cada capa se paga entera antes de ver un píxel. COSMIC no tiene esa torre. La primera receta —`cosmic-session`— salió **a la primera y sin una sola dep en C**: ELF estático, cero `NEEDED`. Lo que sí hereda es lo que ya está pago: | lo que COSMIC necesita | de dónde sale | costo | |---|---|---| | las deps en C de smithay (drm, gbm, seat, input, udev, xkb, pixman) | `mirada-compositor`, que ya es un compositor smithay de este lab | **cero** — misma lista | | `libdisplay-info` + `hwdata` | copiadas byte a byte de la cola GNOME | **cero** — mismo hash, artefacto ya sellado | | `bash`, `dbus-run-session`, `hicolor-icon-theme`, mesa-llvmpipe | corpus | cero | | el andamiaje de arranque en QEMU (`run-qemu-desktop.sh`, base rootfs, console-getty, screendump) | campañas KDE/GNOME | cero | La deuda propia que sí trae, y que ninguna de las otras campañas tocó, es la del **unwinder de la std de rustc** cuando el enlace no es estático — la misma que ya está documentada para las recetas Cargo del corpus. ## El pin es de la SUITE, no de cada paquete COSMIC versiona **todos** sus repos en lockstep con el tag `epoch-N`. Comprobado repo por repo: los doce componentes están en `epoch-1.5.0`. **No mezclar epochs entre piezas.** Comparten protocolos propios (`cosmic-protocols`) y el formato de configuración (`cosmic-config`); un desajuste ahí no da error de compilación, da un escritorio que arranca y no se entiende consigo mismo — que es mucho más caro de diagnosticar. ## La cola `recipes/incoming-cosmic/`, aislada (otro agente comparte el repo; nunca `git add -A`). Las deps se resuelven **hermano → padre**: desde `incoming-cosmic/` se ven las recetas de `recipes/`, pero **no** las de `incoming-gnome/` ni `incoming-kde/`. Por eso `libdisplay-info` y `hwdata` entran copiadas. **Copiar es gratis sólo si TODA la clausura transitiva resuelve a lo mismo.** El ArtifactHash no depende de la ruta de la receta, así que un fichero idéntico *cuyas deps resuelven igual* da el mismo hash ⇒ cache hit sobre el artefacto que ya existe. Se cumplió con `libdisplay-info`, `hwdata` y `nasm`, y se verificó con `hammer hash` **antes** de copiar (`b3:260f0519`, `b3:bdc36cf9`, `b3:33383070`), no después. Dónde deja de cumplirse —y por qué importa— está en la sección de `cosmic-settings-daemon`, más abajo. ## Qué levanta la sesión, y por lo tanto el orden de ataque `cosmic-session/src/main.rs` es la lista autoritativa —el mismo método que cerró la capa JS de GNOME: *leé el fichero donde el programa declara lo que arranca, no lo descubras de a uno*: ``` cosmic-comp → cosmic-notifications · cosmic-panel · cosmic-app-library → cosmic-launcher · cosmic-workspaces · cosmic-osd · cosmic-bg · cosmic-idle ``` Más `cosmic-settings-daemon` y `xdg-desktop-portal-cosmic` fuera de esa lista. | pieza | estado | hash | |---|---|---| | `cosmic-session` | ✅ sellado | `b3:81f02351` | | `cosmic-icons` | ✅ sellado (671 SVGs) | `b3:94d06f77` | | `cosmic-bg` | ✅ sellado (estático) | `b3:7b4962f0` | | `dav1d` 1.5.4 | ✅ sellado (dep arrastrada, ver gotcha 8) | `b3:aab7a874` | | **`cosmic-comp`** | ✅ **SELLADO — el gate** | `b3:b8abc52f` | | `cosmic-idle` | ✅ sellado (estático) | `b3:175632b7` | | `cosmic-osd` | 🔨 en construcción | — | | `cosmic-panel` · `cosmic-launcher` · `cosmic-app-library` · `cosmic-workspaces-epoch` · `cosmic-notifications` | receta escrita, sin construir | — | | `cosmic-settings-daemon` | ni receta | — | **El orden no es el de la lista de arranque.** `cosmic-bg` va antes que el panel a propósito: habla Wayland con `smithay-client-toolkit` y **no toca libcosmic ni iced**, mientras que panel, launcher, osd y workspaces son todos clientes de iced. Con el fondo sellado, «pinta el fondo pero no el panel» separa la capa de UI del transporte; sin él, las dos preguntas llegan juntas. ### `cosmic-settings-daemon`: por qué queda al final y NO se resuelve copiando Es el único con cadena propia. Su workspace incluye `audio-server` como dep de path **no opcional**, que arrastra `cosmic-pipewire` → `pipewire-sys` → `libpipewire-0.3` por pkg-config. Y `pipewire` hoy vive sólo en las colas GNOME y KDE. **Acá el truco de copiar deja de ser gratis, y eso corrige lo que dice más arriba.** La regla completa es: *copiar una receta entre colas es gratis sólo si **toda su clausura transitiva** resuelve a lo mismo.* Se cumplió con `libdisplay-info`, `hwdata` y `nasm` —sus deps están todas en el corpus—, y **no** se cumple con pipewire: de sus catorce deps, cinco viven sólo en colas ajenas (`dbus-shared`, `alsa-lib`, `libsndfile`, `pulseaudio`, `zlib-shared`) y —lo decisivo— **`glib` resolvería distinto**. Desde `incoming-gnome`, `glib` es la sombra de esa cola (`b3:f6ccdf98`); desde `incoming-cosmic` sería la del corpus (`b3:93d2cad0`). Son recetas distintas, no la misma en dos lugares. O sea que copiar produciría un **segundo pipewire** construido contra otra glib, arrastrando su cadena entera. Y eso no es sólo caro: es exactamente el cuadro que la campaña GNOME ya midió y evitó —dos glib distintas con 370 rutas solapadas y dos registros de GType en un proceso—. Antes de tocar nada se midió con `yupana radio` (la puerta única, nunca `grep`): `pipewire` tiene radio 1 en GNOME y 2 en KDE, pero `glib` del corpus tiene **21 dependientes transitivos y afecta la imagen escritorio-gnome**. La decisión correcta no es de esta campaña: es *dónde vive pipewire en el corpus*, y es una decisión de catálogo, no de COSMIC. Hasta entonces, el escritorio arranca sin settings-daemon —se pierden brillo, tema automático y perfiles de audio, no el escritorio—. ## 🚪 EL GATE PASADO: cosmic-comp compila (2026-08-03) `b3:b8abc52f`, 51 min 39 s de cargo con `CARGO_BUILD_JOBS=1` y `lto = "fat"`. **A la primera**, salvo el `--bin` del gotcha 1. Sin un solo parche a la fuente. Lo que dice el enlace es la parte que vale, porque es la medición y no la impresión: ``` NEEDED: libdisplay-info.so.2 · libgbm.so.1 · libseat.so.1 · libudev.so.1 libinput.so.10 · libpixman-1.so.0 · libxkbcommon.so.0 · libc.so ``` Ocho, y las siete primeras son **exactamente** los backends de smithay que la receta declara. La octava es musl. **No hay `libgcc_s`** — o sea que el compositor no arrastra la deuda del unwinder que sí tienen varias recetas Cargo del corpus, porque acá el enlace es dinámico contra musl y no hay `+crt-static` que forzar. Y **no aparece nada que no se haya declarado**: la clausura de build y la de runtime coinciden, que es la propiedad que uno quiere y casi nunca se cumple gratis. Que un compositor Wayland completo —DRM, GBM, EGL, libinput, seat, Vulkan, XWayland, un renderer multi-GPU y una capa de UI en iced— cierre con ocho librerías compartidas y ningún parche es el resultado más limpio de las cuatro campañas de escritorio. ## 🖥 EL PRIMER ARRANQUE: qué falló y qué no (2026-08-03) Tres imágenes y tres causas distintas, todas medidas. Vale dejarlas porque **ninguna estaba en el camino que uno anticipa**, y las tres se manifestaron a varias capas de distancia de su causa. **1. `cosmic-session` no es opcional en el sentido que yo asumí — y `cosmic-settings-daemon` tampoco.** El compositor arrancó perfecto y le devolvió a la sesión su `WAYLAND_DISPLAY=wayland-1`; el gestor entonces intentó lanzar `cosmic-settings-daemon`, no lo encontró, y **panickeó**: ``` INFO cosmic_session: got environmental variables from cosmic-comp: [("WAYLAND_DISPLAY", "wayland-1")] ERROR panic: 'failed to start settings daemon: NotFound' src/main.rs:255 ``` Es un `.expect()`, sin feature que lo apague. **Esto corrige lo que decía la sección de cosmic-settings-daemon**: no es que sin él se pierdan el brillo y el tema, es que **no hay sesión**. Queda por eso el modo `bare` en `cosmic-start` —el camino que upstream ya soporta como `install-bare-session`—: se lanza el compositor solo y los clientes a mano contra su WAYLAND_DISPLAY. Separa «¿compone?» de «¿arranca la sesión?», que son dos preguntas y estaban viniendo juntas. **2. La pantalla negra era `libz.so.1`.** El corpus trae `zlib` estática, que le alcanza a todo el cierre de COSMIC. Pero `kms_swrast_dri.so` es un `.so` que mesa abre con `dlopen` y NEEDea `libz.so.1`: ``` MESA-LOADER: failed to open kms_swrast: Error loading shared library libz.so.1 WARN cosmic_comp::backend::kms: Failed to add device /dev/dri/card0: Failed to initialize GBM device ``` Sin driver de software no hay GBM, y **el compositor arranca igual y presenta una pantalla negra**. La causa está a tres capas del síntoma. Se arregla agregando `zlib-shared` como raíz del hidratador. **3. Los clientes NO pueden ser estáticos.** `cosmic-bg` moría con «The wayland library could not be loaded» **teniendo `libwayland-client.so.0` en el rootfs**. `wayland-client` entra con la feature `dlopen`: carga libwayland en runtime en vez de enlazarla, y **un binario musl estático no tiene dlopen funcional**. No faltaba la librería: faltaba poder abrirla. Todos los clientes pasan a `link = "dynamic"`. **Lo que NO falló, y conviene decirlo:** el compositor no necesitó un solo parche, ni una perilla de KMS, ni forzar modeset legacy. Las cuatro perillas de render por software heredadas de KDE y GNOME (`GBM_ALWAYS_SOFTWARE`, `kms_swrast`, `LP_NUM_THREADS=1`) alcanzaron. GNOME costó cinco muros para llegar a este punto; COSMIC costó tres, y ninguno era del compositor. ### Dos carreras con /run, la misma causa `dbus-daemon --system` falló con «Failed to bind socket /run/dbus/system_bus_socket: No such file or directory» **aunque el directorio se creaba al principio del script**, y dbus-run-session se quejó por su lado de que `XDG_RUNTIME_DIR` no existía. Entre el arranque del getty y el uso pasan varios segundos en los que arje-zero sigue montando, y un tmpfs sobre `/run` se lleva puesto lo que hubiera. **Crear justo antes de usar** es la única forma de no depender de ese orden; comprobar al principio y usar al final es exactamente cómo se cuela una carrera — la misma forma del bug de PolicyKit1 que se cerró hoy en GNOME. ## Gotchas medidos (no previstos) 1. **`cargo rustc --` exige UN target.** La fase compile del lab pasa flags tras el `--`, y cualquier paquete con binario + lib + ejemplos corta con «extra arguments to `rustc` can only be passed to one target». Se arregla con `flags = ["--bin", ""]`. Le pasa a cosmic-comp y a cosmic-bg (workspace con `config/`), y le va a pasar a casi todos: **ponelo desde el principio.** 2. **`start-cosmic` es bash de verdad, no sh.** Usa `mapfile`, `[[ ]]` y expansión indirecta `${!var}`; con el busybox del rootfs no arranca. `bash` ya está en el corpus, pero **tiene que entrar en la imagen** — y no lo pide ningún `[deps]`, así que no lo va a traer nadie por accidente. Lo mismo con `dbus-run-session` (de `dbus`), que es lo que el script exec-uta al final. 3. **`rust-toolchain.toml` con `channel = "1.93"` es inerte acá** — es una perilla de rustup, y el sandbox tiene el rustc de Alpine (1.96) pelado. Conviene saberlo antes de «arreglarlo». 4. **`cosmic.desktop` existe DOS veces** (en cosmic-session y en cosmic-comp) con contenido distinto y en la misma ruta. El de cosmic-comp es el de la «bare session» y apunta a `/usr/bin/cosmic-service`, que vive en la rama de systemd que acá no va. Se instala **sólo** el de cosmic-session; si entraran los dos, quién gana depende del orden de hidratación y el que pierde es el bueno. 5. **`libsystemd` no es systemd.** La feature `default = ["systemd"]` de cosmic-comp trae el crate `libsystemd`, que es una **reimplementación en Rust** del protocolo, no un binding a la C. Y `logind` habla `org.freedesktop.login1`, que acá lo sirve `arje-logind-compat`. No hay que apagar nada: el nombre de la feature es el del protocolo, no el del programa que lo contesta. 6. **Las features de smithay que asustan no cuestan deps de build**: `backend_vulkan` (crate `ash`), `backend_x11` (`x11rb` con `dl-libxcb`) y `xwayland` cargan todo por **dlopen**. Son deuda de RUNTIME —libxcb y vulkan-loader viven hoy sólo en la cola KDE—, no un muro de compilación. 7. **`libxkbcommon` + `pkgconf` van en TODA la cola, sin excepción — incluso en las piezas sin interfaz.** Se aprendió dos veces, con dos razonamientos distintos y los dos equivocados: - `cosmic-bg` fue sin `[deps]` porque «habla el protocolo en Rust y no toca C». Reventó en el `build.rs` de `smithay-client-toolkit` pidiendo `xkbcommon.pc`: **sctk ENLAZA libxkbcommon**; el que la dlopea es `winit`, que es otra capa. Hablar Wayland en Rust dice cómo viaja el byte, no con qué se interpreta un teclado. - `cosmic-idle` fue sin `[deps]` porque *ese* argumento ya no aplicaba —no usa sctk— y un demonio de inactividad no interpreta teclas. Reventó en el ENLACE con `-lxkbcommon`, arrastrado por **`cosmic-settings-config`**: de ahí sale la tabla de atajos y la usa **todo** componente de la suite. La dep no viene de lo que el paquete hace, sino de la librería de configuración común. La lección de método vale más que la dep: **la dep no se deduce de la función del paquete.** Está escrita en el `build.rs` que panickea o en la línea de enlace, y las dos veces el error dijo exactamente quién y por qué. El `link = "static"` sobrevive porque el artefacto de libxkbcommon trae `.a` además de `.so`. 8. **Una feature apagada en el paquete puede estar prendida por otro.** `cosmic-bg` declara `image` con `default-features = false` y sin AVIF, y aun así compila `dav1d-sys`: **las features de cargo se UNIFICAN en el grafo**. Se resolvió trayendo `dav1d` (b3:aab7a874) en vez de parchear el Cargo.toml de upstream — si el grafo dice que sabe decodificar AVIF, que lo sepa de verdad. Mismo modo de falla que un `.pc Requires` arrastrando una dep que nadie declaró. 9. **`cosmic-wallpapers` no se puede empaquetar del tarball: está en git-lfs.** El default de cosmic-bg apunta a `/usr/share/backgrounds/cosmic/orion_nebula_nasa_heic0601a.jpg`, y el archivo de GitHub de ese repo pesa **20 KB** — son punteros LFS, no imágenes. Una receta escrita del modo normal produciría un paquete de ficheros de texto que además *parecería* correcto: se instalarían con el nombre bueno en la ruta buena. Mientras tanto `cosmic-start` escribe la config de USUARIO con una fuente `Color`, que cosmic-bg soporta de fábrica — y va en el lanzador, no en el artefacto, porque es política de la imagen de prueba y no algo que el paquete prometa. ## Cómo se construye ```sh cd ~/hammer ./target/release/hammer --store store hash recipes/incoming-cosmic/.toml # dry-run, ~2ms ./target/release/hammer build recipes/incoming-cosmic/.toml --store store ``` ## Lo que falta, en orden 1. Cerrar `cosmic-comp`. Es el gate: sin compositor no hay escritorio que probar. 2. El primer cliente de iced (`cosmic-panel`), que es la pregunta abierta de verdad — libcosmic con `winit`/`wgpu` puede pedir más de lo que pide cosmic-bg. 3. `cosmic-settings-daemon`, `cosmic-launcher`, `cosmic-app-library`, `cosmic-notifications`, `cosmic-osd`, `cosmic-workspaces-epoch`, `cosmic-idle`. 4. Imagen QEMU con `bash` + `dbus-run-session` + `hicolor-icon-theme`, y **validar CON PANTALLA** (`DISP=gtk` + `screendump`, contar colores distintos) — regla heredada de KDE y GNOME. 5. `xdg-desktop-portal-cosmic` y `cosmic-greeter`, que son de la etapa siguiente.