Commit Graph
15 Commits
Author SHA1 Message Date
Sergio 3d79cdf98b SDD 30 4c: GNOME con los demonios supervisados por arje — y el control arregló colord
Los demonios de sistema pasan de lanzarse con `&` desde un script de 500 líneas
a ser Cards del `genesis`. El bloqueo que este frente daba por corpus se levantó
solo: otro agente selló evolution-data-server y gnome-shell mientras esto se
escribía, y escritorio-gnome quedó 309/309.

SE CORRIÓ UN CONTROL PRIMERO, y es lo único que hace interpretable el resultado:

  colord              control: `MURIÓ al arrancar`   cards: `ya vive (pid 175)`
  ColorManager        control: `NO apareció en 40s`  cards: `OK`
  login1/Accounts/UPower  OK en los dos
  compositor          wayland-0 y shell vivo en los dos

El fallo del control DESAPARECIÓ, y no lo buscaba: colord moría arrancado por el
script y vive arrancado por arje, con el bus ya listo porque la espera está
dentro de su argv. Sin el control, «ColorManager OK» sería un dato suelto en vez
de una diferencia. Los PIDs lo confirman: polkit=119, colord=175, upowerd=178,
accounts=180 — de antes de que el lanzador de sesión existiera.

QUÉ NO SE COMPROBÓ: no hay screendump; QEMU salió por timeout y el control
tampoco lo tuvo. La comparación es serial contra serial y lo que se afirma es
sobre los DEMONIOS, no sobre el pintado.

Las piezas donde corresponde: `takana service-cards` (UNA sola implementación de
receta→Card; el formato es contrato con card_core::Card), `targets.py
--service-paths` (une qué-es con si-arranca), y un inyector en FICHERO APARTE
porque anidar dos heredocs de python falló en vivo — el terminador del interno
cerró el externo y media cosa corrió como shell.

La espera del bus va DENTRO del argv de las 5 recetas de sistema: sin ella un
daemon arranca antes de que dbus escuche y queda en modo idle sin registrar su
nombre — un fallo que no se ve, porque el proceso vive y el bus no lo tiene. Los
5 hashes intactos. Y el guardia de gnome-start es por «¿está corriendo?», no por
una perilla: así es correcto venga de donde venga el proceso y la misma copia
sirve donde no se inyectaron cards.
2026-09-14 01:51:19 +00:00
SergioandClaude Opus 5 4e4a2c8d2a gnome: dash-to-panel, appindicator y gnome-tweaks — y la lista de activas sale de las recetas
GNOME deja de ser la cáscara: tres extensiones y la app que el proyecto no instala y todo el mundo
termina instalando. El perfil queda 190/190 con 28 raíces, deuda 0.

  dash-to-panel v73   junta dash, ventanas y bandeja en una sola barra (lo que más cambia el uso)
  appindicator v64    devuelve los ICONOS DE BANDEJA que GNOME quitó; sin ella las apps que usan
                      StatusNotifierItem corren y no tienen dónde mostrarse
  blur-my-shell v72   (ya estaba; se le saca el override, ver abajo)
  gnome-tweaks 46.1   tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que
                      GNOME esconde, y el interruptor de las extensiones

⚠ LA LISTA DE ACTIVAS SE MUEVE A UN SOLO SITIO, y esto era una bomba de relojería. `enabled-extensions`
es un ARRAY: un override de GSettings lo escribe ENTERO. Con la primera extensión el override vivía
en su receta y funcionaba; con la segunda NO se habrían sumado — ganaría la que compile última
(orden alfabético) y las otras quedarían instaladas y MUERTAS, sin un solo error. Ahora la arma
`scripts/gnome/gnome-start-qemu.sh`, que ya compilaba los esquemas al arrancar, enumerando los uuid
presentes en /usr/share/gnome-shell/extensions. La política es «lo que la imagen instala, se
activa»; el dconf del usuario gana sobre el default.
Una receta NO puede ser ese sitio: hammer exige `[source]` en toda receta («missing field `source`»),
así que no hay forma de escribir una receta de pura política. Se probó el bloque contra un árbol de
juguete: lista las dos extensiones, salta un directorio sin metadata.json, el override compila con
stderr vacío y `gsettings get` devuelve las dos. Y si la lista cambia borra `gschemas.compiled` para
forzar recompilación — sin eso, una imagen ya arrancada ignoraría la extensión nueva en silencio.

⚠ EL HALLAZGO CARO: `msgfmt` de gettext-tiny REVIENTA con las formas plurales del árabe. SIGILL,
exit 132, «index 6 out of bounds for type 'size_t[6]'», y deja un .mo de 0 bytes. Reproducido FUERA
de la receta con el binario sellado. No es el idioma sino el mensaje: blur-my-shell compila su
ar.po sin problema porque no tiene NINGÚN plural (msgstr[5] = 0); el de tweaks tiene exactamente
uno. Acá se saca `ar` del LINGUAS y se pierde la traducción de UNA app, con el diagnóstico escrito
en la receta. ARREGLARLO ES OTRA UNIDAD DE TRABAJO: gettext-tiny la usan 108 recetas.

Cada receta usa el mecanismo de SU upstream, que no es el mismo en las tres:
  blur-my-shell   `make build` llama a `gnome-extensions pack` —el CLI que nuestro shell apaga— así
                  que se copia el árbol a mano, con el layout LEÍDO del zip de EGO y comparado
                  contra él (idéntico, sin sobras ni faltantes)
  dash-to-panel   su Makefile tiene un `install` que honra DESTDIR y hace lo de un paquete de
                  distro: gschema y locale a los directorios GLOBALES. ⚠ `VERSION=73` se pasa a
                  mano porque sin ella el Makefile hace `git describe` y el árbol viene por
                  `git archive`, sin `.git` ⇒ metadata quedaría con el marcador "version": 9999
  appindicator    meson. ⚠ `-Dlocal_install=disabled` explícito: en `auto` decide mirando si el uid
                  es 0, o sea que hoy acierta POR ACCIDENTE — y si el lab dejara de correr como
                  root instalaría en $HOME/.local y el artefacto sellaría vacío. Y `jq` va en deps
                  aunque sea JavaScript puro: su meson saca el uuid del metadata.json con jq

⚠ gnome-tweaks NO costó ninguna receta nueva, y eso hubo que medirlo: su meson declara siete deps
mínimas de runtime y las siete ya estaban selladas. La que parecía traer una cadena de Python
entera —`pygobject-3.0`— la provee `py3-gobject`, que YA ESTABA en el corpus con otro nombre y
publica `pygobject-3.0.pc`. El nombre no es el hecho, otra vez.
Sí costó dos deps que el configure reclamó y que son el patrón `.pc Requires` → `[deps].build`:
`appstream` (la pide `libadwaita-1.pc` y el error culpa a libadwaita, que está bien instalada) y
`desktop-file-utils` (meson la busca como PROGRAMA para `update-desktop-database`).
Sus deps de RUNTIME —python3 y py3-gobject— van declaradas: es una app de Python, si no están en la
imagen se instala y no arranca, y ningún build lo delata.

Las cuatro licencias, verificadas en la fuente pineada y NINGUNA leída del COPYING —cuyo apéndice
«How to Apply» trae siempre la frase «or any later version»—: blur-my-shell del README del commit,
dash-to-panel y appindicator de la cabecera de sus fuentes, gnome-tweaks de su etiqueta SPDX.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 19:41:52 +00:00
sergioandClaude Opus 5 c0055331c7 gnome: esperar el NOMBRE de PolicyKit1 antes de lanzar a sus clientes
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>
2026-08-03 10:05:29 -04:00
sergioandClaude Opus 5 beb2851d99 colord: el demonio ya se construye (b3:8100fa39) — arranca, abre sus DB y NO adquiere el nombre
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>
2026-07-30 04:13:40 -04:00
sergioandClaude Opus 5 8c3bd323d5 audio: ALSA encendido en el kernel — y el muro real es libudev-zero, no el stack de audio
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>
2026-07-29 21:59:18 -04:00
sergioandClaude Opus 5 51c6830f58 audio: wireplumber (b3:39a9a06c) + lua (b3:e6c35f98) — y el Devices vacío es del KERNEL
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>
2026-07-29 20:40:12 -04:00
sergioandClaude Opus 5 5faa259603 audio: PipeWire es el servidor de la distro (decisión del usuario) — gvc conecta
Cierra una pregunta que estaba explícitamente abierta en varias recetas, y que es la
razón por la que `pulseaudio` se había construido con `-Ddaemon=false`: sólo el cliente,
para que gvc hablara el protocolo sin cerrar la decisión de prestado.

**Los dos conviven y por eso la decisión no rompe nada**: pipewire va con
`-Dlibpulse=enabled` ⇒ `pipewire-pulse`, un servidor que habla el protocolo de
PulseAudio. gnome-shell usa libpulse sin enterarse de qué hay del otro lado. No hubo que
tocar una línea del cliente.

  == gnome-qemu :: socket pipewire-0 OK
  == gnome-qemu :: socket pulse/native OK — gvc va a poder conectar
  Gvc-DEBUG: Updating client: index=32 name='pipewire'
  Gvc-DEBUG: Updating sink: index=33 name='auto_null' description='Dummy Output'

El `Failed to connect context: Connection refused` desapareció. El sink es auto_null, que
es la respuesta honesta: la VM no tiene tarjeta de sonido.

**⚠ Falta wireplumber** (gestor de sesión, Lua + su cierre). Sin él PipeWire acepta
clientes pero NO enumera ni enruta dispositivos. «El shell conecta» ≠ «hay sonido», y el
log de arriba es justo el caso donde confundirlos sería fácil. Queda escrito en la receta
y en el runbook.

GOTCHA MEDIDO: ya existía incoming-kde/pipewire.toml sellada y NO se puede reusar
hidratándola acá — los dos cierres traen glib y **no son la misma glib**:
incoming-kde/glib-shared y incoming-gnome/glib producen libglib-2.0.so.0.8800.1 con bytes
distintos (verificado con cmp) y comparten 370 rutas. Proyectar las dos deja que una gane
por orden de proyección ⇒ dos registros de GType en un proceso, el cuadro que costó el
episodio de colord. De ahí la copia en la cola GNOME (glib-shared→glib, dbus→dbus-shared,
pcre2-shared→pcre2), que sí re-construye. Lo que FUE gratis: alsa-lib, cuya única dep es
pkgconf y resuelve al catálogo PADRE ⇒ hash idéntico y cache hit (b3:93cae411).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:59:30 -04:00
sergioandClaude Opus 5 3d01d5cb04 gnome: el bus de sistema COMPLETO — faltaba polkit, y lo dijo la lista de nombres
bus: org.freedesktop.login1      OK
  bus: org.freedesktop.PolicyKit1  OK
  bus: org.freedesktop.Accounts    OK
  bus: org.freedesktop.UPower      OK

accounts-daemon y upowerd arrancaban, seguían vivos, y NUNCA adquirían su nombre: los
dos se bloquean en `polkit_authority_get_sync()` al iniciar, y `org.freedesktop.PolicyKit1`
no lo servía nadie —nuestra receta polkit es libs-only a propósito, porque el demonio lo
pone arje—. gnome-shell esperaba después 25s por cada uno.

**El dato que lo resolvió no salió del log de los daemons sino de la LISTA DE NOMBRES DEL
BUS**: ahí estaban como conexiones anónimas `:1.0`, `:1.1`, sin nombre bien conocido. Eso
distingue tres cosas que en el log del cliente se ven igual — «no arrancó», «arrancó y no
llegó a pedir el nombre» y «lo pidió y se lo negaron». Era la segunda. `gnome-start` ahora
espera cada nombre con NameHasOwner y, si no aparece, vuelca el log del daemon y la lista.

Receta nueva `arje-polkit-compat` (b3:03e0a86f), tercer shim de este tipo tras
arje-logind-compat y arje-sdlogin-compat. Su propio autor ya había escrito el diagnóstico
en el crate: «apps que usan polkit bloquean en CheckAuthorization si no responde nadie».
⚠ AUTORIZA TODO — es la postura de sistema confiado que arje ya tenía tomada, no algo que
esta receta introduzca; queda explícito en su comentario.

Dos gotchas del empaquetado:
- **La política de polkit ya existe y NO alcanza**: permite `own` sólo al usuario polkitd
  y el shim corre como root. Se agrega un `zz-arje-polkit-compat.conf` en vez de reescribir
  la de upstream — dbus lee system.d en orden alfabético y las posteriores ganan.
- **Los ficheros del rootfs fundido son hardlinks de SÓLO LECTURA del store** (0444). Un
  `cat >` encima falla con Permission denied y rompió un build. Regla: nunca sobrescribir
  un fichero que venga de un artefacto; agregar al lado.

Queda escrito en el runbook lo que sigue faltando (colord, y que el shell no tiene servidor
de sonido porque pulseaudio se construyó sólo-cliente: el demonio de audio de la distro
sigue sin decidirse) y las dos lecciones de método que costaron una iteración cada una.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:19:23 -04:00
sergioandClaude Opus 5 3999b421da gnome: cursor visible, iconos y el setuid del launch-helper de D-Bus
adwaita-icon-theme b3:429aef43 + hicolor-icon-theme. No es cosmética: con el cursor por
SOFTWARE —obligatorio en virtio-gpu— mutter dibuja la imagen que le da el tema, y sin
tema el puntero se movía INVISIBLE. Ahora se ve en la captura, y la notificación del
shell tiene su icono. Colores distintos 582 → 618.

hicolor arrastrada por `Inherits=hicolor` del index.theme de Adwaita: la cadena de
fallback tiene que terminar en algo que sea un TEMA (con su index.theme), no sólo un
directorio. No trae un solo icono propio — es el contrato, no el contenido.

Dos gotchas medidos:
- **hicolor 0.18 pasó de autotools a meson**: su tarball ya no trae `configure` (127).
- adwaita declara `gtk-update-icon-cache` como `required: true` para un
  `add_install_script` que **su propio autor marcó `skip_if_destdir: true`** — exige un
  binario que en un build empaquetado no puede ejecutar. Se borran los dos bloques.
  `required: false` NO alcanza: meson no propaga el disabler dentro de
  add_install_script y revienta con «Unhandled python exception». Se descartó declarar
  gtk4 como dep: acoplaría el hash de un paquete de DATOS al del toolkit.

**El setuid del launch-helper es la causa raíz de dos síntomas, no de uno.** El
`dbus-daemon-launch-helper` comprueba sus propios permisos antes de activar nada y se
niega si no es setuid root — de ahí el «The permission of the setuid helper is not
correct» de colord. Sin eso NINGUNA activación por bus de sistema funciona, que es
también por qué UPower timeouteaba a los 25s. La imagen ahora lo deja root:messagebus 4750.

Y `gnome-start` lanza upowerd, pero **verificando**: la primera versión decía «lanzado»
y el shell seguía esperando 25s. Un pid no es un servicio. Ahora comprueba que siga vivo
y, si murió, vuelca su log. Además crea los directorios de estado que upower deriva de
--prefix y sin los cuales se muere en silencio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 18:59:46 -04:00
sergioandClaude Opus 5 723f3ead33 gnome: 🏔🏔 EL ESCRITORIO PINTA — Overview de GNOME desde fuente, con captura
docs/evidencia/gnome-shell-qemu-2026-07-29.png: el conmutador de espacios de trabajo,
el reloj en el panel, la miniatura del escritorio, el dash, y la notificación estándar
de GNOME por sesión de root. Todo construido desde fuente sobre el kernel de hammer,
arje-zero como PID1 y musl.

**EL MURO NO ERA DE KMS.** El diagnóstico anterior culpaba al plano primario de
virtio-gpu («no advertised formats», sólo `Queue mode set`, ningún page flip). Era
correlación. Se probó quitando MUTTER_DEBUG_FORCE_KMS_MODE=simple —el log pasó a decir
`using atomic mode setting`, o sea que el cambio SÍ tomó efecto— y la pantalla siguió
con exactamente 2 colores. Refutada. Mutter no tenía un frame que presentar; no es que
no supiera presentarlo.

La causa estaba en el stack trace, en el log, desde el principio: la excepción por el
gschema `org.gnome.settings-daemon.peripherals.touchscreen` ocurre DENTRO de
`Main.start()`, construyendo los quick settings ⇒ el panel nunca se arma. Proceso vivo,
compositor con DRM master, nada que pintar.

Receta `gsd-schemas`: los gschemas de gnome-settings-daemon SIN g-s-d, que sigue
aparcada por GTK3/X11. **Tercera vez que aparece la misma lección** (tras la política
D-Bus de login1 y el `org.gnome.login-screen` de libgdm): un gschema no es un fichero de
datos del demonio, es una interfaz publicada que consume otro programa.

Dos trampas de diagnóstico, las dos ahora blindadas:
- **`glib-compile-schemas` sale con 0 aunque RECHACE un esquema.** Instalé los once XML,
  el log dijo `esquemas OK` y el shell seguía diciendo `not found`: faltaba
  `org.gnome.settings-daemon.enums.xml`, que meson genera con glib-mkenums y no es uno
  de los `.in`. Sin los `<enum>`, glib descarta el esquema entero, avisa por stderr y
  sigue. `gnome-start` ahora vuelca ese stderr SIEMPRE, no sólo al fallar.
- El log serial persiste entre arranques y me hizo leer dos veces un `STATUS +30s` de una
  VM ya muerta. Truncarlo antes de arrancar; `Failed to get "write" lock` avisa de que
  hay otro QEMU con el disco tomado.

Y queda escrito cómo validar CON PANTALLA sin humano delante: screendump por el monitor
de QEMU, con una métrica barata previa a mirar — contar colores distintos. Negro = 2;
pintando = 582, con el azul GNOME (2,60,136) al frente.

Lo que falta son detalles, ninguno impide el escritorio: lanzar upowerd desde gnome-start,
un tema de cursor, el setuid de colord y gnome-control-center.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 18:05:20 -04:00
sergioandClaude Opus 5 a98267468c gnome: gi-foreign-typelibs (b3:a159373c) — un .gir NO es un typelib
Con accountsservice adentro, la capa JS avanzó y murió un paso después:

  JS ERROR: Requiring Atspi, version 2.0: Typelib file for namespace 'DBus', version '1.0' not found

`Atspi-2.0.gir` declara `<include name="DBus" version="1.0"/>`. El `DBus-1.0.gir` YA estaba en
/usr/share/gir-1.0 (lo pone gi-foreign-girs), pero gjs resuelve en runtime contra el TYPELIB
compilado, no contra el XML — el XML le alcanza al scanner, no al cargador.

Receta aparte y no un compile agregado a gi-foreign-girs porque `yupana radio` da **17 sellados
cayendo a deuda** ahí (catorce recetas la declaran para escanear, mutter y gnome-shell entre ellas).
Compilar cuatro typelibs no justifica reconstruir la cima. No se pisan: una instala sólo en gir-1.0
y la otra sólo en girepository-1.0. Mismo criterio que separó udev-pc de libudev-zero.

Se compilan CUATRO nombrados explícitamente —DBus-1.0, DBusGLib-1.0, fontconfig-2.0, freetype2-2.0—
y no un glob: los .gir de X11 y cairo arrastran includes que no tenemos y son justamente los que
hacían SIGSEGV a g-ir-compiler (la razón de nuestro -Dbuild_introspection_data=false).

Además: gnome-start lanza accounts-daemon explícito. Su .service de activación está instalado, pero
la activación por bus de sistema pasa por dbus-daemon-launch-helper, que acá no es setuid root;
correr como root probablemente alcanzaría, pero depender de eso es depender de un accidente.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:10:05 -04:00
sergioandClaude Opus 5 03bdd41b4a gnome: el muro de input NO era libudev-zero — el fd de TakeDevice era bloqueante
Tres cambios de diagnóstico en `gnome-start` que, juntos, dieron el veredicto:

1. SONDA DE INPUT antes de lanzar el shell. `libinput list-devices` hace lo mismo
   que `init_libinput()` de mutter (udev_new + create_context + assign_seat) pero
   abriendo el devnode por su cuenta. Volvió con rc=0 y los tres dispositivos
   listados ⇒ **la hipótesis libudev-zero del runbook queda REFUTADA**: la
   enumeración por /sys funciona.

2. El volcado POR HILO corría DESPUÉS del `kill -ABRT`, o sea sobre un
   /proc/<pid>/task que ya no existía: salía vacío. Movido antes. Ahí apareció el
   dato: `tid 201 [Mutter Input Th] wchan=evdev_read syscall=0`. El hilo de input
   dormido en un `read()` de evdev dentro del kernel. (El core no servía: gdb no
   desenrolla a través de musl y devuelve `?? ()` para los 11 hilos no principales.)

3. `MUTTER_DEBUG` es una LISTA DE TÓPICOS (g_parse_debug_string sobre
   meta_debug_keys), no un booleano: con `1` no encendía nada. Ahora
   `backend,input,kms`, y el tópico `backend` imprime la última línea antes del
   cuelgue: «Opening and taking control of device file '/dev/input/event0'».

Más: ping D-Bus a login1 en el diagnóstico (el daemon respondía: no estaba
tildado) y copia de los logs de /tmp —que es tmpfs— a la raíz ext4 para poder
sacarlos con debugfs junto al core.

La causa está arreglada en tawasuyu (3dd88f582): `TakeDevice` abría sin
`O_NONBLOCK`. Receta re-pineada a 2445fa31c y re-sellada b3:7ffa9256.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 11:09:54 -04:00
sergioandClaude Opus 5 f56bab72f5 gnome: el compositor SUBE ENTERO en headless — el muro DRM es libinput, y aparece accountsservice
Experimento decisivo: `gnome-shell --headless --virtual-monitor 1280x800`. El
backend headless crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que
saltea init_libinput() — justo el último paso del hilo de input antes de señalar
`input_thread_initialized` (meta-seat-impl.c:3098). Resultado:

  libmutter-Message: Added virtual monitor Meta-0
  libmutter-Message: Using Wayland display name 'wayland-0'
  == compositor OK (wayland-0) — el shell ES el display server

⇒ **CONFIRMADO: el bloqueo del camino DRM está en libinput/udev, no en el resto
del arranque.** Todo lo demás de mutter funciona. Primer sospechoso libudev-zero.

Y con el compositor arriba aparece el muro siguiente, que es de otra naturaleza:

  Gjs-CRITICAL: JS ERROR: Requiring AccountsService, version 1.0:
                Typelib file for namespace 'AccountsService' not found

LA LECCIÓN DE MÉTODO: gnome-shell selló con su cierre de build COMPLETO (108/108)
y aun así la sesión muere pidiendo este typelib. **El cierre de build no es el
cierre de runtime**: todo lo que el shell carga por `imports.gi.*` desde
JavaScript es invisible al grafo de deps. Se encuentra ARRANCANDO, no compilando.

Se autoró recipes/incoming-gnome/accountsservice.toml. El configure pasa entero
—tres seds verificados contra el build real: generate-version.sh (deriva la
versión del nombre del DIRECTORIO, que en el sandbox es /src, y la rama git usa
`date`, o sea no-determinista), la aserción de wtmp (musl no define WTMPX_FILENAME
ni _PATH_WTMPX) y subdir('tests') (arrastra mocklibc, que llama fgetgrent, ausente
en musl)—. Ojo: -Dsystemdsystemunitdir tiene que ser literalmente `no`; con la
cadena vacía el meson interpreta "averiguá el directorio" y aserta pidiendo
systemd.pc.

NO SELLA todavía, y la frontera está contada símbolo por símbolo, no estimada:

  · libelogind exporta 14 símbolos (los que pedía mutter) y accountsservice usa
    OCHO que faltan: sd_get_sessions, sd_seat_can_multi_session,
    sd_session_get_display y los cinco de sd_login_monitor_*. Estos últimos son
    la parte con enjundia: no son getters sino una API de NOTIFICACIÓN (un fd
    poll-able). Sobre el diseño actual el camino natural es inotify sobre
    /run/systemd/{sessions,seats,users}.
  · fgetspent_r no existe en musl (extensión glibc de /etc/shadow, usada en
    src/daemon.c:265): necesita shim o parche a la variante no-reentrante.

Ninguno de los dos es de accountsservice: son huecos de NUESTRA capa de compat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 06:07:42 -04:00
sergioandClaude Opus 5 34c7962b29 gnome: cae el SIGSEGV — el shell toma DRM master e input; muro nuevo en la hebra de input
Dos bugs REALES de arje-logind-compat, encontrados arrancando la imagen y
arreglados (con test) en el árbol de tawasuyu — todavía LOCALES, sin pushear:

  1. La sesión se creaba PEREZOSAMENTE. `ensure_user` sólo corría dentro de un
     método del Manager, y `write_login_state` vive adentro. Pero mutter no
     empieza por D-Bus: lo primero que hace es sd_pid_get_session(), que
     arje-sdlogin-compat resuelve LEYENDO /run/systemd/sessions. Huevo y gallina:
     el estado en disco sólo se escribía después de un pedido que sólo ocurre si
     el estado ya existe. Ahora se crea al arrancar (eager_session).

  2. El object path de la Session estaba MAL ESCAPADO: era `/session/_1`. La
     convención de systemd (bus_label_escape) codifica `_<hex>` todo lo que no sea
     [A-Za-z0-9] **y también el primer carácter si es dígito** ⇒ el id "1" da
     `_31`. Importa porque el cliente calcula el path por su cuenta y NO pregunta:
     mutter reimplementa la misma regla en meta-dbus-utils.c. Con `_1` no había
     nadie sirviendo ahí, la propiedad `Seat` volvía NULL y mutter —que no
     chequea— moría de SIGSEGV en get_seat_proxy. Los objetos User NO usan este
     escapado (systemd hardcodea `_<uid>`), así que user_path() queda igual.

Y ARJE_LOGIN_STATE=1 YA EXISTÍA: el comentario del daemon dice literalmente "en
arje (sin systemd) el launcher de sesión lo prende. Default off" — y el launcher
es gnome-start. El puente que escribía /run/systemd/ a mano era reinventar esa
perilla; queda de fallback inerte.

Resultado: el shell ya no crashea. Corre con 12 hilos, /dev/dri/card0 abierto
tres veces y /dev/input/event0 abierto — el TakeDevice de logind funciona y el
compositor tiene DRM master e input. Queda bloqueado en
meta_seat_impl_initable_init (meta-seat-impl.c:3154): espera en un condvar a que
la "Mutter Input Thread" avise que inicializó, y nunca avisa. Hipótesis principal
libudev-zero, que ya dio un episodio idéntico en el frente de la USB nvidia.

El gnome-start ahora le fuerza un core con SIGABRT al proceso colgado: sin gdb en
la imagen es la única forma de ver dónde está parado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 22:49:46 -04:00
sergioandClaude Opus 5 5295aa35c6 gnome: la cima ARRANCA en QEMU — muere en meta_launcher_new, causa localizada
gnome-shell bootea, compila sus esquemas, toma el DRM de virtio-gpu y se declara
Wayland display server. Después SIGSEGV. El backtrace del core no deja dudas:

  #4 g_variant_get (value=0x0, "(s&o)")     ← GVariant NULO
  #5 get_seat_proxy   src/backends/meta-launcher.c:407
  #6 meta_launcher_new (META_LAUNCHER_FLAG_TAKE_CONTROL)

Mutter pide la propiedad `Seat` del objeto **Session** de logind — `(s&o)` es su
firma estándar (nombre_del_seat, object_path). arje-logind-compat adquiere
org.freedesktop.login1 y sirve el Manager, pero NO expone un objeto Session con
esa propiedad ⇒ la lectura devuelve NULL y mutter desreferencia sin chequear. Que
mutter no valide es fragilidad suya; el hueco es nuestro.

Cinco eslabones hubo que armar antes de llegar a ese muro, ninguno anotado:
  1. gschemas.compiled NO se genera con DESTDIR seteado (meson lo dice en el log
     del build) ⇒ GSettings abortaba en el primer g_settings_new().
  2. GI_TYPELIB_PATH tiene que incluir /usr/lib/gnome-shell: St/Shell/Gvc/Shew se
     instalan aparte por ser privados del shell. Es el env sin análogo en KDE.
  3. /var/run no existía en la base metal; arje-logind-compat busca el bus en la
     ruta legacy y sin el symlink se iba a "modo idle".
  4. La política D-Bus de login1 faltaba (system.conf trae <deny own="*"/> y
     normalmente la instala systemd). PERTENECE al artefacto de arje: está en el
     script de imagen sólo para dejar visible qué falta empaquetar.
  5. /run/systemd/{sessions,seats,users} vacíos — libelogind resuelve la C-ABI
     sd-login LEYÉNDOLOS y nadie los escribe (el Announce de arje-logind-compat al
     bus del fractal falla con "identity mismatch"). gnome-start los escribe A
     MANO y está MARCADO COMO ANDAMIO. Con ese puente desaparece el "Failed to
     find any matching session".

Gotcha que costó una iteración: `kill -0` TIENE ÉXITO sobre un zombi (el padre no
lo cosechó todavía), así que el script reportaba "sin wayland-0 tras 45s" cuando
el shell había muerto en el primer segundo. Mirando State: de /proc/<pid>/status
sale el código real, 139.

Todo el ciclo (hidratar → imagen → bootear → sacar el core con debugfs → gdb) y
la lista de lo que falta quedan en docs/runbooks/gnome-qemu-desktop.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 21:47:11 -04:00