96855001c6e9276014d68fc9f9f85ec765673e28
414
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
96855001c6 |
licencias: 96% (1124/1164) — --fetch para los tarballs que no estaban en caché
Ocho paquetes más (giflib, pcre2, nghttp2, libogg, libvorbis, speexdsp, libuv, tea) con la misma
regla de evidencia. Su tarball no estaba en `work/tarballs/`, así que el modo `--fetch` lo baja a un
temporal, lo verifica contra el sha256 QUE LA RECETA PINEA —o es el árbol exacto o no se mira— y lo
borra. No se escribe en la caché de hammer a propósito: es suya, y un fichero puesto ahí por otro
camino es una vía de envenenamiento que nadie audita.
Dos correcciones al criterio, las dos porque produjo una afirmación falsa:
· `gmp` salía GPL-3.0-or-later. Su `COPYING` es la GPLv3, pero al lado trae `COPYING.LESSERv3` y
`COPYINGv2` porque la biblioteca es LGPL. La regla anterior —«el `COPYING` a secas gana al
sufijado»— arregla socat, cuyo `COPYING.OpenSSL` es una excepción, y rompe gmp, donde el extra
nombra otra VERSIÓN y no una dep. Esa diferencia es demasiado fina para codificarla sin
equivocarse, así que ahora se identifican TODOS los ficheros de la raíz y sólo hay veredicto si
dicen lo mismo. gmp, ffmpeg y los tres `gi-*` quedan pendientes, que es lo correcto: en ffmpeg
la respuesta ni siquiera está en el árbol, la deciden los flags de la receta.
· Pero «varios ficheros» no es «ambigüedad»: el `COPYING` de pcre2 son dos líneas apuntando a
`LICENCE.md`, y los dos dicen BSD-3. Por eso se unen las identificaciones en vez de contar
ficheros — ambiguo es que digan cosas DISTINTAS.
Y el TSV pasa a ser ACUMULATIVO. Una receta ya sembrada sale del conjunto de entrada porque ya tiene
`license`, así que regenerar el fichero entero borraba su cita — y la cita es el rastro de
auditoría, lo único que deja revisar un veredicto sin repetir el trabajo. Un fichero de evidencia
que se olvida de lo que ya probó no es evidencia.
Los 10 ArtifactHash afectados, idénticos antes y después.
|
||
|
|
ba1fb2d3a8 |
licencias: 95% (1114/1164) — la evidencia sale del tarball pineado, offline
`licencias.sh --sembrar` escribe desde una tabla curada a mano y `licencias-desambiguar.sh` resuelve
`-only` vs `-or-later` preguntándole a la búsqueda de código de GitHub. Los dos dejan fuera lo mismo:
lo que nadie curó y lo que no vive en GitHub.
Pero `work/tarballs/` indexa cada tarball por su sha256, así que el árbol EXACTO que la receta pinea
ya está en disco: la declaración del propio autor, en el commit que construimos, sin red. De las 76
sin licencia, 55 tenían su tarball cacheado y 25 salieron con evidencia citable.
`scripts/licencias-tarball.py` la busca en tres niveles: la declaración del autor (`Cargo.toml`,
`meson.build`), un único fichero en `LICENSES/` (REUSE, que usa KDE), y el texto del COPYING más la
CONCESIÓN buscada en las cabeceras de los fuentes — excluyendo COPYING/LICENSE, porque el apéndice
de la GPL trae literalmente «or (at your option) any later version» y buscarla ahí da siempre
positivo siendo plantilla. Es la regla que ya fijó `licencias-desambiguar.sh`, aplicada al árbol
pineado en vez de a GitHub.
La primera versión resolvía 40, y CUATRO estaban mal. Las dejo escritas porque son la forma del
error, no accidentes:
· `socat` salía BSD-2 por su `COPYING.OpenSSL`, que es la excepción, no la licencia. Un `COPYING`
a secas gana ahora a cualquier sufijado.
· `pigz` salía Apache-2.0 por `zopfli/COPYING`: la licencia de una pieza VENDORIZADA leída como la
del contenedor. Sólo se mira la raíz.
· `openssh` salía MIT porque su `LICENCE` es un compendio de cuatro y me quedaba con la primera
que pegara. Si el texto trae varias, no hay veredicto: lo compone un humano.
· `nano` salía GPL-3.0-**or-later** citando el «either version 2» de su `aclocal.m4` — plantilla de
autotools, no del proyecto, y encima de otra versión. Ahora la cita tiene que venir de un fichero
del autor Y hablar de la misma versión mayor que el COPYING.
Y dos bugs míos que producían el mismo daño en silencio: la marca de BSD-3 era «Neither the name of»,
que libpcap y libzip no usan («The names of the authors may not be used to endorse») ⇒ se declaraban
BSD-2; y el mayor de versión lo sacaba de `"GPL-3.0".rsplit("-",1)[0][-1]`, que da «L», así que la
comprobación de coherencia rechazaba TODA cita válida.
La validación final no es una regex: un identificador vale si tenemos su texto en `licenses/`. La
obligación legal es acompañar el binario del TEXTO, así que un SPDX que no podemos entregar no
adelanta nada y sí crea una afirmación que no se sostiene. Eso es lo que atrapa el «GPL2+» que meson
deja escribir en `gsd-schemas` y `libgdm`, que no es un SPDX sino taquigrafía.
Nada se sembró a ojo: cada línea de `docs/licencias-evidencia.tsv` lleva la cita que la decidió, y
las 50 que quedan salen listadas con lo que SÍ se encontró en vez de rellenarse.
Comprobado que no se invalida nada: los 26 ArtifactHash afectados son idénticos antes y después
(`license` no está en `hash_inputs`) — medido, no supuesto.
|
||
|
|
5d06a3b83d |
atuq ABRE: el app_id estaba compilado en el binario, y el árbol copiado llegaba de sólo lectura
Con la cadena GTK3 arreglada, atuq arrancó de verdad: parent vivo, CERO procesos de contenido muertos, y pintando una página local. Quedaban dos cosas. 1. SE ANUNCIABA COMO `firefox-default`. El application.ini decía RemotingName=atuq y aun así el proceso se presentaba así — porque esa cadena es un MOZ_APP_REMOTINGNAME horneado en el ELF, y es la que Gecko pasa a g_set_prgname(), o sea el **app_id de Wayland**. Consecuencia visible: el escritorio no casaba la ventana con nuestro .desktop (StartupWMClass=atuq) ⇒ icono genérico y sin agrupar. Se parchea EN SITIO, no recompilando: cambiarlo en recipes/firefox.toml brandearía la BASE como atuq, y firefox tiene que seguir siendo firefox para los otros forks. Es seguro porque es una cadena C terminada en NUL en el pool de .rodata —`firefox-default\0` son 16 bytes exactos y se escriben 16—, y se EXIGE una única ocurrencia: si upstream la duplica o la renombra, falla en vez de parchear el sitio equivocado. Verificado después: 0 ocurrencias de la vieja, y el log dice `(atuq:26423)`. 2. UN BUG QUE SÓLO SE VE FUERA DE ROOT. `cp -a` preserva los modos y los ficheros del store están sellados sin permiso de escritura; todo lo que viene después los modifica. En el worker pasaba inadvertido porque corre como root, que ignora los bits. El primer build local murió con `PermissionError: /out/usr/lib/atuq/application.ini`. Se arregla con `chmod -R u+w` en la receta y no en el entorno: un artefacto no debe depender de con qué uid lo construiste. Y el runner pasa a hidratar las variantes `-shared`: en runtime una `.a` no sirve de nada, y son las que gtk3 declara NEEDED desde el arreglo del cuadro de las dos Pango. Este atuq se construyó EN LOCAL en segundos, que era la promesa entera del diseño derivado del SDD 26: iterar el envoltorio sin volver a pagar un build de Gecko. |
||
|
|
a828e74c40 |
la cadena GTK3 se enlazaba estática dentro de dos .so: cuatro variantes -shared y xkeyboard-config al corpus
Con `atk` arreglado, atuq llegó más lejos y murió igual, ahora con Pango:
GLib-GObject-CRITICAL: cannot register existing type 'PangoFontMap'
… decenas de líneas … tipo '<invalid>'
MISMO CUADRO, CULPABLE DISTINTO Y MÁS GRANDE. `gtk3` produce DOS objetos compartidos —libgtk-3.so y
libgdk-3.so— y libxul declara NEEDED las dos, así que se cargan siempre juntas. Con las variantes
ESTÁTICAS de sus deps, cada una se llevaba adentro su propia copia. Medido con el mismo instrumento
que cazó a atk, `nm -D --defined-only`, preguntando quién DEFINE cada símbolo:
pango_font_map_get_type → libgdk-3.so, libgtk-3.so, libgailutil-3.so
cairo_create → libgdk-3.so, libgtk-3.so
gdk_pixbuf_get_type → libgdk-3.so, libgtk-3.so
hb_buffer_create → libgdk-3.so, libgtk-3.so, libgailutil-3.so
Y se comprobó el otro lado: libxul NO embebe ninguna —enlaza libgtk-3/libgdk-3 como debe—, así que
la duplicación es toda interna del par GTK3.
Cuatro variantes nuevas: harfbuzz-shared, cairo-shared, gdk-pixbuf-shared, pango-shared. Sólo cambia
el modo de librería; se conservan todos los switches del canónico para no arrastrar deps nuevas.
DOS DEUDAS ANOTADAS, NO OLVIDADAS: `fribidi` y `pixman` siguen estáticos —no existe variante— pero
quedan embebidos en UNA sola .so cada uno, así que no hay copia que colisione; y `libepoxy` sí queda
en las dos, y se deja porque no registra tipos de GObject ni mantiene estado global. Si algún día
otra .so del mismo proceso los embebe, vuelve el cuadro.
`firefox` swapea las mismas cuatro: declarar las ESTÁTICAS junto a un gtk3 que trae las compartidas
son dos artefactos peleando por el mismo `pango.pc`, que es la otra forma conocida de este fallo.
Y `xkeyboard-config` SUBE AL CORPUS. Existía idéntica en las cuatro colas de escritorio (un solo md5
entre las cuatro, verificado antes de mover) y atuq, que vive en el corpus, no podía alcanzar
ninguna: sibling-first y después el catálogo padre, nunca una cola hermana. Sin sus datos el
navegador ni pinta («xkbcommon: failed to add default include path /usr/share/X11/xkb»). Mismo hash
en el corpus que en las colas ⇒ cero rebuilds, y las copias de las colas se quedan donde están.
|
||
|
|
d2527284b9 |
atuq-nested: el rootfs se revalida por HASH, no por existencia
El script daba por bueno el rootfs si el directorio estaba: «¿ya está hidratado?». Es la pregunta equivocada. Tras el re-hash de la cadena atk→gtk3→firefox→atuq el directorio seguía ahí con los artefactos VIEJOS, así que habría abierto contento la versión que acabábamos de arreglar y el diagnóstico habría sido buscar en el sitio equivocado. Misma forma del cache-hit que congela regresiones. Ahora resuelve los hashes vigentes, los anota en `.raices` y rehidrata cuando difieren. De paso: nada de `diff <(...)`, que es de bash. El shebang dice /bin/sh y un script que sólo anda cuando /bin/sh resulta ser bash es una trampa que salta en otra máquina. |
||
|
|
0f7d19232e |
atuq: el lanzador deja de ser un symlink, y un runner para abrirlo en la pantalla que ya tenés
Dos cosas que salieron de intentar ABRIRLO, que es lo único que distingue «sellado» de «usable».
1. EL LANZADOR. Con `/usr/bin/atuq` como symlink, el navegador moría antes de pintar:
XPCOMGlueLoad error for file /usr/lib/atuq/libmozsandbox.so:
Error loading shared library libnspr4.so: No such file or directory
El motor carga sus propias librerías desde `/usr/lib/atuq` y ni el binario ni esas `.so` traen
RPATH/RUNPATH — comprobado con `readelf -d`, no supuesto. Pasa a ser un script que exporta
`LD_LIBRARY_PATH` (lo mismo que hacen Debian y Fedora) y hace `exec`, para que el proceso que
queda sea el motor y `/proc/self/exe` siga resolviendo el appdir. El `LD_LIBRARY_PATH` además
tiene que HEREDARSE: Firefox lanza un proceso por pestaña y todos cargan las mismas librerías.
La alternativa limpia es grabar RPATH=$ORIGIN con `patchelf` —es lo que hace Alpine— pero
`patchelf` todavía no existe como receta del corpus; cuando exista, esto vuelve a ser un symlink.
2. `scripts/atuq-nested.sh` — hidrata el cierre de runtime y abre atuq como ventana anidada en el
compositor que ya está delante (waypipe, mirada, sway). Trae dos cosas aprendidas a golpes:
· EL ROOTFS VA EN EL MISMO MOUNT QUE EL STORE. `hydrate` proyecta con hardlinks y `linkat()`
rechaza cruzar un punto de montaje aunque sea el mismo filesystem. Acá el store es /dev/sdb
bind-monteado y `work/` vive en /dev/sdc: hidratar a `work/…` muere con «Invalid cross-device
link (os error 18)». El volumen entero está en /mnt/cosecha, así que el rootfs va ahí. Se
comprueba con `findmnt -T`, nunca con `stat -c %d`.
· `dejavu-fonts` va en las raíces por necesidad, no por completismo: un navegador sin una sola
fuente arranca, pinta y muestra cuadraditos. Ya nos costó una tarde en GNOME.
Y no saltea en silencio: si falta un artefacto de la lista, sale con error en vez de armar un
rootfs al que le faltan tres paquetes y falla tres capas más abajo.
|
||
|
|
442a319499 |
lab re-pineado con lld (d1e341d5) — y el sha «determinista» lo ensuciaba un log
Al ir a re-pinear la imagen para propagar el `lld` a la granja, hub y worker empaquetaron a shas DISTINTOS. La imagen se creó para que el sha VERIFIQUE, así que un desacuerdo ahí no se pinea: se mide. Los dos rootfs son idénticos: 9550 entradas con la misma lista de ficheros, los mismos tamaños, y CERO diferencias de metadato (modo, nlink, destino de symlink) en las 8778 entradas de fichero y symlink. La única diferencia de contenido en todo el rootfs era `var/log/apk.log`, donde apk anota la FECHA de cada operación — o sea que dos labs equivalentes empaquetaban distinto sólo porque las instalaciones ocurrieron en momentos distintos. Un sha que no se puede reproducir desde un rootfs equivalente no verifica nada, que es justo lo que este fichero existe para dar. Se excluye ese log del tar (no todo `var/log`: cambio mínimo; apk lo recrea solo). Con la exclusión, hub y worker dan el MISMO tar: 179d04f054f28d8dafe7c626d6b8c686a33a00a96715ee544a9bcf6f62cf586d. ⇒ LA GRANJA YA ESTABA SINCRONIZADA Y AHORA SE PUEDE DEMOSTRAR, sin correr `--traer` en un worker que está construyendo firefox — reemplazarle el rootfs a mitad de build habría sido la forma cara de descubrir lo mismo. Nueva imagen publicada: hammer/lab/lab-d1e341d5….tar.zst (310 M), pin actualizado en bootstrap-devfs.sh. Queda en el comentario cómo comparar dos máquinas: se compara el TAR y no el `.tar.zst`, porque el sha comprimido depende de la versión de zstd de cada máquina y dos labs idénticos con zstd distintos darían un falso desacuerdo. |
||
|
|
cb3ecd5a00 |
firefox pasa a clang+lld con LTO: la puerta de las optimizaciones costaba apk add lld, no una receta de LLVM
El SDD 26 estimó que habilitar PGO/LTO exigía una receta `llvm-toolchain` (clang+lld+libc++ desde fuente). ERA CARO DE MÁS. Al mirar el lab en vez de suponerlo: - `.dev-fs/alpine` YA TRAE clang22 + llvm22 22.1.8 — la MISMA major que usa el APKBUILD de Alpine para este mismo Firefox (`_llvmver=22`). - `Compiler::Clang` YA EXISTE en hammer, cableado de punta a punta: `parse_compiler` lo acepta y `hammer-build/src/lib.rs` pone CC=clang, CXX=clang++ y AR=llvm-ar. NINGUNA receta lo usaba. - Lo único que faltaba era `ld.lld`. `apk add lld` ⇒ lld22 22.1.8, dos paquetes, cero upgrades. CON ESO CAEN LOS TRES MUROS QUE OBLIGABAN A gcc, sin perder lo que gcc daba: el sondeo de linker se satisface con `--enable-linker=lld`, el `ar` lo pone hammer solo, y el `NEEDED` de la stdlib de C++ existe porque clang++ de Alpine usa la libstdc++ COMPARTIDA — la prueba no es teórica, Alpine construye este Firefox con clang22 y sin libcxx en sus makedepends. LA HUELLA DEL LAB NO SE MOVIÓ, Y SE MIDIÓ ANTES DE TOCAR NADA. `lld` no casa ningún prefijo de TOOLCHAIN_PREFIXES (hammer-core/src/lab.rs), así que los 43 paquetes que entran en `hash_inputs` salieron idénticos ⇒ los 837 artefactos sellados quedan intactos. Eso es lo que hace barato el cambio HOY, y a la vez es un agujero escrito en los dos sitios: la versión de lld no es parte de la identidad del artefacto, y sólo expone a las recetas `compiler="clang"`, que hoy es una. Meter "lld" en la lista de prefijos es lo correcto y cuesta re-hashear el corpus entero: próxima campaña. En el mozconfig entran, además de lld: `--enable-lto=cross`, `--enable-packed-relative-relocs` y `--with-unsigned-addon-scopes=app,system`. El último no es cosmético: sin él un Firefox de release rechaza las extensiones que la distro deja en distribution/extensions/, así que la capacidad de atuq de shipear su propio `sct` se decide ACÁ, en la base, y no en el overlay del derivado. PGO no entra en esta pasada y el porqué queda escrito en la receta: el perfil se junta corriendo el navegador (Alpine y Arch usan xvfb-run; nosotros no tenemos X11 ⇒ sway headless) y el profdata NO es determinista, así que tiene que sellarse como artefacto propio y consumirse por hash. ThinLTO se capa con la misma cuenta que -j y por la misma razón que ella no es un literal: un número fijo ataría el ArtifactHash a la RAM de quien escribió la receta. Nuevo ArtifactHash: b3:6f2a3b2f6db4452ed0d2d3f4e2ff7cd6562a86878d4360653859720be1c3d94d (el firefox 154.0 sellado con gcc queda SUPERADO, no perdido). |
||
|
|
aa200a4e7e |
vigía de parches: probar que agarran ANTES de las cuatro horas de build
`hammer build` aplica los parches DESPUÉS de materializar las dependencias, así que en una receta hoja de la plataforma Gecko el `patch` que no agarra se descubre detrás de horas de compilar OTRA COSA: waterfox estrena su primer build reconstruyendo nodejs entero (4414 objetivos de V8, a -j2 porque la propia receta capa por RAM). La receta de waterfox dice, con todas las letras, que si patch falla «falla TEMPRANO, antes de compilar nada». Con el orden real de hammer eso no era cierto. Este vigía es lo que lo vuelve cierto: saca de los propios parches los ficheros que tocan (21 en waterfox), los trae por sparse-checkout blob:none, y aplica los once acumulativos y en orden como hace fetch.rs. Un minuto en vez de cuatro horas. LO QUE MIDE NO ES SÍ/NO, SON TRES ESTADOS. `patch` también responde «sí, adivinando»: cuando el contexto no casa aplica igual con FUZZ, y con el `--silent` de fetch.rs eso sale por exit 0 sin que nadie se entere. Un parche con fuzz puede haber editado el sitio correcto o cualquier otro, y el exit code no distingue. Por eso `ok` / `FUZZ` / `FALLA`, y el del medio existe para no perderse. El desplazamiento no se marca: sólo dice que el fichero creció por arriba. VEREDICTO SOBRE LA APUESTA DE WATERFOX (parches de 154 sobre base 153.1.0): SE SOSTIENE. Los once entran; los dos que entran con fuzz —time64 y fix-rust-target— se verificaron a mano y los dos editan el sitio correcto. El contexto que no casa es cosmético: comillas simples vs dobles de un reformateo con black, y un brazo `cfg` de más. Y UN HALLAZGO DE PASO: `firefox-patches/time64.patch` tiene la cabecera MENTIROSA. Su diffstat anuncia tres ficheros y el cuerpo entrega dos — falta el hunk de `wgpu-hal/src/vulkan/adapter.rs`. Como firefox 154 sella igual, nadie lo había notado. Por eso los ficheros se leen del cuerpo y nunca del diffstat. El vigía nació mintiendo en la dirección cara y por eso se probó contra recetas selladas antes de commitear: suponía el prefijo `a/`+`b/` de git y marcaba FALLA sobre gawk, que sella perfecto — su parche es un `diff -upr` a secas con caminos `gawk-5.1.0.orig/`. Se descarta el primer componente se llame como se llame, que es lo que significa el -p1 con el que se aplican. Flags en inglés (--all, --git-only, --keep, --strict) por la regla 4 del repo. |
||
|
|
9834f57052 |
yupana: --help imprimía literalmente None
Todo el encabezado del fichero era comentario `#`, así que __doc__ era None y `main()` hace `print(__doc__)` en dos caminos: `--help` y verbo desconocido. La puerta única de la metodología no sabía explicarse, y el error de verbo tampoco decía cuáles son los verbos válidos. La ayuda operativa pasa a docstring; el diseño y el porqué se quedan arriba como comentario. Cotejada contra el despacho real: los 11 verbos de la ayuda existen y los 11 que existen están en la ayuda (faltaba `sembrar`). |
||
|
|
28d98dc5f9 |
drenaje: medía 5 de 7 imágenes y las otras 2 desaparecían en silencio
targets.toml declara 7 perfiles; drenaje.json listaba 5. escritorio-cosmic (cola incoming-cosmic) y escritorio-sway (cola incoming-wlr) declaraban una cola que ningún grafo de GRAFOS contenía, así que cargar() devolvía None y --todos hacía `continue` pelado. Dos imágenes enteras fuera del artefacto sin dejar rastro, y los dos números —5 medidos, 7 declarados— no se cruzaban en ningún lado. Mismo olvido que ya costó 17 días de build-state-wlr.json congelado: la lista de grafos crece a mano y se queda atrás cuando se abre una cola. Tres cambios: - GRAFOS suma los grafos de cosmic y wlr. - Un perfil sin medir se anota en el artefacto (`sin_medir`), se grita por stdout y drenar.py sale 1. Regla 3 del repo: un ausente falla ruidosamente. - cosecha-cron deja de tragarse la salida; filtra las líneas ⚠ al log, que si no llegaban como un "falló" mudo que no dice cuál imagen falta. Medido: las 2 imágenes que nadie miraba estaban limpias, 0 en deuda. Ahora son 7/7 verificadas en vez de 5 verificadas y 2 supuestas. |
||
|
|
97023c3306 |
granja: PODA_FUENTES nace encendida y respeta el árbol de la receta que falló
Causa raíz de la deuda KDE de hoy: la cascada del 2026-09-03 corrió campana-deuda SIN PODA_FUENTES=1 (cero menciones en su log). Los 71 árboles de fuentes se acumularon en /dev/sdc a ~300 MB cada uno y lo llenaron en la receta 52; las 8 últimas murieron por disco, no por receta. Un default que hay que acordarse de encender no es una defensa. No poda tras un ✗: el árbol de la receta fallida es el post-mortem, y es justo el caso en que alguien va a mirarlo. Mismo criterio que el suelo de 24 h de poda-fuentes.sh, aplicado por resultado en vez de por edad. Probado: sobrevive tras ✗, poda tras ✓, y PODA_FUENTES=0 sigue apagándola. |
||
|
|
3f26d37d17 |
granja: guardián de disco en campana-deuda — corta en vez de anotar ✗ falsos
La cascada KDE del 2026-09-03 llenó el disco en la receta 52 de 71 y siguió moliendo: las 8 últimas murieron en 1-20 s con «No space left on device» y el bucle las anotó ✗, el mismo símbolo que una receta que no compila. El grafo del día siguiente decía «8 en deuda, clase c» y mandó a buscar un bug inexistente. Mide el MÍNIMO de dos filesystems, no uno: en gioser store/ (/dev/sdb, 109 G) y work/ (/dev/sdc, 53 G) son discos distintos y el que se llenó fue el de work/, donde vive el árbol de fuentes. Mirando sólo el store el vigía habría leído 109 G y dejado moler igual. Al cruzar el suelo corta con exit 3 y lista las recetas SIN INTENTAR, que no es lo mismo que fallidas. |
||
|
|
1645e251bc |
cosmic anidado: el panel dibuja en una ventana — y el cierre del perfil NO puede hacer GL
Hermano de `sway-nested.sh` para el cuarto escritorio. Levanta cosmic-comp nesteado y sus clientes;
sale con el panel entero: app-library, app-list, siete applets, pop-launcher, cosmic-toplevel y los
dos procesos del portal.
**El hallazgo, que es de la IMAGEN y no del arnés**: `/usr/lib/dri` del rootfs hidratado trae
`iris_dri.so` y NADA MÁS — el driver de Intel real. Sin `swrast_dri.so`, EGL no inicializa y
cosmic-comp muere con `Egl(InitFailed(NotInitialized))` detrás de dos `MESA-LOADER: failed to open
{zink,swrast}`. O sea que el cierre 133/133 de `escritorio-cosmic` **no puede componer en ninguna
máquina que no sea Intel** — ni en una VM, ni nesteado.
No es nuevo, es un hueco de DECLARACIÓN: `scripts/gnome/qemu-desktop-image.sh` ya lo dice en su
cabecera y copia `mesa-llvmpipe` encima con `--remove-destination`. Cada script de imagen inyecta a
mano algo que `targets.toml` no declara — justo la divergencia que `hydrate-profile.py` hace
visible. Y no se arregla declarándolo: `mesa` y `mesa-llvmpipe` instalan los MISMOS `.so`
(libEGL/libgbm/libGLESv2), así que las dos en un perfil colisionan fichero a fichero — la figura de
las dos poppler. Los scripts lo resuelven PISANDO, que es decisión de imagen y no arista de grafo.
Acá se hace lo mismo y se dice: `mesa-llvmpipe` entra como TERCER overlay, el último, o sea el de
mayor prioridad. El artefacto se elige por `hammer hash`, no por el más reciente del store.
Y `cosmic-session` no sirve para nestear: no le pasa `WAYLAND_DISPLAY` a cosmic-comp —para ella el
compositor ES el display server— y su stderr lo captura `launch_pad`, que sólo repite «cosmic-comp
exited with error code 1» y lo reinicia en bucle, así que la causa no aparece en ningún log. Por eso
el modo por defecto es `bare`: compositor a mano y clientes apuntados a SU socket.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6
|
||
|
|
02e93a02ea |
raíz sucia: 945 ficheros de perl en /, con guardián que nombra al culpable y el número que difiere el arreglo
Hidratando el rootfs de sway aparecieron 945 ficheros sueltos en la raíz — `AnyDBM_File.0`,
`App::Cpan.0`, … Son las páginas nroff de perl: su `Configure -des` no encuentra nroff, elige
`man1ext='0'` y `man1dir=' '` (la convención de perl para «no instales man»), pero `installman` las
GENERA igual y con el directorio vacío `make install DESTDIR=/out` las deja en `/out/`.
Ninguna métrica sobre recetas puede ver esto: hay que hidratar un rootfs de verdad y mirarlo.
El guardián va en `hydrate-profile.py` y mira **por artefacto**, no sobre el árbol fundido: en el
árbol fundido el nombre del culpable ya se perdió y 945 ficheros en `/` no se parecen en nada a
«una fase install con el destino vacío», que es lo que son. Barrido el store entero: **perl es el
único** — `.times`/`.dmerge` son internos del store y `product-rootfs`/`seed-zig` son especiales.
El arreglo es UNA línea (`-Dman1dir=… -Dman3dir=… -Dman1ext=1 -Dman3ext=3`) y NO se aplica hoy:
yupana radio perl → transitivos 347 · sellados que CAEN a deuda 305 · TODAS las imágenes
305 rebuilds para mover páginas de man de sitio no se paga solo. Queda escrito en la receta para ir
con el próximo bump de perl, cuando el re-hash ya esté pagado. Comprobado que el comentario NO entra
en `hash_inputs`: el ArtifactHash es idéntico antes y después (b3:1af26f6b).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6
|
||
|
|
b08212a7f1 |
waypipe: el escritorio en una ventana — ciclo de segundos, y el hueco de la libc queda medido
Dos piezas para mirar la distro sin QEMU, que es lo que hacía falta para cazar el puntero
invisible: ese defecto no lo ve ninguna métrica de clausura Y TAMPOCO una captura headless — el
cursor no está en el framebuffer que devuelve screencopy. Hay que mirar la ventana.
`hydrate-profile.py <perfil>`: proyecta el cierre ENTERO de un perfil de `targets.toml`. Los
`hydrate-*.sh` por escritorio traen la lista de raíces escrita a mano dentro del script, o sea DOS
fuentes de verdad: hoy mismo se añadió `adwaita-cursors` a cosmic y sway y esos scripts —que no la
conocen— habrían seguido armando un rootfs sin cursores mientras el perfil decía que los lleva.
Acá el cierre sale de `yupana.membresia()`, la misma función que usan build-state y vigia-imagen.
Y trae la sonda que costó dos intentos: `hydrate` ENLAZA, y `linkat()` no cruza un punto de
MONTAJE aunque sea el mismo disco. El store y `work/out` son dos binds del mismo /dev/sdb ⇒
`st_dev` COINCIDE y el hardlink falla igual. La primera versión comparaba `st_dev` y no disparó:
salieron 174 «artefactos que no proyectaron» y un rootfs de 272 ficheros con pinta de problema de
recetas. Ahora la sonda es FUNCIONAL —se intenta un enlace de verdad— y el error nombra el montaje
del store.
`sway-nested.sh`: arranca `escritorio-sway` como ventana del compositor que ya tenés delante.
Evidencia de esta corrida, con el rootfs hidratado del perfil:
[wlr] [xcursor] Loaded cursor theme 'default' at size 24 (62 available cursors)
o sea el tema llamado literalmente `default` —el alias que fabrica `adwaita-cursors`— resolviendo
a los 62 cursores de Adwaita. Antes de hoy esa línea no existía.
Tres cosas que sólo salen corriéndolo:
· **Ningún perfil incluye `musl`.** Ni `base`. El cierre de escritorio-sway no trae UN `ld-musl` y
sway es `link=dynamic` con intérprete `/lib/ld-musl-x86_64.so.1`: la libc sale del bootstrap, no
de una receta del perfil. Por eso el arnés monta DOS overlays. No es un bug, pero no estaba
escrito en ningún lado y un rootfs de perfil solo no arranca.
· **`yambar` no va en `bar { status_command … }`**: es una barra completa de layer-shell, no un
productor de estado para swaybar. Puesta ahí, sway rechaza la config ENTERA y arranca pelado —
se lee como «el escritorio está roto» cuando lo único mal es una línea.
· **`grim` hay que apuntarlo al socket de NUESTRO sway.** Adentro `WAYLAND_DISPLAY` es el del HOST
(es lo que sway necesita para nestear), así que un `grim` a secas devuelve una captura perfecta…
del escritorio de al lado. La primera captura fue exactamente eso.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6
|
||
|
|
8c4e1a6b24 |
cursores: cosmic y sway corrían con el puntero INVISIBLE — receta propia, y el vigía medía mal
`vigia-imagen.py` daba ✗ en cursores en DOS de los cuatro escritorios. No es cosmético: con el cursor por software —obligatorio en virtio-gpu y en todo render por CPU— el compositor dibuja la imagen que le da el TEMA, y sin tema el ratón se mueve invisible. cosmic llegó a 43/43 y sway a 173/173 así, porque un tema de cursor no es dep de build de nadie: sólo entra si se DECLARA. `adwaita-cursors` (corpus, 48.1, data-only): del mismo tarball que `adwaita-icon-theme` pero SÓLO `Adwaita/cursors/` — 39 ficheros y 14 MB, sin un icono. Promover el tema entero habría regalado a sway y a cosmic los iconos de GNOME, que está anotado como decisión pendiente y no como olvido. Las dos cosas que el tarball no trae y la receta fabrica: · los nombres X11 heredados (`left_ptr`, `xterm`, `watch`, `hand2`…) son enlaces que genera el `meson.build` de upstream. El mapa se PARSEA de ahí, no se copia: copiado envejece en silencio. Si el origen de un enlace no existe, la fase falla — upstream pone un `files()` como aserción. · `/usr/share/icons/default/index.theme` con `Inherits=Adwaita`. Sin `XCURSOR_THEME` en el entorno, libXcursor y wlroots buscan el tema llamado literalmente `default`; sin él no hay puntero AUNQUE Adwaita esté instalado. Es el eslabón que hace que ande sin configuración. ⚠ no declarar esta receta junto a `adwaita-icon-theme`: chocan en `Adwaita/cursors/*`. Y el vigía estaba midiendo el invariante de al lado: exigía `index.theme` en el directorio para contar un tema, que es correcto para ICONOS —la búsqueda XDG recorre `Directories=`— y falso para CURSORES, porque libXcursor abre `<tema>/cursors/<nombre>` directo y el índice sólo hace falta para seguir un `Inherits=`. Con la receta instalada seguía diciendo «NINGÚN tema de cursor». De paso queda anotado en `targets.toml` que el comentario de cosmic decía «sin ellos arranca sin puntero» sobre `cosmic-icons`, que no trae cursores: describía una protección que no existía. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6 |
||
|
|
fdce080d96 |
qorpa D8: sniper sellado al store, con la marca que evita que la cifra mienta
D8 decía que sniper «entra al store por `file_drop`». Dos correcciones, y la primera es de vocabulario: **`file_drop` en hammer es otra cosa** — una operación de `hammer apply` que coloca un fichero en el sistema instalado verificando su hash. No tenía nada que ver con sellar. Lo que sella es lo de siempre, una receta. Queda escrito en el ADR: un término inventado que suena a mecanismo existente manda a buscar el código donde no está. `recipes/steam-runtime-sniper.toml` sella el árbol del runtime (11196 ficheros) pineado por el sha256 que ya estaba verificado. Entra donde Arch y Ubuntu no pueden por una propiedad, no por simpatía: **no muta** —nadie le instala nada adentro— así que el mismo tarball da siempre el mismo árbol y sellarlo es una afirmación verdadera. **La marca: `foreign = true`.** No cambia el build en un byte y **no entra en `hash_inputs`** (describe procedencia, no identidad — hay test). Lo que cambia es contable: `build-state.py` la clasifica `ajeno`, la resta del denominador de las imágenes y la deja fuera del recuento de recetas. Sin eso, sellar un prebuilt habría subido la cifra que todo el mundo lee como «cuánto construimos» — el riesgo que el ADR escribió antes de que existiera la primera instancia. Verificado: sigue diciendo 821 recetas, y aparte `de las ajenas, 1 selladas al store (prebuilt pineado, sin procedencia de fuente)`. Y la diferencia con el otro ajeno: `xwayland` no se hashea (no hay receta, y un hash afirmaría que lo reproducimos); el sellado **sí conserva su hash**, porque está en el store y que un artefacto exista mientras el grafo lo niega sería otra forma de mentir. Comparten el estado, que es lo que protege la cifra. **`hammer qorpa import --from-store <hash>`** lo consume, y ahí está el detalle que hace que valga: la imagen se registra bajo el **sha256 del archivo de upstream**, no bajo el ArtifactHash. Al revés, la imagen del store y la traída con `pull` serían dos imágenes distintas con los mismos bytes y las instancias de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar. El árbol se **enlaza**: una imagen nunca se escribe (lo que escribe la instancia va a su `upper`), así que compartir inodos con un artefacto sellado y de sólo lectura es correcto por construcción y la imagen cuesta ~0 bytes. La contracara conocida de `.dmerge`: mientras el artefacto siga en el store, borrar la imagen no libera disco; `--copy` lo evita. Licencia `LicenseRef-qorpa-ajena-no-enumerable` a propósito: adentro hay cientos de paquetes Debian y no podemos enumerarlos; vacío se leería como «todavía no la poblamos». SDD 20 lo recoge y afila la distinción: replicarla a nuestras máquinas es lo que ya hace ADR 0013 con las fuentes; publicarla a terceros sigue pidiendo licencia y marca. 29 tests verdes. El sellado en sí corre aparte, esperando el lock de la granja. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q |
||
|
|
7448e106b8 |
campana-deuda: PODA_FUENTES=1 — la campaña se poda sola, dentro del bucle
Las 71 recetas KDE de la cascada de kcoreaddons se muelen en ESTA máquina (sin worker de pago) y `work/sources` vive en `/mnt/vvv`, que arrancó la campaña con 16 G libres. A ~300 MB de árbol por receta, sin podar la campaña se come el disco antes de la onda 4 — y con el disco lleno git falla a mitad y deja el índice a medias, que ya pasó. La poda va DENTRO del bucle y no en un cron paralelo, que es el punto: ahí tenemos el lock Y acabamos de terminar un build, así que no hay ningún bwrap usando un árbol. Correr `poda-fuentes.sh` en paralelo sería el ADR 0012 en su forma más directa — su propio encabezado avisa de que el mtime NO distingue «viejo» de «lento». No cuesta nada: `fetch.rs` borra y re-extrae el árbol en CADA build, nunca lo reutiliza. Default apagado (0), para no cambiarle el comportamiento al worker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGXe6mShYYw6V8ea1dQ13u |
||
|
|
5b24d8b030 |
vigia-imagen: el guardián de los dos huecos de hoy — data de runtime y features apagadas
La regla del repo es que cada punto ciego se vuelve un guardián. Hoy aparecieron dos, los dos
arrancando KDE en QEMU y ninguno visible para las métricas que ya había:
1. DATA de runtime que ninguna arista de BUILD alcanza — el tema de iconos. `build-state` mide
lo DECLARADO y nadie declara lo que no es dep de build de nadie.
2. Una porción OPCIONAL de una librería que sí está — el módulo QML de kcoreaddons. No falta una
receta ni una librería: falta una FEATURE, y ninguna métrica sobre recetas puede verlo.
`vigia-imagen.py` recorre los ARTEFACTOS del cierre de cada perfil (no las recetas: el artefacto es
lo que se instala) y comprueba cinco invariantes de imagen USABLE: iconos, hicolor, cursores,
fuentes, terminal y que todo `import` de los `.qml` instalados tenga un módulo con `qmldir`.
Hermano de `vigia-sonames.py`, que cubre la otra mitad del runtime (los SONAME sin proveedor).
Tres decisiones de diseño que salieron de usarlo contra el corpus real:
- **MEDICIÓN PARCIAL, gritada.** El primer informe dijo «KDE no tiene terminal» teniendo konsole:
los 71 artefactos que la cascada de kcoreaddons dejó en deuda no existen en el store, y sin
artefacto no se puede afirmar NI que falta NI que está. Ahora cuenta los nodos sin artefacto, lo
dice arriba de todo y no cuenta esos ✗ como fallos. `--fail` distingue **exit 1 = medí y falta**
de **exit 2 = no pude medir**. Es la regla del ausente ruidoso, aplicada al propio vigía.
- **EXCEPCIONES con motivo escrito.** `escritorio-mirada` es slim a propósito y `escritorio-sway`
no lleva tema de iconos por una decisión medida. Un ✗ permanente por algo ya decidido es deuda
fantasma — la figura de la terna GNOME que hubo que sacar de targets.toml.
- **Ruido eliminado midiendo, no suponiendo.** `QtSystemInfo` salía como hueco y estaba DENTRO de
un bloque \qml de la documentación de `Video.qml`; `HelperWidgets` viene de los `*Specifics.qml`
de `designer/`, que sólo carga Qt Design Studio. Se despojan comentarios y se saltea `designer/`.
Estado hoy: gnome ✓ en los seis; cosmic y sway ✓ salvo **cursores**, que queda por triar — el
puntero SÍ se ve en las capturas de las dos, así que puede ser fallback embebido del compositor
(smithay trae uno) y no un hueco. KDE sale parcial por la deuda de kcoreaddons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGXe6mShYYw6V8ea1dQ13u
|
||
|
|
914c1b72af |
kcoreaddons: prender el módulo QML — el menú de Plasma ABRE, y detrás de él konsole
`-DKCOREADDONS_USE_QML=OFF` estaba desde que se escribió la receta, con el argumento razonable de
que un framework tier-1 no debería arrastrar QML. El precio no se vio hasta hacer CLIC en el
lanzador dentro de QEMU: `kickoff` importa `org.kde.coreaddons` y ese módulo lo instala esta receta
y ninguna otra ⇒ **el menú de aplicaciones no abría**. La librería C++ salía completa, sus 70
consumidores enlazaban bien y el perfil reportaba 100%.
Es la forma más pura del «sellado ≠ arranca»: no falta una librería, falta una porción OPCIONAL de
una librería que sí está. Ninguna métrica de clausura puede verlo — mide recetas, no features.
`qtdeclarative` ya estaba en `[deps].build`, así que prenderlo no agrega una dep: deja de tirar lo
que ya se podía construir.
VERIFICADO CON UN SOLO BUILD (56 s), a propósito, en vez de pagar la cascada para averiguarlo:
· panel → menú abre (usuario, buscador, Favoritos/Todas, Aplicaciones/Lugares/Sesión)
· buscar «konsole» + Enter → la terminal arranca y corre:
uname -a → Linux (none) 6.16.12 #1 SMP PREEMPT_DYNAMIC … x86_64
konsole --version → konsole 25.04.3
Es la cadena completa del escritorio por primera vez: panel → menú → búsqueda → app → shell.
COSTO, medido antes de tocar y confirmado después: `yupana radio kcoreaddons` predijo 71, y el grafo
regenerado da exactamente **71 en deuda** (`escritorio-kde 192/263`). Queda como deuda DECLARADA
para una campaña de granja; la imagen de hoy corre con un kcoreaddons más nuevo que aquel contra el
que enlazaron sus consumidores, lo que es legítimo porque cruza un SONAME (misma ABI, sólo se suma
un módulo QML) — la misma regla que decidió la promoción de pipewire.
De yapa: `export SHELL=/bin/sh` en plasma-start-qemu.sh. El aviso rojo de konsole («Could not find
'', starting '/bin/sh' instead») era real y no venía de /etc/passwd —que dice /bin/sh— sino de que
konsole lee $SHELL y este getty no es un login shell.
Y el barrido que encuentra esto sin hacer clic queda escrito en el runbook: cruzar los `import` de
los `.qml` instalados contra los módulos con `qmldir`. Además de éste destapó `org.kde.kscreenlocker`
y `org.kde.newstuff.core`, sin diagnosticar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGXe6mShYYw6V8ea1dQ13u
|
||
|
|
4a68dfe761 |
qorpa D9: el canal de evidencia, cableado — la única forma de auditar el montón B
De un binario ajeno no hay fuente que leer. Lo único observable es lo que el
kernel le NIEGA y anota, y hasta acá ese canal existía en el build pero ninguna
instancia lo abría. `hammer qorpa run <id> --evidence` levanta el lector
(`harkaq-audit`) en el HOST —el audit no está namespaceado— antes de que arranque
la instancia, y al terminar imprime uno de tres estados. Los tres, medidos:
HERMÉTICO 0 denegaciones Y el canario las respalda
IMPURO `touch /usr/INTRUSO; mkdir /opt/INTRUSO` →
fs.make_reg · /usr fs.make_dir · /opt
SIN EVIDENCIA quitándole las capabilities al lector. NO es «limpio»
**El canario es lo que hace que «cero denegaciones» valga algo:** un fichero
donde la política no alcanza; al leerlo, el kernel emite una denegación que
revela el `domain=` de ESTE dominio Landlock, un número que desde fuera no se
adivina. Sin él, `denials=[]` sería el instrumento callado.
**Dos condiciones estructurales, y se FALLA en vez de dar un veredicto vacío:**
con `nesting` no hay Landlock (D9 conflicto 1) ⇒ o anidás o auditás; y sin
`seal_image` la política es `rw /` ⇒ no hay NADA denegable y el veredicto sería
limpio por construcción, no por mérito. No es un defecto de la implementación:
**la evidencia sólo existe donde algo puede ser negado.**
**Un bug del propio instrumento, que sólo salió usándolo:** sin CAP_AUDIT_READ
el kernel RESPONDE que no (`NLMSG_ERROR`/EPERM) y el lector ignoraba esa
respuesta esperando una que no iba a llegar — 8 s por consulta, 16 s en su
compuerta. Como `qorpa run` lo despierta al terminar, moría por señal dentro de
la compuerta **sin emitir nada**: un «no» tardío se parecía demasiado a un
cuelgue. Ahora atiende el NLMSG_ERROR y dice su motivo en 2 s. Y si aun así el
veredicto sale vacío, se reporta con el código de salida del lector, que es el
único dato que queda.
Guardián: `scripts/qorpa/evidence-probe.sh`, con las tres aserciones. La 2 es la
que sostiene a la 1 — sin algo que TIENE que salir sucio, «HERMÉTICO» lo cumple
igual un canal muerto. Comprueba también las capabilities del lector, que **se
pierden en cada recompilación** y son la forma más probable de que el canal
muera en silencio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q
|
||
|
|
74fec60281 |
qorpa §Orden 6: pressure-vessel ANIDA bajo la jaula — sin cuenta, sin juego y sin pantalla
El paso 6 quedaba parcial por esta frase: «pressure-vessel con un juego real sigue sin ejercitarse», porque el runtime sniper sólo se baja al instalar un juego y eso pide credenciales. Era un techo falso: el depot está publicado y ahora pineado, así que se coloca a mano y la pregunta que ordenaba D6 se responde entera. **La evidencia**, dentro de una instancia qorpa sobre Ubuntu base, con `nesting` y la jaula puesta: os-release Ubuntu 24.04.3 LTS → Steam Runtime 3 (sniper) ns de montaje mnt:[4026532468] → mnt:[4026532526] /usr/lib/x86_64-linux-gnu → 675 libs de sniper, libSDL2 incluida Contenedor de Valve anidado dentro del nuestro, con el runtime real adentro. **Y la concesión resultó de verdad:** con `nesting = false` el mismo comando muere en `bwrap: Creating new namespace failed: Operation not permitted`. Queda como guardián (`scripts/qorpa/pressure-vessel-probe.sh`), que exige las DOS mitades — sin la negativa, «no salió sniper» lo cumpliría también un cuelgue. Tres cicatrices del camino, todas en los comentarios: 1. **`ldd --version` es la comprobación equivocada** y es la primera que uno escribe: pressure-vessel importa la libc del host cuando es más nueva que la del runtime, así que ver la glibc de afuera adentro es lo correcto y no prueba nada. El veredicto es `os-release`. 2. **`SALIDA=$(timeout … qorpa run …)` se cuelga para siempre** aunque timeout mate al hijo: la sustitución no espera al PROCESO, espera a que se cierre el PIPE, y pressure-vessel deja descendientes con el fd abierto. A fichero termina y devuelve su código. 3. **Dos `run` seguidos sobre la misma instancia fallaban** con `Can't make overlay mount … Device or resource busy`. El kernel niega dos overlays vivos con el mismo `upper` porque eso corrompe la capa — o sea que el EBUSY es un guardián correcto a destiempo: la corrida anterior ya devolvió el prompt y su namespace no terminó de reaparse. `run` reintenta acotado y, si sigue tomado, dice la causa en vez de soltar el mensaje crudo de bwrap. El punto 3 salió porque el guardián exige evidencia POSITIVA de la denegación: con «no apareció sniper» habría dado OK tapando un fallo distinto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QZjGRqvjWij9dew7NVFu4Q |
||
|
|
aefdce4393 |
install-image-efi: STAGE configurable por entorno
El staging estaba clavado en work/.install-efi-stage. Cuando el rootfs fundido vive en OTRO volumen —el store de este hub es un bind-mount de /dev/sdb y work/ está en /dev/sdc— los hardlinks del staging fallan enteros con EXDEV, porque linkat() rechaza cruzar MOUNTS distintos aunque sean el mismo fs. Con STAGE= se pone el staging del lado correcto. Default idéntico al de antes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0164nrZWZc78Mr2srrsnoM8U |
||
|
|
698ca1da5f |
qemu: ACCEL/CPU parametrizables, con caída automática a TCG
El script clavaba '-accel kvm -cpu host'. En una máquina sin /dev/kvm —un vServer sin virtualización anidada, por ejemplo— eso aborta, y '-cpu host' tampoco existe fuera de KVM. Ahora ACCEL/CPU son variables y, si no hay /dev/kvm escribible, cae solo a 'tcg'+'max' avisando que va lento. Sin cambio de comportamiento donde hay KVM. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0164nrZWZc78Mr2srrsnoM8U |
||
|
|
099e84703f |
imagen: inyectar libffi/expat/bzip2 COMPARTIDAS desde el store
El cierre hidratado sólo trae los .a de estas tres (sus recetas canónicas son
--disable-shared), pero libgobject, libgirepository, libwayland-{client,server},
libp11-kit y libgjs salen con símbolos ffi_*, mesa entero (iris/swrast/kms_swrast,
libEGL, libgbm) con XML_* y freetype con BZ2_*. En el host eso resuelve contra el
sysroot Alpine del LAB —así que el artefacto sella y el perfil da 100%— y en la
imagen el loader de musl aborta:
Error relocating /usr/lib/libgobject-2.0.so.0: ffi_call: symbol not found
gnome-shell moría con código 127 antes de exponer wayland-0, y con él login1,
Accounts, UPower, colord y wireplumber. Es el punto ciego que documenta
scripts/vigia-sonames.py, visto desde el lado de la imagen.
Con la inyección: 'compositor OK (wayland-0)' y el shell pinta (evidencia adjunta).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0164nrZWZc78Mr2srrsnoM8U
|
||
|
|
1d9ddcee37 |
qorpa D10: Steam en la mano, y los tres muros que sólo se ven así
Paso 6 del ADR 0015, PARCIAL y dicho como parcial. Steam 1.0.0.87 instalado de verdad desde el multilib de Arch —las libs de 32 bits que la F2 del plan de juegos daba por «una campaña entera»—, su cliente i386 bajado y desempacado por el bootstrap de Valve junto con el Steam Runtime, y el BWRAP ANIDADO VERIFICADO EXPLÍCITAMENTE (24 montajes propios dentro de la instancia), que es lo que este paso pedía. Lo que NO se pudo: la máquina no tiene sesión gráfica, y el runtime sniper sólo se baja al instalar un juego, que exige credenciales ⇒ pressure-vessel con un juego real sigue sin ejercitarse. Se dice, no se insinúa. Tres muros, ninguno en el ADR: 1. LA JAULA MATABA TODOS LOS BINARIOS DE 32 BITS. El filtro seccomp comprueba arch == x86_64 y MATA lo que no lo sea; para un build es la defensa clásica y correcta, para el montón B es fatal porque el cliente de Steam es un ELF i386. El síntoma fue `ldd: exited with unknown exit code (159)` = 128+31 = SIGSYS, que no se parece en nada a la causa. La salida no es aflojar el check sino darle a i386 su propia tabla con la MISMA política. Los 25 números se verificaron uno por uno contra /usr/include/asm/unistd_32.h: 24 bien y UNO MAL — kexec_file_load no existe en i386 y su número de x86_64 (320) es ahí `utimensat`, o sea que habríamos denegado algo que usa cualquier cosa que toque una marca de tiempo. Es la diferencia entre una tabla de memoria y una verificada. 2. STEAM SE NIEGA A CORRER COMO ROOT, y cambiar el mapa para evitarlo CORROMPE la instancia: un fichero creado bajo un mapa aparece con otro uid bajo el otro, así que el useradd de la preparación deja un /home que su propio dueño no puede escribir. ⇒ el mapa es parte de la IDENTIDAD de la instancia. La vía correcta es la de cualquier runtime de contenedores: un solo mapa y se BAJA de privilegio adentro — campo `run_as`, setpriv, con el CAP_SETUID que ya tenemos en el namespace. 3. EL XDG_RUNTIME_DIR ES DEL USUARIO QUE CORRE, no del uid del mapa: con `run_as`, apuntarlo al de root deja al Steam Runtime sin poder crear su temporal. Se lee como un aviso menor hasta que algo deja de andar sin decir por qué. Lo que sí quedó probado, y es el corazón del ADR: un userland glibc ajeno con su cadena de 32 bits completa corre enjaulado sobre nuestro kernel, con seccomp y no_new_privs puestos, y un contenedor anidado funciona adentro — la forma exacta en que Valve prueba Proton. 1 test nuevo (run_as resuelve uid/gid/home del passwd de la imagen, y un run_as inexistente NO cae a root). 48/48. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2 |
||
|
|
e6cfb470f1 |
vigía: provee.py — qué publica de verdad el catálogo, y desde dónde se alcanza
El triaje de apps falló TRES veces, cada vez por una pregunta distinta y cada vez la
anterior daba verde:
1. ¿existe la receta en el disco? — el método original
2. ¿la ALCANZA el consumidor? — wf-recorder grababa mudo: sus backends de
audio existían, pero en colas hermanas, que una receta del corpus no ve
3. ¿la VARIANTE sellada publica la ABI? — imv: mesa existe, es alcanzable, y aun así
no sirve; las tres variantes no publican libGL.so ni gl.pc
Las tres son la misma equivocación disfrazada: preguntarle al CATÁLOGO lo que sólo
sabe el ARTEFACTO. Este vigía indexa los .pc y las librerías (.a y .so, porque
find_library no mira pkg-config) de todos los artefactos sellados y contesta quién
publica cada nombre y desde qué colas es pedible, aplicando sibling-first: lo del
corpus lo ve todo el mundo, lo de una incoming-* sólo esa cola.
Es el hermano de BUILD de vigia-sonames.py, que cubre la mitad de RUNTIME. La de build
se paga antes: es la que decide si el configure de una receta nueva va a morir.
Tres decisiones de forma que salieron de usarlo y verlo fallar:
- el nombre se busca flojo: gl, gl.pc, libGL.so.1 y librsvg (que tiene que encontrar
librsvg-2.0.pc) dan lo mismo. El nombre que trae un APKBUILD casi nunca lleva la
versión, y comparar a lo bruto daba falsos «nadie lo publica».
- agrupado por receta, no por fichero: mesa publica cuatro ficheros de EGL y repetirla
cuatro veces convierte el informe en ruido justo cuando hay que leerlo rápido.
- --desde <cola> sale con código 1 si algo no se alcanza ⇒ sirve de puerta en cron/CI.
Verificado contra los dos casos conocidos: --desde corpus con lo que swayimg pide da
0, y con lo que imv pedía (gl, opengl) da 1. Barrido completo ~9 s con caché por
ArtifactHash en work/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
|
||
|
|
f64859bade |
qorpa export: shims generados, y la clase ajeno para que nadie los cuente mal
Paso 5 del ADR 0015, sus dos mitades. SHIMS. `hammer qorpa export <id>` genera lanzadores finos y `.desktop` en el espacio del host, desde lo DECLARADO en `[export]` — nunca todo: exportar todo haría que el `ls` de la imagen compita con el nuestro, que es la falla de Bedrock (arbitra en tiempo de exec, por heurística). Gana en tres cosas contra un FUSE: cero costo en runtime, `cat` al shim y ves qué hace, y se revoca borrándolos. Se GENERAN, no se copian. El `.desktop` se arma con lista BLANCA de claves, así que `Exec`, `TryExec`, `Path` y `DBusActivatable` quedan fuera por definición y no por enumeración — una lista negra dejaría entrar la próxima clave ejecutable que invente el estándar. El Exec original se cita en un comentario del fichero generado, para que se vea qué decía y qué no se copió. El icono se busca en la vista merged (upper primero, imagen después: si no, se perdería lo que instaló el gestor de paquetes) y se copia al host, porque un icono que el host no resuelve se ve como un cuadrito gris. Y `exported.json` registra cada fichero escrito, para que `--remove` borre EXACTAMENTE eso y no por patrón sobre el ~/.local/bin de alguien. Probado de punta a punta con un .desktop ajeno real de la imagen de Arch: el shim corre `pacman -Q` del huésped desde el host, el X-KDE-Wayland-Interfaces quedó fuera, el icono viajó, y --remove dejó 0 ficheros con la instancia intacta. CLASE `ajeno`. build-state.py inyecta los nodos declarados en el nuevo docs/state/qorpa-ajenos.toml ANTES que los `wanted`, y ese orden es la mitad del punto: un nodo que provee una imagen ajena no es una receta por escribir. Con eso el `xwayland` de escritorio-kde deja de ser deuda y pasa a contarse aparte: escritorio-kde 187/188 listo falta 1 (raíces 14, + 1 ajenas) Dos decisiones que sostienen esa cifra: los ajenos se RESTAN del denominador (si entraran, el número que se lee como "cuánto construimos" crecería solo cada vez que alguien enjaula una app), y la declaración vive en el REPO y no se lee de /var/lib/hammer — build-state.json se commitea y lo regenera el cron en dos máquinas; si la clase saliera de las instancias instaladas, cada una diría algo distinto y se pisarían en cada cosecha. Es el error que ya se cometió con sealed_remoto. Qué provee una imagen ajena es diseño; qué tenés instalado, no. Un ajeno tampoco se hashea, y no por comodidad: no tiene procedencia de fuente, así que un hash afirmaría que lo reproducimos. 2 tests nuevos (que del .desktop ajeno no sobreviva nada ejecutable; que el shim no se rompa con rutas raras). 47/47. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2 |
||
|
|
89da9c822f |
qorpa: el mapeo por rango — subuid + --userns FD, y el impuesto se paga
Resuelve §NO-resuelve 2 del ADR 0015, que era el ticket que más desbloqueaba. Tres síntomas que parecían distintos —apt sin poder bajar a `_apt`, pacman sin poder chownear a `alpm`, pressure-vessel sin poder escribir su uid_map— eran la misma causa: bwrap crea el userns con UN SOLO id. La cura resultó tener TRES partes, y ninguna sobra: 1. `setcap cap_setuid+ep newuidmap` (+ cap_setgid en newgidmap). shadow.toml los instala pero no los provisiona; sin la capability no escriben el mapa. 2. Crear el userns nosotros, mapear el rango de /etc/subuid con newuidmap y pasárselo a bwrap con `--userns FD`. bwrap crea el suyo con un solo id A PROPÓSITO y nunca llama a newuidmap: el trabajo es de quien lo invoca. El fd lo abre la shell (`exec 3<…`), porque un fd sólo cruza el exec si no es CLOEXEC y no valía la pena una dep de C para un fcntl. 3. Devolver las capabilities DENTRO del namespace. Ésta no estaba en el plan y es la que costó: bwrap las tira todas, y en Linux ser root es tener CAP_SETUID, no tener uid 0. Sin ella apt seguía sin poder seteuid(42) — un síntoma que parecía de subuid y no lo era. Son seguras por construcción: dentro de un userns sólo alcanzan lo que ese namespace posee, o sea nuestros propios subuid. CAP_SYS_ADMIN queda fuera y sigue colgando de `nesting`. MEDIDO después: uid_map de 65537 ids, setgroups: allow, apt instala SIN el APT::Sandbox::User=root, pacman sincroniza con DownloadUser=alpm INTACTO, y el userns anidado monta con root=true ⇒ el conflicto 2 de D9 se disuelve solo. Dos cosas más que salieron por medir, no por pensar: - El guardián MENTÍA. qorpa-preflight envolvía al hijo en `timeout`, que forkea, así que newuidmap apuntaba al PID equivocado y el kernel respondía "Operation not permitted" — un falso negativo idéntico a un fallo real. Decía que subuid no andaba cuando a mano andaba. Ahora sale exit 0. - Quitar el impuesto MUEVE el problema: el upper pasa a contener ficheros de los subuid (apt deja los suyos con uid 165577) que nuestro uid no puede borrar ⇒ recreate entra a un userns mapeado para limpiar. Y cuando no hay rango, se degrada diciendo la causa exacta en vez de quedar en misterio. Y un detalle que no es cosmético: `--perms 1777` antes del `--tmpfs /tmp`, o el _apt al que apt baja no puede escribir su fichero temporal. Un /tmp que no es 1777 no es /tmp. 2 tests nuevos (el rango se lee por usuario; CAP_SYS_ADMIN NO está en las caps de root, o `nesting` dejaría de ser una decisión). 45/45. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2 |
||
|
|
5b82474079 |
qorpa D9: harkaq enjaula la instancia, y midiendo salieron dos conflictos
Paso 4 del ADR 0015. harkaq-exec entra como último eslabón dentro de bwrap, igual que en el sandbox del build. Cruza el borde un binario ESTÁTICO, no una librería, así que D2 sigue en pie: lo único compartido es la ABI del kernel. Honestidad primero, y está escrita en el código: en el eje del sistema de ficheros harkaq casi no agrega nada, porque el namespace de montaje de bwrap ya es una lista blanca. Escribir reglas `ro` que repiten eso sería un sello de goma, así que la política de una instancia no sellada es UNA línea (`rw /`) y no finge. Lo que sí aporta: seccomp (bwrap no instala filtro alguno — hoy una instancia podía io_uring, bpf, ptrace, userfaultfd, keyctl, perf_event_open), no_new_privs, el canal de evidencia, y `seal_image`, que congela /usr /bin /lib /opt aunque adentro seas root. Verificado contra el kernel, no contra el log: Landlock ABI 9, logging post-exec ON, NoNewPrivs 1, Seccomp 2. Y sellando, `/usr/bin` y `/bin` denegados mientras /etc y /var siguen escribibles. DOS CONFLICTOS que sólo se ven midiendo, y ninguno estaba en el ADR: 1. Landlock y los contenedores anidados son INCOMPATIBLES hoy: con un dominio activo, `mount` falla con EACCES aunque seccomp lo permita — el kernel no admite montajes nuevos bajo un dominio porque escaparían de sus reglas por-ruta. ⇒ pressure-vessel no arranca bajo Landlock. Por eso `nesting` pasa `--allow-nesting --no-landlock` y lo dice a gritos; seccomp y no_new_privs siguen puestos, que es lo que más pesa con un binario ajeno. 2. `root` adentro y anidar se pelean: con --uid 0, un userns anidado no puede escribir su uid_map. Sin remapear anida, pero el gestor de paquetes se queja. La instancia de juegos y la de paquetes quieren mapeos OPUESTOS, y ahora el manifiesto lo declara (`root`, encendido por defecto). Las dos mitades se curan con lo mismo que el impuesto de apt: un rango real de subuid con newuidmap + --userns FD. Ése es el ticket que más desbloquea. En harkaq-exec, dos flags ADITIVOS y apagados por defecto (--allow-nesting, --no-landlock): el camino del build no cambia ni un byte, que es requisito duro con 700+ artefactos sellados. La lista de syscalls del anidamiento se separó de la base y el _Static_assert del techo de salto BPF ahora suma las dos. 3 tests nuevos: que la política sin sellar no finja, que sellando el `rw /` no sobreviva (uniría derechos por ancestro y anularía el sellado), y que `root` sea lo único que nace encendido. 43/43. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2 |
||
|
|
04e2c89014 |
vigia-sonames: el flag va en inglés (--fail), regla 4 del CLAUDE.md
Código nuevo nace con la superficie de CLI en inglés. Nació con `--fallar` unas horas antes de que la regla quedara escrita; se corrige ahora que no lo llama nadie todavía. |
||
|
|
f9da495b8c |
vigía de sonames: KDE, GNOME y COSMIC tenían la MISMA fuga al lab que sway ya había documentado
Verificando que mpv anduviera en las cuatro imágenes salió esto: `libEGL.so.1` (mesa) pide
`libexpat.so.1` y `libwayland-client.so.0` pide `libffi.so.8`, las recetas canónicas de expat y
libffi son `--disable-shared`, y ningún artefacto del cierre publica esos SONAME ⇒ los tres rootfs
los resolvían contra el **sysroot Alpine DEL LAB**.
Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que el store no puede notarlo —
el artefacto se sella, el perfil reporta 100%, y la imagen sólo arranca en una máquina con Alpine
debajo. El perfil de sway ya tenía escrito exactamente este párrafo desde 2026-08-26; lo que faltaba
era el instrumento para ver que las otras tres estaban igual.
Arreglo (gratis, sin rebuild: las dos recetas ya estaban selladas por sway): `expat-shared` y
`libffi-shared` pasan a raíces de los tres perfiles. Van de raíces y no de `[deps]` por la misma
razón que las fuentes y el XKB — son data de RUNTIME y ninguna arista de build las alcanza.
`scripts/vigia-sonames.py` es el guardián que sale del punto ciego: recorre los NEEDED de todo el
cierre de cada imagen contra los SONAME que ese mismo cierre publica. Dos decisiones de diseño:
· keying por PAR `(cola, nombre)` vía yupana, NO por nombre — `build-state.json` colapsa los
nombres que viven en dos colas y su campo `perfiles` puede quedar vacío para una receta que sí
está en la imagen.
· imprime SIEMPRE quién pide cada soname, porque el cierre incluye herramientas de build
(python3, cmake, perl, go) que la hidratación no instala: sin esa columna el informe no se tría.
Después del arreglo, `libexpat.so.1` y `libffi.so.8` desaparecen de los tres. Lo que queda son
hallazgos REALES que no son de este commit y quedan anotados:
· kde: karchive pide libbz2.so.1 y liblzma.so.5
· gnome: spidermonkey pide libstdc++.so.6 y libgcc_s.so.1; libadwaita pide liblzma.so.5;
freetype-shared pide libbz2.so.1; sqlite-shared pide libreadline.so.8
· cosmic: llvm18 pide libgcc_s.so.1
· los tres: `pw-top` de pipewire enlaza `libncursesw.so.6` DEL LAB — y la receta afirma en un
comentario que sin la dep «meson saltea pw-top». Es falso desde al menos el 2026-08-29:
`dependency('ncursesw')` lo encuentra igual en el sysroot del lab. El comentario dice una cosa
y el binario otra.
|
||
|
|
aa5bb67fd7 |
qorpa: nombre adoptado y el preflight que mide si una máquina puede alojar
ADR 0015 pasa de "propuesta de nombre" a `qorpa` adoptado: la frontera es
`hammer qorpa {…}` y el espacio de nombres se unifica en
/var/lib/hammer/qorpa/{imagenes,instancias}/ — un solo árbol, para que la poda
de §NO-resuelve 5 tenga un único sitio que barrer. La clase de nodo del grafo
sigue siendo `ajeno`: describe la procedencia, no el subsistema.
Y arranca el §Orden de trabajo 1 (subuid) como GUARDIÁN en vez de a mano:
scripts/qorpa/qorpa-preflight.sh mide las cinco capacidades de entorno que un
huésped necesita y que no están en ningún grafo — userns sin privilegios (+
anidado), mapeo multi-id, overlayfs sin root, los nodos del borde y disco.
Tres niveles (BLOQUEA/LIMITA/NOTA) y salida 0/1/2, porque "arranca pero sin
dnf" es una respuesta legítima, no un error.
Medido en `momento` (exit 2, 2 limitaciones):
- userns ANIDADO funciona ⇒ el "verificar, no asumir" de D6 (pressure-vessel
creando su userns dentro del nuestro) queda verificado a nivel de primitiva.
- subuid es papel mojado acá: el rango está declarado en /etc/subuid y las
herramientas están, pero newuidmap/newgidmap vienen sin setuid y sin
capability ⇒ no pueden escribir el uid_map. Es exactamente la "primera cosa
que va a fallar" del ADR, y resulta ser de PROVISIÓN, no de kernel.
El guardián ya se corrigió a sí mismo una vez: marcaba LIMITA por
CONFIG_OVERLAY_FS=m mientras tres secciones más abajo el overlay montaba de
verdad. Manda la prueba funcional, no la declarada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
|
||
|
|
6205882b3a |
deferred: jubilar 5 aparcadas que ya sellan, y corregir lo que dice el comentario
`recipes/incoming/.deferred/` decia "24 recetas aparcadas con diagnostico, p.ej. git con muro en libgit.a". Las dos mitades de esa frase eran falsas. Cinco de las 24 ya estaban RESUELTAS en otro fichero y sellan hoy: dprint, git, gmp, mise e yj (recipes/git.toml, recipes/mise.toml, incoming-kde/gmp.toml...). La copia aparcada era la version anterior y seguia leyendose como deuda abierta — y el ejemplo que daba el comentario era justamente uno de esos cinco. mise se construyo esta misma tarde en el worker. Se van, con `gcc15.patch` que solo citaba la gmp aparcada (ninguna de las dos gmp que sellan declara patches). Y las 19 que quedan no estan "aparcadas con diagnostico": son volcado CRUDO del importador, 15 de import-nix y 4 de import-alpine, todas con la cabecera "PUNTO DE PARTIDA, no final". Nadie las intento todavia; no hay ningun muro que respetar. Es una cola de trabajo sin empezar, no una lista de imposibles, y leerla como lo segundo desanima de tocarla. Gate --check OK, grafo CIERRA, corpus 786/787; las 5 que sellan intactas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
3231c43cdd |
granja: jubilar incoming-clib — 0 recetas y 26 parches inalcanzables
Era la cola de staging de las tandas base-system/foot y sus recetas ya se
habian PROMOVIDO a canonicas commit a commit ("promover 7 tier-2", "promover
cadena crypto", "promover tllist/utf8proc/scdoc/fcft/foot"). Lo que quedaba
eran 0 .toml y 26 .patch: 5 copias byte a byte de la version canonica de
recipes/ y 21 de recetas que ya no viven ahi.
No eran "probablemente inutiles": son inalcanzables POR CONSTRUCCION.
`fetch::apply_patches` resuelve `recipe.base_dir.join(nombre)` y `base_dir` es
el directorio de la receta, sin fallback a `recipes/` — un parche en una cola
sin recetas no lo puede pedir nadie.
⚠ El corolario queda escrito en farm-worker-loop.sh: al promover una cola, los
`.patch` NO viajan con la receta, y la cola queda en un estado que PARECE vivo
(tiene ficheros) sin moler nada.
Sale de QUEUES en farm-worker-loop.sh y de la lista de promote de farm-down.sh.
`incoming-go` se queda en las dos aunque el directorio no exista: es la cola
aislada del frente Go y el guard `ls $Q/*.toml` salta las vacias; quitarla seria
dejarla muerta el dia que ese frente la recree.
Verificado con la regla ESTRICTA de resolucion de parches sobre todo el corpus:
94 referencias resuelven, 0 rotas. (El chequeo que use al jubilar onda-2 era mas
laxo que hammer — aceptaba recipes/<parche> como fallback; su conclusion se
sostiene, pero el metodo queda corregido.)
Gate --check OK, grafo CIERRA, corpus 786/787.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
|
||
|
|
927dee60cf |
fuentes: HAMMER_MIRROR es una LISTA — un espejo propio era el mismo punto único de fallo
El ADR 0013 existe para no depender de 79 servidores ajenos, y lo resolvió creando una dependencia de UNO nuestro. Las dos variables aceptan ahora varios orígenes separados por comas y se prueban todos antes de caer a upstream. El orden se ROTA con una semilla determinista tomada del hash del propio objeto: el mismo objeto sale siempre del mismo origen (un fallo se reproduce), pero objetos distintos se reparten ⇒ una tanda del corpus ejercita la lista entera. Con orden fijo, el segundo origen no se tocaría jamás hasta la emergencia — que es literalmente «un espejo que nadie prueba», la lección del 0013 un nivel más arriba. La parte pura sale a `rotar_bases` para poder probarla sin tocar el entorno del proceso (que es global y haría los tests dependientes del orden en que corren). 3 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CK6HpSoHcN9M4GBpqRSusR |
||
|
|
a2c2c4d968 |
gnome onda-2: jubilar la cola — 22 recetas y 22 hashes identicos a incoming-gnome
El caso LIMPIO del mismo fenomeno que onda-1, sin ninguna de sus asperezas: las 22 recetas son byte a byte identicas a las de incoming-gnome Y las 22 dan el MISMO ArtifactHash, o sea la misma direccion del store. Sin divergencias, sin colision de nombre y sin un solo artefacto que podar al retirarla. La comprobacion que decide NO es `diff` sino `hammer hash`: la resolucion de deps es hermano->padre, asi que dos ficheros identicos en colas distintas pueden sellar distinto. Aca coinciden los 22. Se va tambien `cairo-ctime-r.patch`, verificado identico al de incoming-gnome, y comprobado que ninguna receta de ninguna cola queda apuntando a un parche inexistente. El andamio ya no sostenia nada: el trabajo de la isla dinamica vive en incoming-gnome, que es la cola que el perfil usa. Sale de QUEUES en el mismo commit, por la simetrica de la regla que ese fichero documenta. Gate --check OK, grafo CIERRA, escritorio-gnome sigue 119/119, corpus 786/787. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
cf42cd9049 |
gnome onda-1: jubilar la cola entera — molia para nadie
Cierra la jubilacion empezada con gnome-desktop. Las 5 recetas que quedaban: - glib, glib-introspected, gobject-introspection, py3-setuptools: byte a byte identicas a las de incoming-gnome Y con el MISMO ArtifactHash, o sea que sellaban en la misma direccion y entraban por cache-hit. Coste de build cero; coste real: un segundo sitio donde editar, que se separa en silencio. - gsettings-desktop-schemas: la unica distinta, y es la version ANTERIOR al 2026-07-28 (introspection=false + link=static). La vigente lo activo porque el gir de Meta de mutter incluye GDesktopEnums-3.0.gir, que sale de ahi, y sin el el g-ir-scanner corta en el ultimo target de mutter (721/722). Lo caro no era el tiempo sino el NOMBRE: el gsettings viejo competia con el bueno, dos artefactos homonimos con hash distinto en el store. Podado el huerfano (0fb1703d, 155 KiB exclusivos) y anotado en el ledger anti-churn; los otros cuatro artefactos siguen vigentes porque los produce incoming-gnome. Y al jubilar gnome-desktop las dos hojas de la cola se habian quedado sin consumidor: la cola se alimentaba a si misma y terminaba en el aire. Sale tambien de QUEUES en farm-worker-loop.sh, por la simetrica de la regla que ese fichero ya documenta dos veces: una cola ausente de la lista no da error, da silencio — y una cola retirada que sigue en la lista, tambien. Gate --check OK, grafo CIERRA, escritorio-gnome sigue 119/119, corpus 786/787. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
9591e69b70 |
harkaq: el salto del check de arquitectura ya no se apoya en UB (SDD 25 T17)
`f[k++] = BPF_JUMP(..., I_KILL - k - 1 + 1)` leía y modificaba `k` en la misma expresión, sin punto de secuencia: UB en C11. Andaba porque gcc y clang leen `k` después del incremento y ese `+1` era la compensación exacta, pero si algún compilador leyera antes el salto caería una instrucción más allá del final. El índice se fija ahora en `I_ARCH` antes de usarlo. El código generado es IDÉNTICO —mismo objdump del objeto a -O1 y a -O2, la única línea que difiere es el nombre del fichero—, que es lo que prueba que es la misma cuenta escrita sin UB y no un arreglo que además mueve el salto. Comprobado además que la jaula sigue puesta: ptrace desde dentro da EPERM y un ejecutable fuera de la clausura no arranca. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
e5d56a4168 |
grafo de estado: un directorio VACIO se contaba como sellado
`state_of` decidia `sealed` con `os.path.isdir`, o sea por presencia del nombre y no del contenido. Hoy se vio en vivo: una cosecha que murio a mitad dejo `...-mise` sin un solo fichero y el grafo lo conto sellado — misma cifra, misma linea, indistinguible de la corrida buena. Es el eslabon que describe la regla 3 del CLAUDE.md: `Store::has` ya rechaza los vacios, pero cada consumidor que mire `isdir` los vuelve a leer como presencia. Un ausente falla ruidosamente; un vacio llega hasta el final diciendo que todo fue bien. Los cinco grafos salen identicos tras el cambio (no hay vacios en el store ahora mismo), asi que corrige el criterio sin mover ningun numero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
6558b7c8de |
granja: la cosecha llenaba el disco — rsync sin -H y bajando .dmerge
La bajada del store murio con "No space left on device" con 95 G libres y un store remoto de 25 G. Dos causas que se multiplican: - `rsync -a` NO preserva hardlinks (eso es `-H`). El store es CAS y .dmerge hardlinkea los artefactos, asi que los 25 G que mide `du` en el origen -que deduplica por inodo- se escriben en destino como copias enteras, una por enlace. - `.dmerge` es cache pura: hammer la reconstruye sola, no aporta un artefacto y es la parte mas pesada del arbol (132 G en el hub, 111,7 GiB exclusivos). Ademas el fallo a mitad deja directorios creados sin ficheros, que es un cache-hit envenenado: `mise` se cosecho asi, como nombre vacio. Se anade un guardian que barre los vacios al terminar la bajada y avisa de re-correr. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
f2b289e6a9 |
harkaq: un portón de compilación para el techo de la denylist (SDD 25 T17)
`jt`/`jf` de la BPF clásica son __u8 y en la forma de poner_seccomp() el salto más largo mide n+3: pasadas ~252 entradas el desplazamiento da la vuelta y el filtro deja de decir lo que dice el fichero, sin error y sin aviso. Hoy son 25 y sobra sitio — el riesgo es que agregar syscalls a una denylist es exactamente lo que uno hace sin pensarlo. _Static_assert, o sea cero código generado: comprobado en las dos direcciones —baja el techo a 10 y el compilador para; y la sección .text del binario es byte a byte la misma con y sin el assert—, que es el requisito de este fichero (nada que re-hashee). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
33e02bdfbc |
poda de fuentes: 70 G recuperados y un vigia para que no vuelvan
El volumen del store estaba al 97% (8,8 G libres) con la granja escribiendo ahi, y el gordo NO era el store: eran 79 G de `work/sources` — 106 arboles con el `target/` de cargo y los `.o` dentro, porque el arbol de fuentes es tambien el directorio de compilacion. `pixi` pesaba 3,5 G y siete apps cosmic pasaban de 3 G cada una. Nadie podaba eso: store-gc mira el store, la poda de CI mira la cache de CI, y este tercer monton crecia sin dueno desde que el store se mudo al volumen. Borrarlo no cuesta nada, y eso es lo que lo hace seguro: los dos caminos de fetch.rs (fetch_git:73 y fetch_tarball:234) hacen `remove_dir_all` del arbol y lo re-extraen en CADA build, sin una sola rama que reutilice. Un arbol viejo no acelera nada; la re-extraccion ocurre igual. Lo unico que se pierde es el post-mortem del ultimo build, de ahi el suelo de 24 h. El script toma `work/.farm-build.lock` porque el unico dano posible es borrar el arbol que un bwrap esta compilando (ADR 0012 en su forma mas directa), y el mtime del directorio raiz no distingue "viejo" de "build lento". Con espera acotada: si hay algo en vuelo salta el ciclo. Suelo en HORAS y no en dias por la leccion de cache-ci-no-envejece: aquel cron podaba a +10 dias sobre datos de horas y liberaba cero. Los 106 arboles abarcaban 3 dias. Sin umbral por espacio libre: podar solo cerca del borde convierte una poda barata y plana en un pico justo cuando un build puede quedarse sin disco a mitad. Probado en las cuatro ramas: dry-run vacio, dry-run con 15 candidatos, --aplicar sobre un arbol sintetico (borra el viejo, deja los 15 recientes) y lock tomado (salta y sale 0). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
5fd34b07c1 |
cosecha-cron: cablear el audit de enlace estatico, con puerta diaria
Tercer cable de la misma familia que el vigia de fuentes y el contrato del kernel, y por la misma razon exacta: el gate existia desde julio, nadie lo re-corria, y hoy el «MIENTEN: 0» con el que se habia cerrado el frente resulto ser 4 — todas selladas DESPUES del cierre. Un frente que se cierra en cero y no se vuelve a medir no se queda en cero. Lo que costaba ese silencio, medido hoy: `bash` —el shell de los perfiles base, cli y dos escritorios— NO ARRANCABA fuera del sandbox, y las cuatro apps GTK4 del corpus (hammer-edit incluida) segfalteaban en gtk_init. Todo sellado y en verde. PUERTA DIARIA por sello (`work/.static-audit.sello`, gitignoreado): el barrido lee cada ejecutable de los ~750 sellados con file+readelf y tarda ~8 min de CPU en 4 vCPU compartidos con el worker. Cada 30 min seria mas de un cuarto de core para siempre vigilando algo que solo cambia al re-sellar. Con la puerta, 8 min al dia. Forzar: `rm work/.static-audit.sello`. El sello es LIMITADOR DE RITMO, no marca de exito ⇒ se toca en cuanto el barrido termina, salga como salga. Si solo se tocara al acertar, un fallo que consuma los 8 min se repetiria cada 30 y el cable pasaria de vigilancia a sangria. No toma work/.farm-build.lock a proposito (solo LEE el store; retenerlo 8 min pararia la granja) y va con `nice -n 19`: la prioridad la tiene construir. El veredicto lleva FECHA DE MEDICION en la primera linea, y no es decoracion: con el frente en verde el texto del audit es constante, `git diff --cached --quiet` no veria cambio, no se commitearia nada, y en tres meses el fichero seria indistinguible de uno rancio. Con la fecha, cada barrido deja huella en el git log: se ve que el vigia sigue VIVO, no solo que el ultimo veredicto fue bueno. Misma trampa que el build-state-wlr congelado 17 dias. Comprobado en las dos direcciones, que es donde estos cables fallan callados: la puerta abre sin sello / a las 25 h / a los 3 dias y cierra a las 0 h y 23 h; y con un audit que sale !=0 sin imprimir nada, NO pisa el veredicto anterior, toca el sello igual y no deja temporales. Ya corrio en el cron real: la cosecha de 17:01Z commiteo docs/state/static-audit.txt y logueo «sello de hoy, no toca». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
5155f90ab9 |
static-audit: miraba UN binario y capaba a 40 — dos pases en falso probados
Arreglando las tres hojas salio que el guardian tenia dos agujeros, y los dos devuelven un pase en falso, que es la peor direccion posible para un gate. 1. `break` en el PRIMER ELF ejecutable. e2fsprogs: el primero que encontraba era `bin/lsattr` (estatico) ⇒ receta HONESTA, mientras `e2fsck` y los tres `fsck.ext*` eran dinamicos. 4 de 31, invisibles. 2. `find | head -40`. bash trae 42 ejecutables y CUARENTA son los modulos cargables `usr/lib/bash/*`, que son ELF *shared object* y no matchean `ELF.*executable`. El cap cortaba la lista ANTES de llegar a `bin/bash` ⇒ el audit concluia «sin ELF» y eso en el resumen se lee como que no hay nada que objetar. El shell del perfil base, dinamico, sin que nadie lo viera. Con dwarves el `break` habia acertado de casualidad —el primero tambien era dinamico— y por eso reportaba 1 donde habia 10. Ahora recorre TODOS los ejecutables, sin cap, cuenta cuantos mienten sobre cuantos hay, y nombra un ejemplo: «gtk4 dice static, 8 de 8 ejecutables DINAMICOS (p.ej. usr/bin/gtk4-rendernode-tool)». Barrido completo con el metodo estricto: 684 estaticos de verdad, MIENTEN 3 (gtk4, bash, e2fsprogs), 59 sin artefacto. Con el metodo viejo, sobre el mismo store, salia 685/1/60. La diferencia son los dos que se escapaban. Es «no comprobar no es aprobar» aplicado al propio guardian — el mismo fallo que `hammer kernel contract` ya habia tenido que corregir en SDD 25 §4.ter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
588fedebcb |
granja: freno al crashloop del worker — 30 h de OOM y reinicio en bucle
`Restart=always` + `RestartSec=30` sin límite: si una receta no entra en la RAM de la caja, el OOM killer la mata, systemd relanza a los 30 s, el loop recorre la cola por glob alfabético y vuelve a la MISMA receta. En el LXC dev.gioser.net eso duró 30 horas con `clang18`, y dejó la máquina con presión de I/O `full avg300=43` —todo bloqueado en disco casi la mitad del tiempo— y sshd incapaz de completar el banner. Diagnosticarla desde fuera era imposible: parecía red o disco. El único rastro eran dos `oom-kill` en el journal, que sólo se ven desde dentro. StartLimitBurst=3 / StartLimitIntervalSec=3600: a los 3 arranques en una hora systemd se rinde y deja la unidad en `failed`, VISIBLE en `systemctl status`. OOMPolicy=stop: un OOM no es un fallo transitorio, si no entra ahora no entra en 30 segundos. Rendirse no pierde nada: el hub ya tolera workers ausentes —cosecha-cron dice "siembra falló" y sigue con el siguiente— y un worker parado y diagnosticable vale más que uno que se reinicia para siempre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
19b5d51dfe |
latido: el contrato del kernel se comprueba solo en cada cosecha
El gate de SDD 25 §4.ter existía y NADIE lo corría. Un kernel reconstruido sin MEMCG volvería a pasar inadvertido — y ese fallo no se ve del lado del desarrollo, porque el kernel de la máquina de trabajo sí trae MEMCG: sólo se ve comprobando el artefacto. Mismo cable que el vigía de fuentes y por la misma razón que está escrita ahí arriba: lo que nadie refresca envejece hacia el optimismo. Ahora cada ciclo deja el veredicto en docs/state/kernel-contract.txt y el git log lo muestra como el resto del khipu. El fichero se escribe por temporal y sólo se mueve si tiene contenido: contract sale != 0 cuando el contrato no se cumple —y ese rojo es justo lo que hay que guardar—, pero un fichero vacío se leería como «nada que objetar». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih |
||
|
|
dcf80a17a6 |
vps-setup: patch faltaba en la rama Fedora — tumbaba el stack GUI de GNOME
En Debian `patch` entra de regalo dentro de `build-essential`; al desglosar la rama dnf a gcc/g++/make sueltos se cayó sin que nadie lo notara. El campo `patches = [...]` de una receta lo aplica hammer DEL LADO DEL HOST, fuera del sandbox, así que no alcanza con que el rootfs lo traiga —lo trae, `/usr/bin/patch` está en el alpine de los dos labs— y por eso el diagnóstico "rootfs laptop ≠ worker" no aplicaba acá. Medido en dev.gioser.net: `cairo-shared` moría con `spawn patch: No such file or directory` y detrás caían `pango` y `gtk4`. El stack GUI entero de GNOME por un binario de 129 KB ausente en el host. Regla que queda escrita: toda herramienta que hammer invoque HOST-SIDE va en esta lista, no en `[deps]` de la receta — declararla en la receta re-hashearía cairo y todo GNOME debajo, para arreglar algo que no es de la receta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |
||
|
|
b3cc18018c |
granja: incoming-cosmic e incoming-wlr faltaban en QUEUES del worker
Es el mismo fósil que el propio script documenta para GNOME, repetido con las colas que nacieron después. Las 25 recetas en deuda de COSMIC no fallaban: nadie las intentaba, porque su cola no estaba en la lista. Una cola ausente no da error, da silencio — el mismo modo de fallo que "el corpus no está en las QUEUES del worker". Se añade la regla en el comentario: cola nueva y línea QUEUES, en el mismo commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4 |