openssl-sys cortó el build con 'Could not find directory of OpenSSL installation'.
No lo pide el daemon: llega por native-tls, que llega por el crate geonames — la
base de husos horarios que usa para el tema claro/oscuro por hora. Es el único de
la suite que toca TLS, y por una función que nadie asocia con la red.
El openssl del corpus ya publica openssl.pc, así que alcanza con declararlo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El getty ejecuta cosmic-start sin ambiente heredable, así que probar el modo
session exigía editar el script. Ahora la imagen hornea /etc/cosmic-mode, mismo
truco que /etc/gnome-mode.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Escrita mientras compila su dep: cosmic-session lo lanza con .expect() en
main.rs:255, así que si falta panickea la sesión entera JUSTO DESPUÉS de que el
compositor arrancó bien. Eso es lo que lo hace confuso de diagnosticar y lo que
lo puso último en la cola pese a ser obligatorio.
Es el único de la suite con cadena propia en C (audio-server → cosmic-pipewire →
libpipewire-0.3 por pkg-config), que es exactamente lo que costó traerlo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Medido en vez de estimado: copiar pipewire a incoming-cosmic necesita CUATRO
recetas hermanas (alsa-lib, dbus-shared, libsndfile, pulseaudio), no las seis que
había contado a ojo, y de esas **tres dan hash idéntico y ya están selladas**.
Sólo pulseaudio y pipewire hay que construir, porque su glib resuelve al corpus
(b3:93d2cad0) en vez de a la sombra de GNOME (b3:f6ccdf98).
Y eso refina lo que este runbook advertía: el peligro de las dos glib es MEZCLARLAS
EN UNA IMAGEN, y la imagen COSMIC tiene una sola. Acá el costo es de builds, no de
corrección. Lo que sigue vigente es no promover a ciegas entre colas.
Gotcha del copiado: el se resuelve relativo a la receta, así
que copiar el .toml sin su .patch da 'no pude leer patch' — el hash ni siquiera
se calcula.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La línea de enlace de cosmic-osd pedía '-ludev -linput -lxkbcommon': los tres
llegan por la cadena de libcosmic (enumeración de monitores y de dispositivos de
entrada), no por nada que el cliente haga a la vista.
Los venía descubriendo de a uno por build —xkbcommon con cosmic-bg, udev con
osd, ahora input— hasta leerlos todos juntos en el comando que rustc le pasa al
linker. El error dice el que falta primero; el COMANDO dice todos.
Entra además cosmic-idle rehecho dinámico (b3:e1806acc).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cosmic-bg dinámico (b3:b2cfc410) se conecta a cosmic-comp, entrega su buffer y el
compositor lo presenta: fondo completo en el color EXACTO que se le configuró,
(0.13,0.29,0.53) → (33,74,135), 164 colores distintos.
Que el color sea el pedido y no uno cualquiera es lo que hace de esto una
medición: descarta un buffer sin inicializar o el borrado del compositor. La
cadena cliente→compositor→KMS funciona de punta a punta.
El NEEDED del binario confirma de paso que dav1d no era un capricho del grafo:
libdav1d.so.7 y libc.so, nada más.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El default de cosmic-bg apunta a una imagen del repo cosmic-wallpapers, cuyo
tarball de GitHub 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 — nombre bueno, ruta buena.
Mientras tanto cosmic-start escribe la config de usuario con una fuente Color,
que cosmic-bg soporta de fábrica. Va en el lanzador y no en el artefacto porque
es política de esta imagen de prueba, no algo que el paquete prometa.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pantalla completa en el gris de COSMIC (39,41,42) con el cursor dibujado, 134
colores distintos, validado CON PANTALLA (DISP=gtk + screendump).
Entra el andamiaje completo: hydrate-cosmic.sh (44 recetas, 0 faltantes),
qemu-desktop-image.sh y cosmic-start-qemu.sh.
Tres causas medidas en tres arranques, ninguna donde uno la busca:
1. cosmic-settings-daemon NO es opcional: cosmic-session lo lanza con .expect()
(main.rs:255) y panickea si falta. Corrige lo que este runbook afirmaba. De
ahi el modo bare del lanzador, que separa como compone de como arranca la
sesion.
2. La pantalla negra era libz.so.1: kms_swrast_dri.so lo NEEDea y el corpus solo
traia zlib estatica. Sin driver de software no hay GBM y el compositor arranca
igual, negro. Causa a tres capas del sintoma.
3. Los clientes no pueden ser estaticos: wayland-client entra con la feature
dlopen y un musl estatico no tiene dlopen funcional — moria 'The wayland
library could not be loaded' CON el .so presente.
Y dos carreras con /run (dbus y XDG_RUNTIME_DIR) de la misma forma que el bug de
PolicyKit1 de hoy en GNOME: comprobar al principio y usar al final. Se crea justo
antes de usar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hermano de hydrate-gnome.sh y con su misma propiedad: el cierre sale del GRAFO
REAL de recetas y el artefacto se elige por 'hammer hash' (la receta de HOY), no
por el más reciente del store, que con dos artefactos del mismo paquete miente.
Las raíces se nombran a mano porque en COSMIC la brecha entre cierre de build y
cierre de runtime es enorme: cosmic-session lanza a sus componentes por PATH y
por nombre, así que ninguno es dep de build de otro y el grafo no los ve. La
lista autoritativa es cosmic-session/src/main.rs.
Resultado: 207 binarios, 109 .so, y los siete NEEDED de cosmic-comp resueltos
dentro del propio cierre. Falta sólo el loader musl, que lo pone la base metal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Decía «copiar es gratis de verdad, no casi». La regla honesta es: gratis SÓLO si
toda la clausura transitiva resuelve a lo mismo. Se cumple con libdisplay-info,
hwdata y nasm; no con pipewire.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El compositor compiló a la primera en 51m39s (JOBS=1 + lto fat), sin un solo
parche a la fuente; lo único que hubo que corregir fue el --bin del gotcha 1.
Lo que vale es el enlace, porque es medición y no impresión: ocho NEEDED, y las
siete primeras son EXACTAMENTE los backends de smithay que la receta declara
(display-info, gbm, seat, udev, input, pixman, xkbcommon). La octava es musl.
No hay libgcc_s —el compositor no arrastra la deuda del unwinder— y no aparece
nada sin declarar: la clausura de build y la de runtime coinciden.
Un compositor Wayland completo (DRM/GBM/EGL/libinput/seat/Vulkan/XWayland,
renderer multi-GPU y UI en iced) cerrando con ocho librerías compartidas y cero
parches es el resultado más limpio de las cuatro campañas de escritorio.
Queda anotado además por qué cosmic-settings-daemon NO se resuelve copiando
pipewire: copiar entre colas es gratis sólo si TODA la clausura transitiva
resuelve igual, y ahí glib resolvería distinto (sombra de GNOME b3:f6ccdf98 vs
corpus b3:93d2cad0) ⇒ un segundo pipewire contra otra glib, que es el cuadro de
dos registros de GType que la campaña GNOME ya midió y evitó. Medido con yupana
radio antes de tocar nada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Escritas ya con el patrón medido —libxkbcommon + pkgconf en todas— en vez de
descubrirlo cinco veces. Cada install sale del justfile/Makefile de upstream
leído, no del parecido con el anterior; ahí estaban las tres diferencias que
importan:
- cosmic-panel: default_schema NO es un fichero sino un ÁRBOL de tres componentes
de cosmic-config con un fichero POR CLAVE. Aplanarlo deja un panel que arranca
con todo por defecto compilado: sin plugins, sin dock, sin tamaño.
- cosmic-workspaces-epoch: los tres nombres NO coinciden — repo con sufijo
-epoch, binario y AppID sin él, y cosmic-session lo busca por el del binario.
- launcher y app-library instalan .desktop + metainfo + icono; notifications y
osd no llevan ninguno porque no se lanzan a mano.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cosmic-idle se escribió sin [deps] con un argumento que parecía sólido: no usa
smithay-client-toolkit (el que hizo caer a cosmic-bg) y un demonio de inactividad
no interpreta teclas. Reventó igual, pero en el ENLACE y no en un build.rs:
-lxkbcommon lo arrastra cosmic-settings-config, de donde sale la tabla de atajos
y que 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.
Dos veces el mismo error de método con dos razonamientos distintos, y las dos
veces el mensaje decía exactamente quién y por qué. Queda en el runbook como
regla, no como anécdota.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cosmic-bg declara `image` con default-features=false y sin AVIF, y aun así se
compila dav1d-sys: las features de cargo se UNIFICAN en el grafo, así que alcanza
con que otro crate del árbol la prenda para que la decisión del que la apagó no
mande. Mismo modo de falla que un '.pc Requires' arrastrando una dep que nadie
declaró.
Se podría parchear el Cargo.toml de upstream para cortarlo; no se hizo, porque
eso es mentirle al paquete sobre lo que hace. Si el grafo dice que sabe
decodificar AVIF, que lo sepa de verdad — y cualquier cosa del corpus que decodifique
AV1 va a querer dav1d igual.
Dos decisiones del build, las dos leídas del meson.build y no supuestas: nasm es
dep REAL (find_program en :536, exige >= 2.14; copiado de la cola GNOME con hash
verificado b3:33383070), y default_library=both porque quien consuma dav1d con
enlace estático necesita la .a — publicar sólo la .so convierte cualquier
link=static río abajo en una etiqueta falsa.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cosmic-bg se escribió sin [deps] con ese argumento y reventó en el build.rs de
smithay-client-toolkit pidiendo xkbcommon.pc. El que dlopea xkbcommon es winit,
que es otra capa. Hablar el protocolo en Rust dice cómo viaja el byte, no con
qué se interpreta un teclado. El link=static sigue siendo verdad: el artefacto de
libxkbcommon trae .a además de .so.
Corolario anotado en el runbook: todo cliente de la suite va a necesitar al menos
libxkbcommon + pkgconf.
Entran además cosmic-idle (cliente Wayland puro, SIN deps a propósito: no usa
sctk y no necesita interpretar teclas) y cosmic-osd, que es el primer cliente de
iced/libcosmic y por eso vale como sonda: panel, launcher, app-library,
workspaces y notifications comparten su mismo sustrato exacto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deja asentado por qué COSMIC no se parece a KDE ni a GNOME (no hay torre de C
debajo: la primera receta salió estática y con cero NEEDED), la tabla de lo que
se hereda gratis de mirada/GNOME, el pin en lockstep epoch-1.5.0, y los seis
gotchas medidos — entre ellos que start-cosmic es bash de verdad (mapfile, [[ ]],
${!var}) y que bash no lo pide ningún [deps], así que a la imagen no lo trae
nadie por accidente.
Y el porqué del orden de ataque, que NO es el orden de arranque: cosmic-bg antes
del panel para que «pinta el fondo pero no el panel» separe la capa de UI del
transporte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cosmic-comp es LA pieza y la receta salió casi gratis: mirada-compositor ya
probó que smithay construye en este lab, y las deps en C de smithay dependen de
las features del backend, no del compositor. La lista es la misma menos
linux-pam y más libdisplay-info.
libdisplay-info y hwdata entran COPIADAS byte a byte de la cola GNOME (idénticas
a las de KDE): las deps resuelven hermano→padre, así que desde incoming-cosmic
no se ven las de otra cola. Verificado que el hash NO se mueve (b3:260f0519 y
b3:bdc36cf9) ⇒ los artefactos ya sellados sirven y la copia no cuesta un build.
cosmic-bg va antes que el resto de los clientes porque separa dos preguntas:
habla Wayland por smithay-client-toolkit y NO toca libcosmic/iced, así que si el
fondo pinta y el panel no, el problema es de la capa de UI y no del transporte.
Toda la suite está tagueada epoch-1.5.0, confirmado repo por repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sonda barata antes del compositor: Rust puro, sin una dep en C, y prueba las
tres cosas nuevas que la cola COSMIC necesita — edition 2024 sobre el rustc 1.96
del sandbox, deps de git en el Cargo.lock (launch-pad, cosmic-dbus-a11y), y el
patrón de instalación de la suite (binario + start-cosmic + cosmic.desktop).
Salió a la primera: ELF estático, cero NEEDED.
Toda la cola incoming-cosmic va pinneada al tag epoch-1.5.0: COSMIC versiona sus
repos en lockstep y mezclar epochs no da error de compilación sino un escritorio
que no se entiende consigo mismo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El plan seguía sin tilde aunque el shell arranca justamente porque están: se
verificó uno por uno con `hammer hash` contra el store en vez de fiarse del
recuerdo. Se deja el texto original del plan por la advertencia de libelogind,
que sigue vigente.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
accounts-daemon y upowerd morían al arrancar: el shim de polkit se lanza en
background y tarda ~2s en adquirir org.freedesktop.PolicyKit1, ellos llamaban a
polkit_authority_get_sync() en ese hueco, D-Bus intentaba ACTIVAR el servicio y
fallaba con 'Failed to execute program ... Permission denied'.
Lo peor era el informe: los tres esperar_nombre estaban al final, así que para
cuando corrían el shim ya tenía el nombre y el resumen daba 'PolicyKit1 OK'
sobre dos clientes ya muertos. Un chequeo posterior a la carrera no la mide.
esperar_nombre pasa a definirse antes del primer daemon y el bloque de polkit
espera su propio nombre antes de que arranque ningún cliente suyo. Lanzar en
orden no es estar listo en orden.
De paso queda cerrado el ítem 4 del runbook: validación CON PANTALLA (DISP=gtk)
del Overview con el PID1 sellado nuevo — 689 colores distintos, con evidencia.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Las 4 imágenes de escritorio (GNOME + los 3 de KDE: qemu, metal, dual) fundían `work/metal-rootfs`
por hardlinks y se llevaban SU arje-zero, que era del 2026-06-19: ese directorio se armó a mano en
la campaña de metal y nadie lo regeneraba al re-sellar la receta. Resultado: todas arrancaban con un
PID1 de hace mes y medio, en silencio.
No es hipotético — se comió un fix real. El `identity mismatch` del bus del fractal estaba arreglado
río arriba y la VM seguía imprimiendo el mensaje viejo porque el binario de la imagen no venía de la
receta (ver el comentario de recipes/arje-zero.toml).
El síntoma engaña porque el directorio base es LEGÍTIMO para todo lo demás —busybox, firmware, el
kernel EFI-stub, la estructura de /etc—: sólo la pieza que TAMBIÉN es receta se queda atrás, y justo
esa es PID1. La regla que queda escrita en el helper: si algo del rootfs tiene receta, la imagen lo
toma del artefacto sellado, no de la copia congelada.
El helper va en scripts/lib/ y no inline ×4 a propósito: el porqué es largo y vale una sola copia.
Elige el artefacto por `hammer hash` (el de la receta de HOY, no el más nuevo por fecha, que miente
en cuanto conviven dos) y ABORTA si falta, porque seguir con el PID1 congelado es exactamente el
modo de falla que cierra.
Probado: las 4 pasan `bash -n` y la imagen GNOME lo ejecuta bien sourceado (`✓ e570c1482432 (el de
work/metal-rootfs era de 2026-06-19)`), con la VM ya validada booteando ese PID1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cierra un TODO que el propio script tenía escrito («esto pertenece al artefacto de
arje-logind-compat, no a la imagen») y que estaba bien puesto: **un servicio que no puede
adueñarse de su nombre no es un servicio**, así que su política D-Bus es parte de lo que el
paquete promete, igual que el binario. Con la política en el script de UNA imagen,
cualquier otra —metal, KDE, mirada— se llevaba el daemon y no podía usarlo.
arje-logind-compat b3:b4795829 · arje-polkit-compat b3:6c82f44b. Radio de las dos: 0.
**Y la comprobación que reemplaza a escribirlas atrapó un bug de verdad en el primer
intento.** Mover algo a un artefacto sólo es una mejora si se NOTA cuando falta, así que el
script pasó de ESCRIBIR los .conf a EXIGIRLOS. Falló al toque: `✗ falta
org.freedesktop.login1.conf`. Causa — el script inyectaba **sólo el binario** del artefacto
(`install -Dm755 .../usr/bin/...`), así que el .conf existía en el store y nunca llegaba a
la imagen. Sin esa comprobación habría sido el fallo tardío de siempre: daemon que arranca,
no adquiere el nombre, y 25s de espera por servicio sin decir por qué.
Verificado de punta a punta con las políticas viniendo del artefacto: los cuatro nombres
del bus arriba (login1, PolicyKit1, Accounts, UPower) y el audio intacto.
El `zz-` de la de polkit no es decorativo y queda explicado en su receta: a diferencia de
login1, ese nombre YA tiene política —la trae el artefacto de polkit— y permite `own` sólo
al usuario polkitd; dbus lee system.d en orden alfabético y las reglas posteriores ganan, así
que un fichero que ordene después AÑADE el permiso sin descartar el resto de upstream.
ColorManager sigue sin aparecer: es lo de colord, ya diagnosticado hasta dónde llega y
fuera del alcance de este cambio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La receta llevaba una bandera de include que su propio comentario marcaba EN DUDA: se
había puesto porque el build cortaba con `../lib/wp/wp.h:13: fatal error: 'client.h' file
not found`, un header que SÍ existe justo al lado del que lo incluye. La explicación real
era que **otro agente construía en el mismo repo y borraba árboles de work/sources/ en
pleno build** — la carrera del ADR 0012, que produce exactamente esa firma: ficheros que
existen cuando los mirás después y no existían al compilar.
Re-probado CON EL REPO QUIETO —comprobando primero que no hubiera ningún `hammer build`
vivo— y sella igual sin la bandera. Confirmado, no supuesto. Verificado además de punta a
punta: audio sigue dando `48. HDA Intel [alsa]` con sink y source reales, 0 page-flips
fallidos.
La lección queda en la receta: antes de agregar una bandera que no se explica, mirar si hay
otro build vivo. Es barato y evita inventar arreglos para síntomas que no son del código.
**GOTCHA DEL LAB, aprendido a la mala en el mismo movimiento: los BYTES del fichero .patch
entran al ArtifactHash.** Corregir la PROSA obsoleta de `pipewire-sound-initialized.patch`
—decía que la decisión libudev-zero-vs-eudev seguía pendiente cuando ya está resuelta—
re-hasheó pipewire y arrastró wireplumber. La distinción que conviene tener presente:
**comentar una RECETA es gratis** (hash_inputs toma source/compiler/target/link/flags/fases/
deps, no los comentarios del TOML) **y comentar un PARCHE cuesta un rebuild**. Anotado en
pipewire.toml, que es donde se va a leer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sealed 765 → 768 debt 3 → 0 never 2 (los dos con bloqueo documentado)
base 50/51 → 51/51 cli 73/74 → 74/74 escritorio-mirada 29/31 → 31/31
Cierre de la deuda que yo mismo abrí al parchear libudev-zero (una hoja del cierre de las
CINCO imágenes): reconstruidas `usbutils` (b3:245c718b — depende de libudev por pkg-config),
`mirada-compositor` (b3:c906e244) y `mirada-greeter` (b3:c7dfcf09).
Ojo con la aritmética, que casi me confunde: `yupana radio libudev-zero` dice 58 dependientes
transitivos y build-state sólo veía 3 en deuda. No es contradicción — **build-state cubre el
catálogo canónico `recipes/`, no las colas `incoming-*`**, y 47 de esos 58 están en
incoming-kde. Los 8 de incoming-gnome ya se habían reconstruido ayer.
Los dos `never` NO son trabajo pendiente disfrazado; los dos tienen bloqueo real y ya escrito:
- **dwarves**: su cmake no halla libdw. La receta ya lo documentaba y el build lo confirmó
palabra por palabra («Could NOT find libdw include dir / library»): `elfutils.toml`
empaqueta sólo LIBELF —lo que kbuild necesita— y es INTOCABLE porque es build-dep de los
kernels sellados. La salida es una receta hermana `elfutils-libdw`, que en musl arrastra
shims de argp/fts/obstack. Campaña propia, no un arreglo.
- **llimphi-counter**: es una PLANTILLA, no un paquete. Su commit es `000…0`, un placeholder
que nunca se llenó. Gasté un build en descubrirlo, así que ahora la receta lo dice en la
primera línea: `⛔ ESTO ES UNA PLANTILLA, NO UN PAQUETE. NO INTENTES CONSTRUIRLA.` El estado
`never` del grafo era técnicamente cierto y semánticamente engañoso.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La receta iba con `-Ddaemon=false` y el razonamiento era «mutter enlaza libcolord, no
necesita el demonio». Cierto para COMPILAR mutter y falso para el escritorio andando: el
artefacto INSTALA el `.service` de activación (`Exec=/usr/libexec/colord`) y ese binario no
existía, así que cada arranque se comía 25s de timeout. **No era «colord apagado» sino
colord roto de forma lenta** — el peor de los dos, porque no falla, tarda.
Su propio comentario marcaba la condición de vuelta: «si alguna vez hace falta el demonio
de verdad, vuelve con polkit encima». Polkit ya está (arje-polkit-compat). Y medido: prender
el demonio **no agrega una sola dep nueva** salvo polkit-gobject-1, ya sellada — el bloque
de dependency() de colord 1.4.7 es de nivel superior y pedía gusb/gudev/libudev igual con
daemon=false. El coste estaba pagado desde el 2026-07-27 sin que nadie lo cobrara.
**PERO EL TIMEOUT SIGUE**, y lo que aprendí es dónde NO está:
- El binario existe y arranca: `/usr/libexec/colord` crea sus tres bases en
/var/lib/colord (mapping.db, storage.db) y lo dice en el log.
- Con `--verbose` NO hay una línea más después de abrir la tercera base.
- La política D-Bus SÍ permite `own` a root, y el `.service` corre como root: no es el caso
de polkit (donde `own` estaba restringido al usuario `polkitd`).
- Los cuatro `cd_main_load_introspection` que van entre las DB y `g_bus_own_name`
(cd-main.c:2434-2460) leen de un **GResource compilado en el binario**, no de disco, así
que no pueden faltar. Los XML instalados en /usr/share/dbus-1/interfaces son para otros.
⇒ Queda entre `cd_main_load_introspection` y `g_main_loop_run`, y el siguiente dato es si el
proceso sigue vivo en ese momento. **Esa comprobación me faltaba en el script** — reportaba
«colord lanzado (pid N)» sin verificar nada, que es exactamente el error que yo mismo había
señalado para upowerd («un pid no es un servicio») y no apliqué acá. Ya está puesta: sin
ella, «no apareció el nombre» no distingue MURIÓ de SE COLGÓ, y son dos investigaciones
distintas.
Verificado que no hay regresión en lo que sí funciona: audio (48. HDA Intel, sink y source
reales) y vídeo (0 page-flips fallidos) siguen bien con el demonio en la imagen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El kernel ya traía el driver (SND_SOC_SOF_TIGERLAKE); faltaban los blobs, y sin ellos SOF
carga y falla: en el laptop no hay sonido aunque ALSA esté. Mismo patrón que i915 con su
DMC/GuC — driver desde fuente, firmware pineado por contenido, porque Intel no libera el
código y la soberanía build-from-source no aplica a datos fijos.
Tres piezas, cada una por un motivo distinto:
- `sof-tgl.ri` + `sof-tgl-h.ri`: el firmware del DSP. Se copian LOS DOS porque el driver
elige según el SKU y no quiero hornear la suposición. **`cp -L` es obligatorio**: en
linux-firmware son SYMLINKS a `intel-signed/`, y un symlink relativo copiado a otro
árbol apunta a la nada. Verificado: los tres quedan ficheros reales (525K/447K/95K).
- `sof-tgl.ldc`: el diccionario de logs del DSP. No es opcional en la práctica — el
driver lo pide al inicializar y sin él el firmware arranca SIN TRAZA, o sea que el día
que algo falle no hay por dónde mirar.
- 47 topologías (`sof-hda-generic-*`, `sof-tgl-*`): describen el grafo de audio. El driver
pide UNA por nombre derivado de la máquina, y no se puede saber cuál sin el hardware
delante. Copiar de más cuesta megas; copiar de menos cuesta un viaje físico al laptop
para descubrir qué nombre pidió.
~3 MB en total. Si no hay SOF en el origen, AVISA y NO falla: el audio del laptop se
pierde pero la imagen sigue booteando y en QEMU suena por HDA legacy — fallar bloquearía
builds que no necesitan SOF.
Verificado que no hay regresión: con los blobs en la imagen, el audio de la VM sigue
funcionando por la ruta HDA (48. HDA Intel [alsa], sink y source reales) y cero page-flips
fallidos. **El camino SOF en sí queda SIN VALIDAR hasta el próximo viaje al metal** — en
QEMU no se ejerce, y eso está escrito en la receta del kernel en vez de dado por bueno.
De paso, la cabecera del script deja de mentir: se llama «firmware» y no «wifi» porque
copia las TRES familias de blobs que el metal necesita (iwlwifi, i915, SOF).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Audio
├─ Devices: 48. HDA Intel [alsa]
├─ Sinks: * 52. HDA Intel Analog Stereo [vol: 0.40]
├─ Sources: * 53. HDA Intel Analog Stereo [vol: 1.00]
Y gvc con perfiles reales (`output:analog-stereo+input:analog-stereo`). Se fue el auto_null.
LA CAUSA: SPA pide el device `card` (alsa-udev.c:178-183), que no tiene major:minor, y
libudev-zero recorría SÓLO /sys/dev/{block,char} — sólo devices CON nodo. El device que
SPA necesita nunca entraba en la enumeración: no faltaba una propiedad, faltaba la mitad
del espacio de búsqueda.
**Y LA PRIMERA VERSIÓN DEL PARCHE ARREGLÓ EL AUDIO Y ROMPIÓ EL VÍDEO.** Recorría
/sys/class entero, y /sys/class/drm no trae sólo tarjetas: trae los CONECTORES
(card0-Virtual-1) y un fichero `version`, ninguno con nodo. Mutter los tomaba como
candidatos ⇒ `No available CRTC for monitor` + `Page flip failed: drmModeAtomicCommit:
Invalid argument` en bucle. Pantalla negra con el compositor vivo: el mismo síntoma de
antes por una causa nueva. El udev real también los enumera, pero les adjunta las
propiedades por las que mutter los descarta, y ésas libudev-zero no las sintetiza.
La lección, que vale para cualquier reimplementación parcial de una API: **ensanchar la
enumeración expone los huecos de PROPIEDADES a más consumidores. Los dos lados del
contrato tienen que crecer juntos.** El parche final lleva una LISTA ACOTADA de
subsistemas (`sound` hoy); agregar otro es una línea *y* una prueba de que el consumidor
lo necesita y no se rompe.
POR QUÉ NO eudev — y corrijo mi propia sugerencia de antes, que era la peor opción: el
repo ya lo había probado en otro frente y ROMPIÓ EL INPUT. Su libudev.so.1 pisa el de
libudev-zero y, sin udevd, no expone ID_INPUT ⇒ libinput ignora todos los dispositivos en
silencio. Verificación A/B en QEMU de aquel episodio: eudev = 0 «New device»,
libudev-zero = 5. eudev sin udevd es PEOR, y correr udevd contradice la arquitectura
(arje-zero es PID1). El arreglo va donde está el hueco.
Radio pagado: 51 sellados a deuda, 9 en el cierre GNOME (reconstruidos: libinput,
libgudev, colord, mutter, gnome-shell, libgdm, upower, pipewire, wireplumber). El resto
—base, cli, KDE— queda para el latido.
Captura actualizada: escritorio pintando (628 colores) Y audio, cero page-flip fallidos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ALSA =y en linux-metal (b3:613bca15): HDA legacy + codecs (lo que emula QEMU y usan las
máquinas pre-SOF), SOF para TigerLake (el laptop del target NO suena por la ruta legacy:
Intel movió el audio al DSP) y SND_USB_AUDIO. Todo =y porque el producto es monolítico.
⚠ Falta inyectar el FIRMWARE de SOF en la imagen (intel/sof/*.ri, sof-tplg/*.tplg), igual
que metal-firmware.sh hace con los tgl_* de i915: sin blobs el driver carga y falla.
FUNCIONÓ LA MITAD: en la VM `/dev/snd` ya trae controlC0 + pcmC0D0p/c y /sys/class/sound
lista card0. El kernel detecta la tarjeta. Pero wpctl sigue con `Devices:` vacío.
**Y LA CAUSA ES ESTRUCTURAL, no una propiedad que falte.** `get_card_nr` de SPA
(alsa-udev.c:178-183) sólo acepta el device CARD —`/sys/.../sound/card0`—, que es un
device de CLASE: sin major:minor, sin nodo en /dev. Y `udev_enumerate_scan_devices` de
libudev-zero recorre EXCLUSIVAMENTE /sys/dev/block y /sys/dev/char (udev_enumerate.c:281),
o sea sólo devices CON nodo; el udev real enumera /sys/class entero. **El device que SPA
necesita nunca entra en la enumeración**: no hay guarda que sacar, falta la mitad del
espacio de búsqueda. Salidas: (a) enseñarle a libudev-zero a recorrer /sys/class —radio 51
sellados— o (b) empaquetar eudev. Es decisión de arquitectura, no parche.
CORRIJO ALGO QUE ESCRIBÍ ANTES: el parche `pipewire-sound-initialized.patch` se hizo
creyendo que SOUND_INITIALIZED era el bloqueo, y NO lo era — con el parche aplicado el
resultado no cambió. Se conserva porque la incompatibilidad que describe es real (sin
udevd nadie pone esa propiedad y SPA descartaría toda tarjeta en cuanto la enumeración
funcione), pero su prosa ahora empieza avisando que no es la solución. Sacarlo del camino
importa: dejarlo presentado como el arreglo mandaría al próximo a buscar en el lugar
equivocado.
Dos arreglos de andamiaje que costaron un ciclo cada uno:
- **`-nic none` por defecto.** Agregar la tarjeta de sonido CORRIÓ LAS RUTAS PCI, la entrada
de disco cacheada en las VARS de OVMF dejó de coincidir, y la firmware se fue al orden por
defecto: PXE v4, PXE v6, HTTP… minutos de timeouts. Con un TIMEOUT corto eso se lee como
«la VM no arrancó» y el serial sólo dice `PXE-E16: No valid offer received`.
- El filtro del log de wireplumber era `alsa|udev|sound|card|monitor` con `tail -40`, y se
llenó de bluez/v4l2/libcamera —tres monitores que avisan que su plugin no está— dejando
fuera justo las líneas de ALSA. **Un filtro ancho con cola corta es peor que ninguno.**
Ahora filtra `alsa` y además copia el log entero a la raíz ext4 (tmpfs muere con la VM).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cierra el gestor de sesión de PipeWire, que es lo que separa «PipeWire acepta clientes»
de «PipeWire tiene dispositivos». Su política está escrita en Lua, así que arrastró una
receta de Lua que hubo que autorar entera.
LO QUE LA RECETA DE LUA TIENE QUE INVENTAR: el Makefile de upstream sólo produce
liblua.a y los binarios — **no hay regla de .so ni fichero pkg-config**, los agrega cada
distro. Acá la compartida se enlaza a mano desde la .a con --whole-archive (una estática
sólo aporta lo referenciado y hay que llevarse todo) y el .pc se escribe con los TRES
nombres que se usan por ahí, porque wireplumber prueba lua-5.4, lua5.4 y lua54 en orden.
`-fPIC` no es opcional: libwplua es un objeto compartido.
Dos símbolos más en libelogind (tawasuyu 087230054 → re-pineado 749edfe41):
sd_uid_get_seats y sd_uid_get_state, que module-logind.c de wireplumber usa para saber si
el usuario está en un asiento antes de tomar los dispositivos. Van 28 símbolos sd-*.
**EL `Devices:` VACÍO DE wpctl NO ES UN FALLO DE wireplumber: el kernel no tiene ALSA.**
recipes/linux-metal.toml:111 lo apaga explícito (`-d SOUND -d SND`), así que no hay
/dev/snd que enumerar. Y esto NO se dio por supuesto: se agregó una ich9-intel-hda
emulada a QEMU (AUDIO=1, ahora el default de run-qemu-desktop.sh) justo porque un
«Devices: vacío» se lee IGUAL si wireplumber funciona sobre una VM sin hardware que si no
funciona. Con tarjeta y sin ALSA en el kernel, el resultado no cambia — la ambigüedad
queda resuelta. Encender el sonido en la distro es una decisión con costo (re-sellar el
kernel, rehacer la imagen metal) y queda a la vista en vez de escondida en un default.
Bug propio destapado en el camino: el bloque de wireplumber quedó DUPLICADO en
gnome-start y había DOS wireplumber peleándose el grafo. El síntoma no era un error sino
una lista de clientes que se lee normal si no se cuenta: dos «WirePlumber» con pids
distintos en wpctl status.
Y una nota de ADR 0012 que costó un rato: la cascada de libelogind cortó a mitad y dejó
el `output/` de mutter a medio hacer; el reintento moría con «error opening
'...c.o.d': No such file or directory». Un `rm -rf output` en el configure NO alcanzó
—el árbol tenía estado viejo más allá del build dir—; lo que lo arregló fue BORRAR
work/sources/mutter-* para que el fetch lo re-extraiga limpio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>