17b86cf85eb20e33f52744788f7305ad8fb176ec
252
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
888fdc31c7 |
firmware: SOF para TigerLake — el DSP de audio, pineado como el de i915
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
8d1efca26e |
gnome: GIRepository-2.0 — el typelib que no pide el shell sino gjs
Con los siete de `dependencies.js` cerrados, el arranque avanzó y murió en uno que NO
está en esa lista:
JS ERROR: Requiring GIRepository, version 2.0: Typelib file ... not found
El que lo pide es **gjs**, desde su propio JavaScript embebido: su GResource trae
literalmente `imports.gi.versions.GIRepository = '2.0';` (verificado con strings sobre
libgjs.so.0). Dep de runtime del INTÉRPRETE — invisible al grafo de build Y a la lista
del shell. O sea que `dependencies.js` es necesario pero no suficiente: hay una capa
más abajo.
HAY DOS GIRepository Y NO SON LO MISMO, y los números confunden a propósito:
· GIRepository-3.0 = la API NUEVA, la que GLib absorbió (libgirepository-2.0). La
produce glib-introspected y ya estaba.
· GIRepository-2.0 = la API VIEJA, la de libgirepository-1.0.so.1, contra la que gjs
está enlazado. La produce gobject-introspection, pero sólo con
-Dbuild_introspection_data=true, y el nuestro va con false.
La 2.0 es la vieja porque nombra la librería 1.0; la 3.0 es la nueva porque nombra la 2.0.
Receta aparte en vez de prenderle la opción a g-i: su radio alcanza toda la cadena GNOME
(catorce recetas la declaran para escanear) y además traería de vuelta los typelibs de
X11/cairo que motivaron apagarla. Acá el radio es cero.
Replica el custom_target('gir-girepository') de upstream (gir/meson.build:494) pero
escaneando contra la libgirepository-1.0 ya instalada. **La lista de fuentes va enumerada
a mano y no como glob**, y no es prolijidad: `girepository/*.h` barre también
`gitypelib-internal.h`, que declara G_TYPELIB_ERROR; el scanner emite entonces el stanza
del error-quark y su binario temporal no linkea, porque `g_typelib_error_quark` es LOCAL
en la .so instalada. Upstream nunca lo escanea — el glob era el error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
bc4d0c8aea |
gnome: los 7 typelibs CERRADOS — librsvg y ibus, y dos deudas viejas pagadas de paso
Rsvg-2.0 librsvg b3:351f4658 IBus-1.0 ibus b3:d4d3750a **1. PERILLA NUEVA DEL LAB: `[source] cargo_vendor`.** `detect_build_system` asume UN sistema de build por árbol, y librsvg genuinamente tiene dos: `configure.ac` gana por precedencia pero su Makefile llama a `cargo build`, que dentro del sandbox hermético no tiene red. La perilla fuerza el vendoreo (que ocurre en el fetch, donde sí hay red) sin tocar la detección. **No entra al ArtifactHash** —decide de dónde salen las deps, no cuáles: eso lo fija el Cargo.lock, ya bajo el sha256 de la fuente— así que se puede prender en una receta ya sellada sin re-hashear nada. Con test, y verificado en vivo: libelogind no movió su hash tras el cambio. **2. LA DEUDA DEL UNWINDER, PAGADA.** librsvg moría en `undefined reference: _Unwind_DeleteException`. No es de librsvg: es de la `std` de rustc, que trae landing pads y espera el runtime que en glibc vive en libgcc_s. Es la deuda que el corpus arrastra desde matar-gcc —«las 12 recetas Rust son UN problema, no 12»— y el remedio estaba a mano: **zig empaqueta la libunwind de LLVM** y exporta los `_Unwind_*` (verificado con nm). Alcanza `LIBS=-lunwind`. Va por LIBS y no por LDFLAGS porque autotools pone LIBS al FINAL de la línea de enlace, que es donde tiene que ir una librería que resuelve símbolos indefinidos. librsvg va en 2.58.5 y no 2.59+: en 2.59 cambió a meson + cargo-c, y cargo-cbuild enlaza el crate `cargo` entero (libgit2, libssh2, libcurl, openssl) — una campaña propia por un binario que sólo corre en el constructor. 2.58.5 produce el mismo Rsvg-2.0. Mismo criterio que gnome-desktop 44.5. **3. `x11-compose-data`, y es una tensión que vale la pena tener escrita.** ibus COMPILA la tabla de teclas muertas de X11 dentro de libibus (Makefile.am:310, incondicional, sin `--disable-`). Sin datos no construye. Los datos viven en el tarball de libX11 por historia, no por necesidad técnica: son 5192 líneas de reglas. La receta extrae SÓLO los ficheros de locale reproduciendo la regla de upstream (`cpprules.in`: cpp crudo + CPP_SED_MAGIC literal) y no compila una línea de X11. Un escritorio Wayland-only sigue necesitando la tabla de composición del mundo Unix. Gotchas de ibus, los tres medidos: su ayuda MIENTE (`--enable-gtk4`/`--enable-wayland` sugieren default apagado; el default es `yes`); `--disable-emoji-dict` NO apaga `--disable-unicode-dict`; y con `--disable-wayland` el build muere igual porque `tools/main.c` llama wl_display_* sin `#ifdef` mientras WAYLAND_LIBS sólo se agrega si la opción está prendida — bug de upstream en su propia configuración sin Wayland. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fd9f9dff6d |
gnome: libgdm (b3:46fac484) + cairo-1.0.typelib — la capa JS avanza dos muros más
Dos rondas del mismo patrón, las dos destapadas ARRANCANDO: 1. `Requiring Gdk 4.0: Typelib 'cairo' 1.0 not found` ⇒ cairo-1.0 sumado a gi-foreign-typelibs. Necesita el mismo sed que gi-foreign-girs (viene como .gir.in con dos placeholders), para compilar EXACTAMENTE el XML que está instalado: si divergieran, el typelib describiría otra librería. NO segfaultea g-ir-compiler — el crash que motivó -Dbuild_introspection_data=false era del g-i de antes de la isla dinámica. La lista de cinco no se adivinó: sale de leer los <include> de TODOS los .gir del cierre y cruzarlos contra los typelibs existentes. 2. `Requiring Gdm 1.0` ⇒ receta `libgdm`, SÓLO la librería cliente. gnome-shell la importa sin condicional (js/misc/dependencies.js:12), o sea que **la librería cliente de GDM es dep de runtime del SHELL, no del greeter** — y eso no se ve en ningún meson.build. `gdm.toml` (el demonio entero) sigue aparcada por linux-pam, pero PAM es del DEMONIO: libgdm/ no lo toca. La receta corta por ahí; cuando exista linux-pam las dos conviven. Para que libgdm compilara hicieron falta tres símbolos más en libelogind (tawasuyu 9bf977a52, re-pineado a 98db584fd, re-sellado b3:c1fc4bd8): sd_seat_get_sessions, sd_session_get_service y **sd_booted**, éste en una cabecera nueva systemd/sd-daemon.h. sd_booted NO es un stub que devuelve 0: comprueba lo mismo que systemd —que exista /run/systemd/system/— y bajo arje no está, así que responde 0 porque ES 0. gnome-shell re-sellado b3:b2d5919b por la cascada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
08fc1d4b92 |
gnome: accountsservice SELLA (b3:0328b0d0) — cae la frontera de la C-ABI de logind
Los dos huecos que bloqueaban esta receta estaban medidos desde ayer símbolo por
símbolo, y ninguno era de accountsservice. Los dos cerrados:
1. libelogind pasa de 14 a 22 símbolos (tawasuyu 8d892151b, re-pineado a b56aeff46,
re-sellado b3:9650ee06). Los ocho nuevos son los que usa accountsservice, con
`sd_login_monitor_*` implementado sobre inotify en /run/systemd/{sessions,seats,users}.
2. `fgetspent_r` no existe en musl ⇒ accountsservice-fgetspent_r-musl.patch. NO es
sustituir por `fgetspent()` a secas: daemon.c guarda los buffers en un GHashTable
y `fgetspent()` devuelve un struct estático que se reescribe en cada llamada —
todas las entradas de la tabla apuntarían al último usuario leído. El parche copia
el registro al buffer del llamador, y sigue el patrón que el propio accountsservice
ya usa para /etc/passwd (`src/fgetpwent.c` bajo `#ifndef HAVE_FGETPWENT`).
Produce `AccountsService-1.0.typelib`, que es exactamente lo que la capa JS pedía.
De paso, dos cosas que este frente enseñó y quedan horneadas:
- libelogind necesitaba `cargo_vendor_dir` (el `vendor/` de smithay entró a la rama
selfhost con el merge de main; el mismo choque que ya tenía arje-logind-compat).
- `hydrate-gnome.sh` ahora hidrata DOS raíces por defecto. accountsservice no es dep
de build de nadie: es dep de RUNTIME, resuelta por gjs al arrancar. **El cierre de
build no es el cierre de runtime**, y lo que el shell carga por `imports.gi.*` hay
que nombrarlo a mano o no entra al rootfs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
8cf9a87dd2 |
arje-logind-compat: repineado al fix (b3:5e4b8e04) — la cadena vuelve a ser reproducible
Los dos arreglos de arje-logind-compat (sesión eager + object path de Session
escapado como systemd) están commiteados y pusheados en tawasuyu, así que la
imagen ya NO corre un binario compilado a mano: corre el artefacto sellado, y se
verificó que da EXACTAMENTE el mismo resultado (mismo `Added virtual monitor
Meta-0` → `Using Wayland display name 'wayland-0'` → mismo muro en el typelib de
AccountsService).
El pin va a la rama selfhost, no a main: esa rama existe para que hammer pueda
construir con --locked de forma hermética (el monorepo gitignora el Cargo.lock).
Estaba MUY atrás — el artefacto viejo ni siquiera tenía el objeto Session que
mutter necesita —, así que se le mergeó main y se regeneró el Cargo.lock, que con
main al día había quedado viejo y habría hecho fallar el --locked.
Y ahí apareció una colisión ya conocida pero no aplicada acá: el monorepo COMMITEA
su propio `vendor/` (`[patch.crates-io] smithay = { path = "vendor/smithay" }`, la
copia parcheada que mirada usa para el tearing) y `cargo vendor` de hammer escribe
en `vendor/` por defecto, pisándolo. El build moría con
`failed to read /src/vendor/smithay/.cargo-checksum.json`. Se resuelve con el
campo que ya existía para esto: cargo_vendor_dir = ".hammer-cargo-vendor".
De paso, el script de imagen elige el artefacto por `hammer hash` sobre la receta
—el que corresponde a la receta de HOY— en vez de por el más reciente del store:
con dos artefactos del mismo paquete conviviendo, la fecha no dice cuál es el
vigente. Mismo criterio que hydrate-gnome.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
f6734d6ce6 |
gnome: hydrate-gnome.sh — el cierre de runtime de la cima CIERRA 108/108
Proyecta al FHS el cierre de una receta GNOME desde artefactos SELLADOS, sin
rebuild. Resultado sobre gnome-shell: **108 recetas, 0 faltantes** — 614
binarios, 455 .so, 49 typelibs.
Diferencia con scripts/kde/hydrate-from-store.sh, y por qué importa: aquél
resuelve el cierre desde un index.json de repo y elige el artefacto por MTIME con
un CUTOFF. Eso es una heurística — si dos artefactos del mismo paquete conviven
en el store, la fecha no dice cuál corresponde a la receta VIGENTE. Acá el cierre
sale del GRAFO REAL de recetas (deps.build, resolución hermano→padre, la misma
que usa hammer) y el artefacto se elige por `hammer hash`. Cero ambigüedad, y si
falta algo el reporte dice qué receta y con qué hash lo buscaba.
Auditado además el cierre DINÁMICO del rootfs (452 ELF, 104 librerías NEEDED).
Sin resolver quedan cuatro, y sólo dos son hallazgos:
libc.so 359 consumidores — es el propio musl (en musl el loader
ES libc); lo aporta la base metal, no es hueco.
libc.musl-x86_64.so.1 17 artefactos (nss + spidermonkey) piden ESTE soname en
vez de libc.so. Es la MISMA libc: los construidos con el
gcc/clang de Alpine emiten un soname distinto al de
zig-cc. No rompe si el rootfs trae los dos nombres, pero
es una fisura de consistencia a documentar.
libstdc++.so.6 + SÓLO libmozjs-128.so y js128 ⇒ **la sesión GNOME arrastra
libgcc_s.so.1 el runtime C++ de Alpine por la cadena
gnome-shell → libgjs → libmozjs**. Es exactamente la
"última milla" de matar-gcc que se cerró para cmake con
-static-libstdc++ y que spidermonkey no cubrió. Radio
medido: 2 sellados (gjs, gnome-shell). Pendiente, y
conviene en el worker: el compile de mozjs come ~8GB+.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5aec112629 |
gnome onda 3: cierra las 3 fronteras de gnome-desktop + el latido regenera el grafo GNOME
Las 3 fronteras que el propio comentario de gnome-desktop declaraba:
iso-codes receta nueva (datos + .pc). Ya no está en download.gnome.org (404);
Salsa es GitLab y su archive no es determinista (lección de cairo-shared),
así que la fuente es el .orig.tar.xz inmutable del pool de Debian.
Sin traducciones: i18n.gettext exige msgfmt completo y sólo hay
gettext-tiny — se vacían los 8 meson.build de dominio.
libseccomp receta nueva (autotools estático, gperf de build-dep real).
xkeyboard-config duplicada desde incoming-kde: fichero idéntico ⇒ MISMO ArtifactHash
(b3:bcf9b766) ⇒ ya está SELLADO. Frontera cerrada con cero rebuild.
Y un punto ciego del latido: cosecha-cron regeneraba build-state.json y el de KDE, pero
NUNCA el de GNOME. El grafo llevaba días mintiendo `never` sobre gobject-introspection y
toda la onda 2, que están selladas. Un grafo viejo miente con la misma cara que uno fresco.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
da26b3a280 |
gnome onda-1: cola aislada incoming-gnome-onda1 para el worker
Las 3 raíces de la onda 1 (gsettings-desktop-schemas, gobject-introspection, gnome-desktop) copiadas a una cola propia + añadida al QUEUES del worker-loop. Aislar evita que el worker dispare rebuilds de spidermonkey/mutter (onda 2/3). El cierre real de RECETAS de las 3 es 39 nodos, 0 en incoming-kde: el "gap del resolver" que temía era de seed-edges (.pc cairo→libX11), NO de deps declaradas. El espinazo corpus (38 nodos: gtk4/cairo/pango/gdk-pixbuf en recipes/) está 100% sellado ⇒ el worker cachea todo e sólo construye las 3. Hashes byte-idénticos a los sellos de incoming-gnome (base_dir no entra al hash). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
afd867ea51 |
yupana: drenar reckona honesto un perfil sin cierre (guardián GNOME)
drenar --perfil escritorio-gnome erraba 'no encontré un grafo (¿corriste
build-state.py?)' — mentira: el grafo GNOME existe, pero NINGUNA raíz del perfil
(mutter/gnome-shell/gdm) está autorada ⇒ 0 nodos etiquetados con el perfil, y la
comprobación por etiqueta en cargar() fallaba. Dos correcciones:
· cargar(): una cola NO-corpus identifica su grafo unívocamente ⇒ matchear por
cola; la etiqueta sólo desambigua dentro de corpus (perfiles solapados).
· main(): ámbito vacío con perfil ≠ 'imagen completa' — es 'SIN CIERRE todavía:
0 raíces autoradas', y apunta a autorar top-down.
KDE (162 sellados) y corpus (769/4-deuda) sin regresión. El yupana-gnome queda
armado en las 4 capas: objetivo, radio/grafo, keystones y drenar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9a028ba04f |
gnome: SPIDERMONKEY (mozjs 128) SELLA en el hub — keystone caído
El motor JS de Mozilla, keystone del frente GNOME (gjs→gnome-shell cuelgan de él),
sella b3:c48dbd10: libmozjs-128.so + libjs_static.a + shell js128 + mozjs-128.pc +
550 headers. Compiló el C++ entero a -O3 -j8 sin OOM en el hub (31GB/0 swap, al filo).
Seis capas iteradas desde el borrador:
1-2. FIX DE INFRA: el rootfs builder tenía clang pero NO el paquete (Alpine
separa los binutils LLVM del compilador). Sin llvm-ar/llvm-objdump/llvm-profdata
configure aborta. bootstrap-devfs.sh: +llvm22 en NEEDED + paso 3a-ter que
symlinkea la suite llvm-* a /usr/bin (Alpine la deja en /usr/lib/llvm22/bin sin
exponerla). Aplicado ya al rootfs real (.dev-fs/alpine).
3. triple: mozjs no traduce vendor ; 4. debe COINCIDIR con el host de rustc
(); fijado ese exacto.
5. venv de mach: cada fase es un bwrap con /tmp fresco ⇒ el venv no sobrevivía de
configure a compile. Anclado a /src/.mozbuild (persiste, es scratch).
No re-hashea artefactos (el rootfs no entra al hash). OJO: el worker golden también
necesita llvm22 (ya cubierto por bootstrap-devfs.sh para la próxima provisión).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
d33fc94fc5 |
gnome: enciende la CAPA GRAFO del yupana-gnome
build-state.py gana --gnome (análogo a --kde): carga recipes/incoming-gnome/ y sale a build-state-gnome.json. drenar/yupana/seed-graph aprenden ese grafo (keystones, drenar --perfil escritorio-gnome, radio de nodos incoming-gnome, frontera). Hoy la cola está vacía ⇒ el grafo trae 0 nodos gnome; se poblará al aterrizar recetas y ahí drenar/keystones reckonan solos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0b333b4c9e |
gnome: integra el frente a yupana — perfil escritorio-gnome + reckoning honesto
1. targets.toml: [perfil.escritorio-gnome] (9 raíces de sesión, cola incoming-gnome) ⇒ `yupana objetivo` ya lo ve; fluye por targets.py. build-state lo SALTA hasta que la cola se cargue (--gnome), así declararlo no reporta raíces fantasma. 2. seed-gnome.py OPTIMIZADO: hornea la triage (sustitución/tooling/opcional/espinazo) como HIPÓTESIS con el caveat "el cierre de nixpkgs sobreestima ~5-10×", marca el keystone (spidermonkey→gjs→gnome-shell), y deja de mentir con el 465 crudo. El espinazo (353) se reporta como COTA ALTA, no como lista de build. Falta (se activa al autorar): grafo build-state-gnome + flag --gnome ⇒ keystones/ drenar nativos sobre GNOME. Hoy la capa OBJETIVO del yupana-gnome está; la GRAFO no. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
224b15f231 |
gnome: añade seed-gnome.py + los mapas del subtree (faltaron en 72de19c)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b98995b7b4 |
deadman: NO contar farm-worker-loop como trabajo (3er bug del dead-man)
El loop del worker está SIEMPRE vivo (idle-loopea con la cola vacía). Contarlo en hay_trabajo() reseteaba los ticks a 0 cada 10 min ⇒ el worker NUNCA acumulaba idle ⇒ NUNCA se auto-mataba. Quedó 2h17m idle quemando € (2ª vez). El loop construyendo YA se detecta por su hijo `hammer build`; el loop vacío NO es trabajo. Se afina el match a `release/hammer.* build ` y se suma `campana-deuda`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2bd1944173 |
deadman: --puede-borrar sourcea /etc/hammer-deadman.env (o daba falso-negativo)
Cazado con el 1er worker real: el token vive en /etc/hammer-deadman.env (lo carga systemd como EnvironmentFile), pero corrido A MANO (farm-up --puede-borrar) ese fichero no se lee solo ⇒ mi cadena de fallback no lo veía ⇒ decía 'sin token' y farm-up habría DESTRUIDO un worker que sí puede matarse. Fix: sourcear el EnvironmentFile antes de la cadena de tokens desnudos. Verificado en worker real: 'SÍ: puedo borrar mi id=154276696'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3de75a5229 |
granja: el dead-man del worker se mata SOLO (sin hub) — 3 bugs + gioser doble-blindado
El worker DEBE matarse solo, sin depender de la laptop (es la razón de ser del
dead-man: vive en el worker; el volumen hace que morir no pierda nada). El reaper
del hub queda sólo como último recurso. El dead-man estaba TRIPLEMENTE roto:
1. TOKEN EN EL FICHERO EQUIVOCADO: hay 2 caminos de creación con el token en
lugares distintos (farm-up → /etc/hammer-deadman.env; harkaq-vol → /root/
.hcloud-token). La service lee sólo el primero ⇒ un worker del otro camino
quedaba sin token, disparaba pero salía 1 "sin HCLOUD_TOKEN". Fix: deadman.sh
busca el token EN CADENA (volumen primero, que es lo más persistente).
2. REGEX DEL ID ROTO: la API devuelve JSON con espacio ("id": 126, no "id":126)
y el dead-man usaba '"id":[0-9]+' ⇒ NUNCA resolvía su id, aun con token
válido. Fix: '"id":[[:space:]]*[0-9]+'. (Doblemente roto: por eso NUNCA murió.)
3. FIRING ≠ CAN-DELETE: farm-up sólo verificaba que el timer dispara. Ahora
corre `deadman.sh --puede-borrar` (token en cadena + ve su id en la API) y si
NO puede matarse, DESTRUYE el worker ahí mismo. Un worker que no se autodestruye
es inadmisible ⇒ jamás debe existir.
gioser DOBLE-BLINDADO en TODOS los sitios de borrado (lista negra por nombre +
label role=hammer-worker), igual que farm-down ya tenía: reaper, farm-up-destroy,
deadman, y el helper de harkaq-vol. "Ahí está nuestra vida." Verificado vivo (104d).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
be06314336 |
granja: el reaper dropea de .fleet los workers que ya no existen en hcloud
Sin esto un server borrado se quedaba en .fleet para siempre (describe falla ⇒ 'sin label' ⇒ nunca se dropeaba). Ahora si no existe en hcloud, se saca. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1203f86cda |
granja: reaper HUB-SIDE — la garantía "vps idle inadmisible" deja de depender del worker
INCIDENTE (2026-07-23): un worker quedó 3.5h idle quemando € mientras el usuario
dormía. El dead-man del worker estaba "activo" (timer disparando cada 10 min)
pero FALLABA con exit 1 en cada tick: llegaba a matarse pero su
/etc/hammer-deadman.env no tenía token válido ⇒ salía 1 "sin HCLOUD_TOKEN" y
nunca se borraba. Verificaron que el timer DISPARA, no que puede BORRAR —
firing ≠ can-delete.
Causa de fondo: la garantía dependía de que CADA worker se auto-provisione bien
un token, y eso falla EN SILENCIO. Un invariante que depende de una provisión
frágil no es un invariante.
FIX en dos capas (defensa en profundidad):
1. REAPER HUB-SIDE (cosecha-cron.sh): el hub tiene un token que funciona y el
latido corre cada 30 min aunque no haya sesión (setsid). Un worker sin trabajo
activo REAPER_MAX=2 ciclos (~1h) se BORRA desde el hub, con el MISMO blindaje
de label role=hammer-worker (gioser jamás pasa). Cubre "dead-man del worker
roto". El dead-man del worker sigue cubriendo "hub caído".
2. farm-up verifica CAN-DELETE, no sólo firing: que el token del worker vea su
propio id en la API (lo mismo que hace al morir). Si no puede, LO GRITA al
provisionar en vez de descubrirlo 3.5h tarde.
Acción inmediata tomada: worker borrado a mano (label verificado), €0 baseline
restaurado — sólo queda gioser (protegido, fijo). Todo estaba cosechado.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8c10c0a908 |
yupana: modelo EXTRA-GRAFO — preflight aprende los patrones de hueco de entorno
El grafo modela deps (R1: Receta×Receta). El entorno es OTRA capa: R2 (Receta×
Capacidad, qué exige un build) y R3 (Host×Capacidad, qué ofrece la máquina).
Construible = clausura(R1) ∧ R2⊆R3. Esos huecos viven FUERA del grafo (infra del
sandbox, toolchain, layout de fs) y una matemática de grafos sola no los ve —
pero SÍ se aprenden, como patrones con predictor, calibrados contra fallos reales.
`yupana preflight <receta>` corre los patrones ANTES de construir. Primer patrón:
overlay-lowerdir, la cicatriz de kio. Habría marcado kio antes de quemar la
campaña descubriéndolo a los golpes:
kio: 52 deps ⇒ lowerdir ~5346B > 4096B (una página) ⇒ depende del merge
CALIBRADO, no adivinado: el estimador daba corto (2974B "ok") hasta usar el
prefijo que bwrap VE en runtime (/oldroot/…, ruta del worker, no la del hub).
Con eso: kio estimado 5346B vs real 5315B — 31B de error, sin fudge. El primer
predictor "obvio" mentía con confianza; la lección de siempre.
preflight --perfil escritorio-kde destapa lo sistémico: las 40 recetas KDE
profundas CUELGAN del merge (plasma-desktop: 114 deps → 11708B, casi 3× el
límite). Todo el escritorio dependía del fix
|
||
|
|
6a0dc40a05 |
sandbox: el merge de deps cae en el store (mismo fs), no en la raíz — desbloquea kio
merge_deps_layer funde las deps en UNA capa overlay por hardlinks (cp -al) para no desbordar el lowerdir de overlayfs (tope ~4096B por página). Pero calculaba la ruta del merge en deps.parent().parent() = <store>/../.dmerge, asumiendo que el store está en <base>/store del MISMO fs. En el worker el store es un VOLUMEN bind-montado (/opt/hammer/store en /dev/sdb, /opt/hammer en /dev/sda1) ⇒ el merge caía en la raíz, cp -al fallaba cross-device, y el fallback apilaba las ~50 deps de kio directo → lowerdir 5315B > 4096B → "bwrap: Can't make overlay mount" → kio (EL keystone, gatea 36) imposible de construir. Por eso estaba clavado. Fix de una línea: el merge va en el padre INMEDIATO de las deps (= el store mismo), garantizado mismo fs. Confirmado en el worker: cp -al a /opt/hammer/.dmerge falla "Invalid cross-device link"; a /opt/hammer/store/.dmerge funciona. En el laptop andaba por casualidad (store y su padre en el mismo fs). `.dmerge` (dot-prefix) es invisible para el store lookup/build-state como .times. cosecha excluye /.dmerge del rsync (es scratch de hardlinks; sin -H rsync lo expandiría a copias reales). Descubierto levantando el worker para poblar tiempos: yupana keystones apuntó a kio, la campaña falló ahí, y el radio de lowerdirs (5315B) destapó la causa. Una "sorpresa de entorno" (infra del sandbox, fuera del grafo) que el frente de timing sacó a la luz. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
85b5a9bc46 |
yupana: instrumentar duración de build en el worker → camino crítico PESADO
El peso que le faltaba a las ondas: sin él, critical-path = nº de pasos; con él, ETA real en segundos. INSTRUMENTACIÓN (worker): scripts/farm/build-timed.sh envuelve `hammer build` midiendo la pared y la registra en $STORE/.times/<hash>-<host>.json — un SIDECAR del store, NUNCA dentro del artefacto. La duración es no determinista (varía por máquina/carga) ⇒ no puede tocar el ArtifactHash. Keyed por hash (CAS-safe: dos workers no se pisan) + host (varias muestras por receta). Viaja al hub con el rsync de store que ya hace la cosecha; ninguna herramienta del store lo confunde con artefacto. Sólo builds REALES (pared ≥ UMBRAL 3s) — los cache-hits no envenenan la mediana. campana-deuda.sh ahora construye vía build-timed.sh (transparente, mismo exit). CONSUMO (hub): yupana._tiempos() carga name→mediana de segundos; keystones computa el CAMINO CRÍTICO pesado = longest weighted path del subgrafo de deuda (peso = segundos de build). La cadena más larga hay que construirla en SERIE aunque haya ∞ workers ⇒ es la ETA con paralelismo infinito. Degrada con gracia: sin datos, peso=1 y el camino crítico = nº de pasos (lo que ya daban las ondas). Verificado: con muestras sintéticas da "ETA 19 min sobre 5 pasos, kio → kparts → frameworkintegration → breeze → plasma-integration"; sin datos degrada a "5 pasos (SIN datos)". store/.times está git-ignorado (metadata de máquina, no se commitea). LÍMITE honesto (en la cabecera de build-timed.sh): `hammer build` arrastra deps ⇒ la pared incluye deps no selladas. En orden topológico (drenar) las deps ya están selladas y la pared mide sobre todo ESTA receta — cota superior buena para pesar. La precisión exacta pediría instrumentar el sellado dentro de hammer-build (Rust). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4f609080df |
yupana: los 2 "bugs" de duplicados eran falsos positivos MÍOS — detector afinado, 0 reales
El usuario pidió arreglar itstool y la sombra de dbus. Apliqué el reflejo
(`yupana radio` + diff completo antes de tocar) y NINGUNO era un bug: ambos eran
patrones deliberados y documentados que mi detector, demasiado superficial, leyó
mal. Si los "arreglaba" a ciegas rompía dos builds.
· dbus: corpus/dbus (estático) e incoming-kde/dbus (dinámico) NO son redundantes
— qtbase linkea libdbus-1.so dinámicamente para Qt6DBus. El detector comparó
sólo versión+sha+deps, no el BUILD. Borrar la sombra rompía KDE.
· itstool: es un STUB documentado (`[source]=carrier`). El itstool real es
Python con libxml2-bindings ausentes en el lab; este genera un script inline y
sólo PRESTA el tarball de gettext-tiny. Apuntarlo al itstool real rompía
appstream (radio 5) con un source que ni compila acá.
Los 3 *-hello son el mismo patrón carrier; prison/prison-scanner una variante
cross-nombre (misma fuente, WITH_ZXING distinto).
FIX = el detector, no las recetas. Tres señales que le faltaban:
1. huella de BUILD en la firma de sombra (estático≠dinámico ⇒ no redundante).
2. CARRIER: ≤1 receta del grupo construye la fuente ⇒ el resto presta el tarball.
Señal: ¿invoca make/meson/cmake/ninja/cargo? (comentarios strippeados — la
prosa "invocación de meson" de un stub daba falso builder, lo cazó el guardián).
3. variante cross-nombre: ≥2 builders con BUILD distinto = deliberado, no bug.
Resultado: 0 colisiones reales (invariante c se cumple), 4 carrier + 14 variantes
+ 12 sombras justificadas, todas benignas.
Guardián extendido: ancla que el detector agrupa por FUENTE (itstool↔gettext-tiny)
y NO cría lobos (itstool=carrier, dbus=variante deliberada).
LECCIÓN: el reflejo del radio/diff antes de tocar evitó dos borrados destructivos
guiados por un falso positivo de mi propia herramienta. Medir antes de creer, aun
a la propia yupana.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
619a30187c |
yupana: keystones — el camino crítico real, con la matemática correcta (AND, no OR)
`yupana keystones` rankea los nudos EN DEUDA por cuánto trabajo BLOQUEADO libera su sellado (cierre inverso transitivo restringido a la deuda). Sellá de arriba hacia abajo y la cascada cae lo antes posible. CORRIGE UN ERROR MÍO: dije "árbol de dominadores = keystones". Lo medí y es FALSO. El dominador ingenuo asume alcanzabilidad OR (basta un camino), pero construir es AND (hacen falta TODAS las deps): kcoreaddons depende de dbus/libdrm /mesa además de qtbase ⇒ hay un camino de desbloqueo que evita qtbase y el dominador-OR lo pierde. La métrica correcta bajo AND es el cierre inverso transitivo. Y el crudo lo gana el toolchain (make gatea 313, inútil) ⇒ el keystone accionable es el cierre restringido a la deuda, y sólo cuenta si el nudo mismo está en deuda (un sellado ya está disponible, no gatea nada). Estado actual (qtbase ya sellado, era EL keystone con 117 gateados): kio 36 ← sellarlo libera 36 recetas KDE bloqueadas kparts 19 kcmutils 17 ksvg 14 libplasma 13 Ése es el camino crítico ahora. El dominador SÍ entra, en su uso legítimo: la columna `exclusivo` = retención estilo GC (nodos que sólo dependen de mí). kwin=9 (sus plugins privados), libksysguard=3. Responde "si suelto esta feature, cuánto más puedo soltar". Sin datos nuevos (no necesita duración de build). Cuando instrumentemos tiempos, el mismo cierre se vuelve camino crítico PESADO (ETA real). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0f3a1dc394 |
yupana: invariante (c) CANÓNICO — detector de duplicados por evidencia de fuente
`yupana duplicados` caza nudos que son el MISMO paquete bajo nombres/colas
distintos, agrupando por lo que BAJA (sha/url), no por el nombre. Clasifica:
colisión-fuente — mismo sha, familias de nombre DISTINTAS ⇒ casi seguro un
sha copiado por error. ACCIONABLE.
variante — mismo sha, misma familia (zlib/zlib-shared, mesa-*) ⇒
diversificación deliberada estático/shared, OK (14 vistas).
sombra — mismo nombre en >1 cola; redundante si versión+sha+deps
coinciden (una sobra), justificada si difieren.
HALLAZGO INMEDIATO — el detector encontró un bug real en su primera corrida:
itstool.toml dice versión 2.0.7 pero su url+sha apuntan al tarball de
gettext-tiny (29cc165e…). Construiría la fuente equivocada. radio itstool=1,
gettext-tiny=78 ⇒ el sha copiado es el de itstool.
Otras 3 colisiones son demos (adwaita-hello/libadwaita, sourceview-hello/
gtksourceview, radio 0) o subcomponentes (prison/prison-scanner) — a revisar,
no urgentes. Y 1 sombra redundante: incoming-kde/dbus es byte-idéntica a
corpus/dbus (mismo hash de artefacto) ⇒ los 114 consumidores KDE podrían
apuntar a corpus/dbus sin rebuild.
NO funde nada solo: reporta a docs/state/duplicados.json y deja el juicio al
humano (como el triaje). El radio de cada nudo dice cuál es el canónico (el de
mayor radio). Guardián extendido: test-yupana-radio.py falla si el detector
deja de ver colisiones de fuente (agrupar por nombre en vez de sha = invariante
c ciego).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e4a0237b6d |
yupana: rename khipu→yupana — el registro es el khipu, la yupana es el ábaco que reckona sobre él
`khipu` colisionaba con una app de tawasuyu. En vez de un nombre a dedo, el significado más profundo RESUELVE la colisión: en los Andes el khipu GUARDABA (el registro de nudos) y la yupana CALCULABA sobre él (el ábaco). Acá igual, y la distinción es arquitectura: docs/state/build-state.json = el KHIPU (el registro firme: nudos y cuerdas) scripts/yupana.py = la YUPANA (el motor que reckona: radio, ondas, clausura) Nunca fue del todo un khipu lo que construimos; era la yupana. La colisión empujó al nombre más exacto. git mv preserva historia. Verificado tras el rename: `yupana radio libdrm` da 126/132/[kde,mirada]; el guardián de regresión pasa; build-state importa yupana y --check sigue en 0. Cero referencias `khipu` colgadas en código. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4d0a909bbd |
khipu: guardián de regresión del radio cruzado (la cicatriz de libdrm)
test-khipu-radio.py falla si el radio de un paquete compartido entre colas vuelve a medirse recortado: verifica que libdrm lo consuman >1 cola y que su membresía incluya escritorio-kde (la mentira original decía sólo mirada). Regla que blinda: cuando khipu se equivoca, no sólo se arregla — queda el test que impide repetirlo. Es la automejora arquitectónica hecha práctica. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8da6ef6122 |
khipu: el motor de dependencias inversas que cruza TODAS las colas (y arregla la mentira de perfiles)
El 2026-07-22 un cambio a libdrm (default_library=both, para gtk4) invalidó 118
recetas KDE. El radio se midió contra recipes/*.toml y no contra
recipes/incoming-kde/ ⇒ 8 en vez de 118. Y build-state.py sin --kde cometía el
MISMO error: cargaba sólo el corpus, y su campo `perfiles` AFIRMABA que tocar
libdrm sólo afectaba a escritorio-mirada. Una mentira con confianza.
Causa estructural: las dependencias INVERSAS son un hecho del disco entero, no
de la vista que uno cargó. khipu.py las calcula SIEMPRE sobre todas las colas,
con resolución sibling-first (el nodo se identifica por (cola, nombre), porque
corpus/fontconfig y incoming-kde/fontconfig son nudos distintos).
QUÉ NODOS PUNTÚO es decisión de vista. QUIÉN ME CONSUME es un hecho.
khipu es la PUERTA ÚNICA de la metodología: radio/perfiles nativos (cruzan todo
el repo) + delega estado/drenar/triaje/frontera/objetivo a sus órganos.
khipu radio libdrm → 126 directos, 132 transitivos, [escritorio-kde,
escritorio-mirada], 11 sellados que caen. El número
honesto que habría frenado el cambio.
build-state.py ahora saca `perfiles` y el nuevo `dependientes_total` de khipu
(grafo entero), no de los nodos que cargó. Verificado: el grafo por defecto ya
atribuye libdrm a escritorio-kde; sin_perfil bajó 674→666 (8 recetas del corpus
que sólo KDE consume, ahora bien atribuidas). --check sigue en 0, grafo cierra.
Repo-wide por diseño, no KDE-céntrico: zlib toca las 4 imágenes (233 transitivos),
make 313.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9b67268a65 |
triaje: los 170 candidatos de frontera clasificados, y la cadena se ejerce entera
Evidencia dura primero: NINGUNO de los 170 bloquea un build. Toda receta que
pide uno de estos ya está sellada al menos una vez ⇒ hammer construye sin
ninguno. Eso descarta empíricamente "hueco de build" para los 170 y deja sólo
la pregunta de runtime.
VEREDICTOS
provisto 11 — no faltan: ya los da otra receta con otro nombre. nixpkgs parte
en varios paquetes lo que acá es uno solo. Verificado contra el
store: wayland-scanner→wayland, mesa-libgbm→mesa (nuestros tres
mesa producen libgbm.so.1), libxcb-*→xcb-util-*, poppler-qt6→
poppler, gmp-with-cxx→gmp, uname→coreutils.
nix-ismo 5 — andamiaje de nixpkgs (env wrappers, helpers del stdenv, glibc
asomando por su libc).
opcional 150 — software real que nixpkgs habilita y hammer no necesita, con el
porqué agrupado: systemd (usamos arje-zero), X11 heredado (el
escritorio es Wayland), Vulkan/shaders, audio/multimedia,
conectores de BD, tooling de docs/tests, bindings Python de Qt,
paquetería ajena (tenemos .swm).
hueco 4 — gaps de RUNTIME, no de build: shared-mime-info (sin base MIME
no hay tipos de fichero), xwayland (ninguna app X11 corre),
polkit-qt-1 (sin diálogos de autorización), qqc2-breeze-style
(los controles QML caen a un estilo genérico).
CORRIGE UN ERROR MÍO DE P3: dije que mesa-libgbm/libglvnd eran "el muro de
GBM/EGL". Falso — libgbm ya lo produce nuestro mesa. El muro era softpipe vs
llvmpipe, no un paquete ausente.
CUARTO VEREDICTO NUEVO (`provisto`) con su propio lazo: sale a alias-triaje.txt
y seed-graph.py lo carga en MAPA ⇒ esos 11 nombres dejan de contarse como hueco
para siempre. Junto con nixismos-triaje.txt, el sembrador aprende de su triaje.
Y LA CADENA SE EJERCE ENTERA POR PRIMERA VEZ: los 4 huecos entraron como raíces
de escritorio-kde → nacieron 4 nodos `wanted` (raíces 11, sin receta 4; el grafo
sigue cerrando, --check exit 0) → seed-graph los sembró (42 aristas conocidas,
21 candidatos nuevos de frontera) → drenar.py los ordena marcándolos [semilla],
que es la regla de la fuente única a la vista. qqc2-breeze-style no cae en la
onda 1 porque sus deps SEMBRADAS la traban (kcodecs, kirigami…): el andamio
funcionando como se diseñó.
De paso: qtbase se selló mientras corría esto ⇒ la onda 1 de KDE se abrió de 1 a
13 recetas. El cuello de botella que reportó P4 ya está destrabado.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ec28a9b394 |
catálogo objetivo P5: tandas a archivo, y el triaje que cierra el lazo
tandas/README.md declara los 55 ficheros ARCHIVO de campañas pasadas (no se
borran: su git log es la crónica de cada frente) y documenta de dónde sale el
trabajo ahora. import-batch.sh -f sigue vivo como vía de escape, no como camino.
Lo que faltaba para que la cola fuera derivada de verdad era el paso humano, y
estaba sin forma: 137 candidatos clasificables sólo en la cabeza de alguien.
triaje.py les da forma durable — docs/state/frontera-triaje.toml, 170 candidatos
unificados de los 3 perfiles. Cada veredicto cierra un lazo distinto:
hueco → raíz en targets.toml ⇒ nace un nodo `wanted`, el sembrador lo
siembra y el drenaje lo ordena (= trabajo nuevo para el worker)
opcional → registrado, deja de reaparecer como pregunta
nix-ismo → vuelve a seed-graph.py como descarte ⇒ el sembrador APRENDE de su
propio triaje y cada corrida sale más limpia
Sincronizar NUNCA pisa un juicio humano: agrega los nuevos y marca ausente=true
los que dejaron de aparecer (progreso, o cambio de nixpkgs — se ve en el diff).
`sugerencia` propone con evidencia mecánica pero `veredicto` arranca en
`pendiente`: la heurística abarata el juicio, no lo cierra.
Lazo verificado punta a punta: python3-3.14.6-env marcado nix-ismo → --aplicar
lo escribe al descarte → seed-graph.normalizar() lo devuelve clasificado y deja
de contarlo como hueco. Probado y revertido; los 170 siguen pendientes.
Cadena cerrada y corriendo: targets.toml → build-state.json → drenaje.json →
worker, regenerada por el latido cada 30min. El único paso que pide una persona
es el triaje.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
25e3878331 |
farm: JOBS=1 en el worker-loop mientras el ADR 0012 esté sin decidir
`build-farm.sh` usa `xargs -P$JOBS`; con >1, dos recetas que comparten dep disparan la carrera del árbol de fuentes del ADR 0012 y el árbol queda roto para siempre. Se vio a escala: al invalidar libdrm, 118 de las 205 recetas KDE pasaron de cache-hit a rebuild real y ~93 murieron con "/src/.zwrap/cc is not a full path to an existing compiler tool" — el wrapper de zig que la receta deja en el ÁRBOL DE FUENTE, barrido por el fetch concurrente de otra receta sobre el mismo qtbase. El reintento serial de build-farm tampoco las recupera: el árbol ya quedó inconsistente. La caché estaba ocultando la carrera, no evitándola. Serializar es la única mitigación correcta hasta que el ADR 0012 elija salida. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b08e582c89 |
catálogo objetivo P4: la cola de la granja pasa a ser derivada del grafo
drenar.py parte la deuda en ONDAS topológicas (una receta sólo espera por deps que también estén en deuda; las selladas ya están en el store). Onda 1 = construible ya; dentro de cada onda, orden por `unblocks` desc. Da lo que una lista plana no da: el orden correcto, la PROFUNDIDAD real de la cadena (pasos secuenciales = el reloj de pared) y qué paraleliza sin pisarse. base 0 en deuda imagen completa cli 0 en deuda imagen completa escritorio-kde 77 en deuda 12 ondas onda 1 = 1 receta: qtbase escritorio-mirada 4 en deuda 2 ondas onda 1 = 3 HALLAZGO: el escritorio KDE está serializado detrás de UNA receta. qtbase destraba 117 nodos y es lo único de la onda 1 — hasta que no esté sellada no hay nada que paralelizar, por más workers que se enciendan. Cambia la pregunta de "¿cuántas faltan?" (77, poco informativo) a "¿cuál es el camino crítico?". Dos correcciones salidas de mirar la salida: · el grafo se elige por la `cola` declarada en targets.toml, NO por cuál tiene más nodos: incoming-kde SOMBREA recetas canónicas, así que medir escritorio-mirada contra el grafo KDE medía una imagen que nadie construye (3 en deuda en vez de 4). El mismo atajo estaba en seed-graph.py --frontera; corregido en los dos. · regla de la fuente única implementada en deps_de(): manda la receta si existe, la semilla sólo si el nodo es `wanted`, marcando procedencia en la salida. Enchufado al latido: cosecha-cron regenera docs/state/drenaje.json junto al grafo y lo commitea ⇒ cola derivada y versionada, el git diff entre ciclos muestra qué salió de la deuda. NO lanza builds: eso cuesta € y sigue siendo decisión explícita (farm-up + campana-deuda.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0093c1f19c |
catálogo objetivo P3: sembrador de aristas desde nixpkgs, calibrado contra la verdad
seed-graph.py extrae deps de la metadata de nixpkgs sin construir nada (leerla
no viola el ADR 0004, que prohíbe *construir* con nix). `--calibrar` mide la
semilla contra la única verdad disponible: las recetas escritas a mano.
VEREDICTO (60 recetas, 686 deps):
directas recall 39%*, precisión 59% (269 de 455)
transitivas recall 84%, precisión 6% (390 de 6760)
* contra la verdad APLANADA — comparación injusta, y el modo transitivo
prueba que la información sí está; sólo falta aplanarla.
Decisión: sembrar aristas DIRECTAS, la transitividad la hace el grafo. hammer
aplana la clausura en [deps].build (cada dep = capa --overlay-src), nixpkgs
declara directas y propaga; reproducir el aplanado arrastra 4629 nodos fantasma
del bootstrap de nixpkgs.
Lo que enseñó la calibración (3 rondas, cada fix salido de un dato):
· 4 clases de discrepancia, no 1: convención hammer (make/binutils/linux-headers,
implícitas en el stdenv de nix), variante nuestra (zlib vs zlib-shared),
provisto por el lab (cargo/rustc — las recetas Rust declaran deps=[]), y recién
después ruido real. Contarlas juntas hacía parecer irreducible lo clasificable.
· desalineación aburrida y arreglable: caja (libx11/libX11), implementación
(gettext→gettext-tiny, ninja→samurai), versión pegada al pname, hooks del
stdenv. Precisión 45%→59%; "sin attr en nixpkgs" 22/40 → 6/60.
· el escritorio KDE cuelga de kdePackages., no del top-level: sin ese fallback
el perfil objetivo era insembrable.
Hallazgo de secuencia: hay 0 nodos `wanted` (targets.toml se pobló por
lift-and-shift de lo existente), así que el sembrador no tenía a quién sembrar.
De ahí `--frontera <perfil>`: siembra las recetas que YA existen y reporta lo que
nixpkgs pide y hammer no puede construir. 137 candidatos en escritorio-kde
(kdoctools ×6, milou, polkit-qt-1, libkscreen, y mesa-libgbm/libglvnd/spirv-tools
= el muro de GBM/EGL ya documentado), 51 en cli, 46 en escritorio-mirada.
Cada candidato es una HIPÓTESIS a clasificar a mano: dep opcional no habilitada,
nix-ismo por filtrar, o hueco real. Nada se promueve a receta automáticamente.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a27d9a94a4 |
catálogo objetivo P2: estado wanted + membresía de perfil en el grafo
El grafo cruza ahora el corpus con el manifiesto de objetivo:
· estado `wanted` — una raíz declarada sin receta se materializa como nodo de
frontera. Se crea ANTES de buscar huérfanas a propósito: un objetivo declarado
no es una arista colgando, así `--check` conserva su significado (verificado:
exit 0, grafo CIERRA, topo OK). `wanted` ≠ `unhashable`.
· campo `perfiles[]` por nodo — qué imágenes lo alcanzan desde sus raíces.
· bloque `by_profile` — el "cuánto falta", POR IMAGEN.
Primeros números, derivados de raíces y no de listas a mano:
base 51/51 listo
cli 74/74 listo
escritorio-mirada 27/31 (falta libinput, mesa-swrast, mirada-{compositor,greeter})
escritorio-kde 85/162 faltan 77 ← de 7 raíces, antes ilegible
674 recetas no las alcanza NINGUNA imagen (catálogo, no distro)
La clausura es COTA INFERIOR mientras haya nodos `wanted`: sus deps no se
conocen hasta construirlos (o sembrarlas, P3). Está documentado en el header.
`totals.recipes` sigue contando sólo recetas con fichero (los consumidores lo
leen como tamaño del corpus); `totals.nodes` incluye la frontera. La vista HTML
regenera sin cambios de contrato.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c5ae42b92e |
espejo: push a gitea + GitHub privado, y script para reponerlo tras un clon
git.tawasuyu.net resuelve a gioser, marcado como FIJO y SIN BACKUP. Sin espejo, las dos únicas
copias del repo eran el laptop —que ya se corrompió una vez por un corte sucio, el 2026-07-22— y
esa. Ahora hay una tercera, en otro proveedor y PRIVADA.
Se implementa con `git remote set-url --add --push origin` en vez de un remoto `github` aparte,
para que TODO `git push origin main` que ya existe en los scripts (cosecha-cron.sh, el latido)
espeje solo, sin tocar un script. Si GitHub falla, el push devuelve != 0 aunque gitea haya
aceptado; cosecha-cron.sh ya lo tolera ("push falló, reintenta próximo ciclo") ⇒ el latido no se
rompe por eso.
La credencial va por HTTPS con el token de `gh`, no por SSH: las tres claves SSH del laptop
(github5, key25, sergiogithub) son DEPLOY KEYS de repos ajenos y dan "Repository not found" contra
este. El bloque `Host github.com` del ~/.ssh/config además no tiene `IdentitiesOnly`, así que ssh
ofrece todas y gana una deploy key.
El script existe porque las dos cosas que configura viven en `.git/config`, que NO se versiona: se
perderían exactamente en el escenario para el que se pusieron (el laptop muere, clonás de nuevo).
Incluye también el `core.fsync` del mismo incidente. Idempotente (los pushurl son acumulativos, así
que los reconstruye en vez de añadir) y con `--check` para auditar sin tocar nada.
Verificado: los dos remotos en el mismo commit, GitHub reporta PRIVATE, y correrlo tres veces deja
2 pushurl, no 6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3a64b603d7 |
catálogo objetivo P1: manifiesto de perfiles — el set de la distro deja de ser un string de shell
El destino de la distro vivía en 2 strings de product-userland-from-repo.sh, 1 de mirada-usb.sh y 55 tandas planas. Ahora en docs/state/targets.toml: un perfil = una imagen enviable, listando sólo las RAÍCES (lo que se pide por nombre); la clausura la calcula el grafo, no un humano. 4 perfiles: base (26), cli (46, hereda base), escritorio-mirada (14), escritorio-kde (7 raíces, cola incoming-kde). scripts/targets.py expande hereda (transitivo, con detección de ciclo) preservando el orden — mirada lo necesita. Lift-and-shift verificado: las 3 listas expandidas son byte-idénticas a los strings que reemplazan. Ninguna imagen cambia de contenido. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
142c417d36 |
farm: que el loop DIGA que está esperando el lock, en vez de parecer que trabaja
El `echo "ciclo: N recetas en $Q"` sale ANTES de pedir el lock, así que con la campaña corriendo el journal mostraba el anuncio del ciclo y después nada: parece un loop trabajando y es un loop bloqueado. Es la misma clase de problema que el "no encuentro el ejecutable zig" apuntando al directorio equivocado — un log que miente cuesta horas de diagnóstico. `flock -n` primero, y sólo si falla se anuncia la espera y se bloquea. Sin coste cuando el lock está libre, que es el caso normal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1b436a717f |
farm: lock compartido entre campana-deuda y el worker-loop
Los dos construyen sobre el MISMO work/, y `fetch` nombra el árbol de fuentes de forma determinista
(`work/sources/<receta>-<sha16>`, sin nada que dependa de QUIÉN construye) ⇒ dos procesos que
necesiten la misma DEP apuntan al mismo directorio: uno hace `remove_dir_all` mientras el otro
corre `tar -x`. El árbol queda a medias y devuelve "Directory not empty" (os error 39), y así se
queda hasta que alguien lo borra a mano.
No es teórico: la campaña de deuda y el loop pidieron `mesa` a la vez (el loop lo arrastraba desde
la cola KDE al invalidarse libdrm) y se llevó puesta media cascada GUI, con un error que no nombra
la causa. Casi borro ese árbol a mano antes de ver que había un bwrap montándolo.
Grano: la campaña toma el lock para toda su corrida; el loop, por ciclo de cola. Más fino rompería
el `xargs -P2` de build-farm.sh.
Dos honestidades en los comentarios, para no prometer de más:
- `flock` NO es FIFO. El loop vuelve a pedirlo enseguida y le gana a la campaña que espera:
medido, la campaña entra al terminar TODAS las colas, no entre dos. La ventana real es el
IDLE_SLEEP. Por eso espera con techo (LOCK_WAIT=7200) y sale limpia en vez de colgarse.
- Esto NO cierra la carrera del todo: el `-P2` interno del loop puede correr dos recetas de la
misma cola que compartan dep, y ésas se siguen pisando. El arreglo de fondo es un lock POR
ÁRBOL dentro de `fetch`, que cubriría los dos casos.
Verificado con flock real: exclusión mutua (la campaña entra justo cuando el loop suelta), salida
limpia por timeout con rc=0, y la no-equidad de flock medida en vez de supuesta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|