main
319
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
889720cac0 |
granja: golden NUEVA con Rust y Go — desbloquea el 76% del catálogo
Imagen 417847948 («hammer-golden-rust-go-2026-08-08») reemplaza a 408909310 como default de farm-up. EL PROBLEMA QUE CIERRA: hammer invoca `cargo vendor` y el toolchain Go en el HOST, no dentro del sandbox — el vendoreo pasa ANTES de entrar a la caja. La golden vieja no los traía, así que la granja no podía construir NINGUNA receta Rust ni Go: COSMIC entero, las 228 Rust y las 362 Go del corpus. El 76% del catálogo dependía de UNA SOLA máquina, el hub — justo lo que el respaldo de ayer intentaba dejar de asumir. VERIFICADO ANTES DE SNAPSHOTEAR, no después: `cosmic-bg` —que minutos antes moría con `spawn cargo vendor: No such file`— sella en el worker. Un snapshot de una máquina a medio arreglar es peor que ninguno. MISMAS VERSIONES QUE EL HUB (cargo/rustc 1.96.0, go 1.26.4) a propósito: introducir un skew de toolchain nuevo mientras se arregla otro es cómo se fabrican los fallos que nadie reproduce. Y 1.96 es además el techo MSRV del sandbox ya documentado. NO RELAJA LA HERMETICIDAD: cargo/go son herramientas de FETCH, del mismo orden que `git` para clonar. El build sigue ocurriendo dentro del sandbox con el árbol ya vendoreado. Es lo contrario del caso «no engordar el rootfs del worker», que habla del rootfs DEL SANDBOX. El PATH queda persistido en /etc/profile.d/hammer-toolchain.sh para que sobreviva a los logins no interactivos de la granja. Y la imagen trae el store del worker al momento del snapshot (KDE y GNOME reconstruidos hoy), así que los workers nuevos arrancan con más caché. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
040d174bbe |
desplegar-strip: ordenar por IMPACTO EN EL GRAFO, no alfabéticamente
Medido sobre el corpus: **686 de 779 recetas son HOJAS** (cero dependientes transitivos) ⇒ el 88% se puede desplegar sin provocar UNA SOLA reconstrucción extra. Las caras son pocas y conocidas: make 416 · pkgconf 360 · go 359 · binutils 343 · zlib 315 · python3 287 · samurai 272 · meson 257. El orden alfabético anterior era activamente malo: la primera tanda de la etapa 4 tocó expat, zstd y ncurses —tres bibliotecas base— y dejó 54 recetas sin artefacto vigente, incluida la cadena wlroots/sway sellada la noche anterior. Sin este orden, cada tanda pequeña provoca una cascada grande y la campaña avanza hacia atrás. Hojas primero; las ~93 con dependientes, en una ola coordinada y con la granja arriba. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b38db3f35d |
respaldo: .dmerge tumbó la subida 40 veces seguidas y el mensaje nunca lo dijo
El respaldo se detuvo en 79 G de 127 G con «40 intentos y sigue cayéndose». El código era 23 («some files/attrs were not transferred»), que está en la lista de reintentables — así que el bucle lo reintentó cuarenta veces contra la misma pared y se rindió. LA CAUSA, al mirar los errores de verdad en vez del código de salida: todos eran rutas `/home/hammer/store/.dmerge/...`. Son directorios TRANSITORIOS de fusión del store, que aparecen y desaparecen mientras hammer sella. rsync los empieza a copiar, se esfuman a media transferencia, y falla. `cosecha-cron.sh` ya los excluía; este script no, y ésa era toda la diferencia. ⇒ Añadido `--exclude /.dmerge`. Y una lección para el propio bucle de reintentos: **un error reintentable que se repite 40 veces no es un corte de red, es algo estructural**. Cuarenta reintentos idénticos deberían haber gritado «esto no se arregla esperando» en vez de agotarse en silencio. Queda anotado; el bucle todavía no distingue «se cayó una vez» de «falla siempre igual». De paso, `appstream` —la única de las cinco del split que se perdió al borrarse el worker— se reconstruyó en el hub: 18 M, 0 secciones .debug_. Las cinco quedan consistentes (hash vigente con artefacto presente): bison 3M · appstream 18M · zstd 2M · expat 1M · ncurses 2M. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
102aabd1ff |
SEGUNDA corrección: lo que medía el test era DERIVA, no no-determinismo — el corpus SÍ reproduce
El §1.bis dijo «sí hay no-determinismo» porque appstream y bison divergían. También estaba mal,
y por un fallo de diseño del propio test.
`verificar-repro.sh` compara el artefacto GUARDADO contra una reconstrucción de hoy. Pero el
guardado puede tener meses: se construyó con OTRO estado del lab. El test conflaba dos cosas:
· NO-DETERMINISMO — mismas entradas, mismo lab, salidas distintas (rompe el invariante);
· DERIVA — el artefacto viejo no es lo que el lab de hoy produce (el mundo se movió).
LA PRUEBA QUE LAS SEPARA es construir DOS VECES HOY, y se dio sin querer: al reejecutar el
verificador sobre recetas ya reconstruidas, TODAS pasaron a reproducir.
anew ✗ diverge → ✓ REPRODUCE
gron ✗ diverge → ✓ REPRODUCE
age ✗ diverge → ✓ REPRODUCE (y en la 1ª ni instalaba los mismos ficheros: faltaba age-inspect)
⇒ EL CORPUS ES DETERMINISTA HOY. Lo que hay es deriva contra artefactos viejos.
QUÉ SIGNIFICA, sin adornos:
· El argumento de reproducibilidad para el split de debug SE CAE. El split se justifica por
ESPACIO (~60%), que sigue siendo real y grande. Nada más.
· Pero la DERIVA es un problema por derecho propio y mayor: el store contiene artefactos que el
lab de hoy no reproduciría, así que «nuestros artefactos son verificables por terceros» es
falso para parte del corpus — quien reconstruya no obtendrá lo publicado. `age` es el caso
feo: la reconstrucción ni siquiera instala los mismos ficheros.
· ⇒ La reconstrucción masiva SIGUE valiendo la pena, pero por otra razón: no para arreglar el
determinismo sino para PONER EL STORE AL DÍA CON EL LAB y que la promesa sea cierta.
Y otro hallazgo del mismo experimento: en el hub las recetas Go SÍ reconstruyen (0 fallos). Lo
que fallaba en el worker era la RED para bajar los módulos, no las recetas. Eso cambia el
bloqueante de la etapa 4: no es «Go no reconstruye», es «el worker no tiene red para módulos».
LA LECCIÓN, que es la misma tres veces en este documento: un test hay que diseñarlo contra la
PREGUNTA, no contra lo que es fácil de comparar. Comparar con lo que hay en el store es cómodo;
comparar dos builds de hoy es lo que contesta. El script queda anotado con esto en la cabecera.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
b344c5803b |
etapa 4: primera tanda desplegada y verificada (3/3) + el desplegador por tandas
zstd 12M→2M (−83%) · ncurses 6M→2M (−67%) · expat 2M→1M (−50%). Cero ficheros vacíos y **las tres REPRODUCEN**. La maquinaria de la etapa 4 queda validada de punta a punta: activar → construir en la granja → verificar con why-differs. LA DEP DE binutils VA EXPLÍCITA, y es la decisión de diseño de esta etapa. El paso de strip usa `strip --strip-debug -D` de binutils. Se podría hacer que el lab lo materialice solo, sin tocar las recetas — pero entonces la VERSIÓN de binutils sería un input INVISIBLE: dos corridas con binutils distintos darían artefactos distintos con el mismo hash. Los `deps` sí entran en `hash_inputs`, así que declararlo es lo único que mantiene el invariante. Cuesta una edición mecánica por receta; el invariante no se negocia por comodidad. EXCLUSIONES, y son exactamente dos: `binutils` y `make`. binutils provee el strip y depende de make ⇒ activarles el split los haría necesitarse a sí mismos para construirse. No es preferencia, es la circularidad. (Las recetas CERRADAS POR DECISIÓN tampoco se tocan.) POR TANDAS Y NO DE GOLPE: activar re-hashea la receta a propósito y en cascada todo lo que dependa de ella. Hacerlo sobre las 775 candidatas a la vez dejaría el corpus entero sin sellar al mismo tiempo — días de granja antes de poder verificar NADA, y el disco aguantando artefactos viejos y nuevos a la vez. Por tandas se mide, se verifica y se poda entre medias, que es lo que hace la campaña reversible. La primera tanda NO se tomó del orden alfabético que propone el script: ésas son CLIs Go/Rust que ya vimos que no reconstruyen en el worker (necesitan red para sus módulos), y validar la maquinaria con recetas que fallan por otro motivo no habría probado nada. Se eligieron tres paquetes C con artefacto presente. Para las tandas grandes hay que resolver antes el acceso a red de los módulos Go/Rust, o restringirse a lo que reconstruye. Estado: 775 candidatas, 5 desplegadas (bison y appstream del piloto + estas 3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1eb469337d |
etapa 3: el ahorro real es ~60%, no 79% — la cifra publicada medía otra cosa
El «79%» que este plan y los SDD 19/20 venían citando era **el 79% del CONTENIDO BINARIO**,
medido sobre una muestra de .so/.a. Es cierto y es la cifra equivocada para planificar, porque un
artefacto no es sólo binarios: trae cabeceras, datos, iconos, locales, .pc y documentación, que
no encogen.
MEDIDO con `scripts/medir-debug.sh` (suma los tamaños reales de las secciones .debug_* vía
readelf -S y los compara con el tamaño del árbol, sin reconstruir nada):
40 artefactos · 876 MB · 27%
100 artefactos · 2955 MB · 38% (truncada a 60 ficheros/artefacto)
250 artefactos · 8874 MB · 60% ← la que manda: sin truncar, ~7% del store
La varianza es alta porque unos pocos artefactos grandes dominan el total, así que el número
necesitaba validación independiente — y la tiene: el piloto de la etapa 2, donde se reconstruyó y
se pesó de verdad, dio bison −50% y appstream −68%. Los dos ENCIERRAN el 60% de la muestra
grande. El modelo predice lo que el rebuild produjo.
CIFRAS CORREGIDAS, con el store en 127 G:
se venía diciendo medido
reserva de disco ~96 G ~76 G
store tras el split ~30 G ~51 G
espejo público ~30 G ~51 G
Sigue siendo el mayor ahorro disponible en el proyecto —76 G no es poco— pero no es lo
prometido, y la diferencia importa para dimensionar el alojamiento del espejo, que es una
decisión con factura.
LA LECCIÓN: una cifra medida sobre una cosa (contenido binario) se citó durante semanas como si
midiera otra (tamaño de artefacto), y llegó a tres documentos. El número no era falso; LA UNIDAD
sí. Cuando un número vaya a decidir un gasto, hay que volver a la medición original y comprobar
QUÉ estaba midiendo.
Corregido en los tres sitios donde se había propagado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
eea1c02f43 |
etapa 1: la puerta funcionó — me equivoqué, SÍ hay no-determinismo (y las dos mitades son una)
Ayer escribí en el SDD 23 que el `-ffile-prefix-map` global «se cancela porque su premisa es
falsa». **Era falso**, y la etapa 1 lo demostró en veinte minutos. Corregido en el §1.bis.
LA MEDIDA: `scripts/verificar-repro.sh` aparta el artefacto, reconstruye con las deps cacheadas
y compara con `why-differs`. Sobre las que pudieron reconstruirse: binutils ✓ y wl-clipboard ✓
reproducen; **appstream ✗ y bison ✗ DIVERGEN**. Causa idéntica en ambas:
difieren [.debug_aranges, .debug_info, .debug_pubnames, .debug_pubtypes, .debug_str]
— sólo info de depuración (el código ejecutable es idéntico)
· .debug_str sólo en B: /src/output/meson-private
DÓNDE ME EQUIVOQUÉ, exactamente: mi barrido buscaba rutas DEL HOST (/home/<user>/,
/tmp/<aleatorio>) y no halló ninguna nuestra — cierto. Pero el no-determinismo no venía de una
ruta del host sino de rutas INTERNAS al árbol de build (/src/output/meson-private) que varían
entre corridas aunque /src sea constante. Buscar la forma equivocada de ruta y no encontrarla no
prueba que no haya otra. Y una sola muestra que reproduce no es una muestra: leí `wl-clipboard`
como si probara más de lo que probaba, que es el error contra el que el propio documento
advertía dos párrafos antes.
Lo que me salvó fue haber puesto la PUERTA antes de la campaña en vez de después. Si hubiera
seguido mi conclusión, habría cerrado como «resuelto» un invariante roto.
🎁 Y de ahí sale la mejor noticia del plan: LAS DOS MITADES SON EL MISMO TRABAJO. La divergencia
vive ENTERA en secciones .debug_* y el código ejecutable es idéntico ⇒ separar el debug del
artefacto principal hace que el artefacto principal REPRODUZCA, sin tocar -ffile-prefix-map en
720 recetas. Queda decidir qué hacer con el contenido del paquete -debug, pero eso afecta a un
artefacto secundario que nadie instala por defecto.
El script queda como herramienta: muestra por clase de build (C, Rust, Go: el no-determinismo
suele vivir en codegen paralelo y orden de símbolos, no sólo en C), y RESTAURA el artefacto si
el rebuild falla — distinguir «no reproduce» de «no construye acá» importa, porque mezclarlos
inventaría un problema de determinismo que no existe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d70098ade5 |
GTK3: decisión del usuario — NO entra; gnome-session, gsd y gdm quedan CERRADAS por escrito
Decidido el 2026-08-08. Las tres recetas dependen de GTK3 (gnome-settings-daemon además de gtk+-x11-3.0) y quedan cerradas, **no «en deuda»**. La diferencia no es semántica: una deuda invita a reintentarla en cada informe y en cada ciclo de granja; una decisión se respeta. POR QUÉ ES RAZONABLE Y NO UNA RENDICIÓN: · GNOME funciona sin esto — el camino vivo es mutter → gnome-shell arrancado por arje, y ninguno de los dos depende de gnome-session (su cierre es mutter+gjs+gobject-introspection+ gnome-desktop). · Traer GTK3 sería una torre de C muerta —y para waybar además gtkmm, sus bindings de C++— por un gestor de sesión y un login manager que este escritorio no necesita. · La función está cubierta: la sesión la levanta arje y la barra de estado es `yambar`, C puro, sellado anoche en el frente wlr. Se reabre si alguien trae GTK3 por otro motivo con peso propio (una app gráfica que lo exija). EL MARCADOR VIVE EN LA RECETA, NO EN UNA LISTA APARTE. `build-farm.sh` salta las recetas cuya cabecera dice «CERRADA POR DECISIÓN». Podría haber hecho un fichero de exclusiones, pero una lista y una cabecera se desincronizan solas: así, quien lee el porqué y quien decide saltarla miran EL MISMO TEXTO. Y el drenador lo dice en su salida, para que la decisión sea visible en vez de silenciosa. Con esto la granja deja de quemar CPU en tres recetas que nadie va a arreglar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e3403ff20e |
sway ARRANCA EN QEMU con pantalla — evidencia, y seis muros que el grafo no podía ver
`docs/evidencia/sway-qemu-2026-08-08.png`: 1280×800, **102 colores distintos**, sway con foot a pantalla completa y un prompt vivo, la barra de título del compositor arriba y el fondo #1a4b8c asomando en los bordes. Sobre el kernel de hammer con arje-zero como PID1, sin X11 y sin systemd. EL VEREDICTO ES CONTAR COLORES, NO LEER EL LOG — y esta campaña lo demuestra sola: hubo un arranque con TODO verde en el log (salida activada, modo 1280x800, «Commit of 1 outputs succeeded», workspace creado) y la captura salía **100% negra**. Un compositor puede estar perfectamente vivo y no pintar nada. LOS SEIS MUROS, ninguno visible en un grafo de dependencias que decía 121/121: 1. `PATH` vacío — el console-getty no lo exporta y ni `mkdir` se encontraba. «mkdir: not found» se lee como «falta busybox» cuando busybox está entero: mismo engaño que el exit 127 de meson. 2. `libz.so.1` AUSENTE del rootfs. El zlib del corpus es sólo estático; la compartida vive en `zlib-shared`, en otra cola. Se cazó comparando los NEEDED del ELF contra el rootfs, no leyendo logs. 3. `LIBSEAT_BACKEND=builtin` NO EXISTE en nuestro libseat: `recipes/seatd.toml` construye con `-Dlibseat-seatd=enabled -Dlibseat-logind=disabled`, o sea UN backend, confirmado con `strings`. La receta manda sobre lo que uno cree recordar del proyecto — y la misma receta traía la salida: `-Dserver=enabled` construye `seatd-launch`. 4. `/run` de SÓLO LECTURA. La raíz de hammer es inmutable por diseño, y sway moría con «unable to open lockfile … check permissions», que suena a permisos de directorio y era un filesystem read-only. Se resolvió montando un tmpfs, que es lo que hace cualquier init. 5. `xkeyboard-config` — «failed to add default include path /usr/share/X11/xkb». Es EXACTAMENTE lo que dejé anotado en la receta de swaylock: «para que el teclado tenga distribución en una imagen real hará falta incluirlo por el lado del perfil». Apareció donde se dijo. 6. SIN TIPOGRAFÍAS no hay terminal. `fcft: failed to match font` → `failed to load primary fonts`: foot arrancaba y no dibujaba. `dejavu-fonts` estaba en el catálogo pero en otra cola. Va en la lista de §I.5 del SDD 20 («tipografías») y acá se ve por qué no es un detalle. Y un fallo de MÉTODO que costó dos ciclos: mandé el log de sway a /var/log/sway.log DENTRO de la VM, así que la primera captura negra vino sin una sola línea que la explicara — el diagnóstico estaba dentro de la caja que no arranca. Un log que no se puede leer desde fuera no es un log. Ahora va al serial. Los `exec` de la config de sway disparan ANTES de que exista el workspace (0,259 s vs 0,305 s) y mueren con «Failed to create launch context. No workspace». Los clientes se lanzan desde sway-start esperando el socket de Wayland, que es la condición real. Andamiaje reutilizable en scripts/wlr/: qemu-sway-image.sh (hermano del de COSMIC, sin logind ni dbus, que sway no necesita), sway-start.sh y sway-config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
55dd36d97a |
perfil escritorio-sway: 121/121 CERRADO — la cuarta imagen de escritorio, y la más barata
Declarado en targets.toml y verificado con el grafo: **121 nodos, 121 sellados, falta 0**. Es una imagen construible hoy, no una intención. Nueve raíces (sway, yambar, fuzzel, swaybg, swaylock, swayidle, grim, slurp, wl-clipboard) más `hereda = ["cli"]`, porque un WM sin userland debajo no se usa: hacen falta la terminal y las herramientas. `foot` no se lista porque ya entra por la clausura de `cli`. El resto de la imagen —wlroots, mesa, wayland, libinput, seatd, cairo, pango, fcft…— lo calcula el grafo siguiendo las aristas: escribir la clausura a mano es justo lo que targets.toml existe para evitar. LA COMPARACIÓN QUE VALE: KDE y GNOME fueron campañas de semanas porque debajo tienen una torre de C (Qt entero, GTK, la cascada de mesa). Esto es una decena de binarios pequeños sobre wlroots y salió en una noche. Ya estaba escrito en el SDD 20 como hipótesis —«los WMs ligeros son la mejor relación esfuerzo/resultado que queda»—; ahora está medido. `--wlr` en build-state.py, con su flag propio por la misma razón que COSMIC: sin él el frente es INVISIBLE para el khipu, y `yupana radio` sobre una receta compartida (wayland, libinput, pixman, mesa, libxkbcommon…) no reportaría la imagen sway entre las afectadas — o sea que el próximo que toque una de ésas mediría de menos y creería que no rompe nada. Queda lo que no puede decidir el grafo: probarlo en QEMU CON PANTALLA. La regla ya pagada cara es que las imágenes de escritorio no se validan con `-nographic`, porque el compositor puede «arrancar» en los logs y no pintar nada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
39b88ebd2a |
churn del store: eran CUATRO rutas de cosecha, no una — arreglé una y di el bucle por cerrado
Podé el store por tercera vez hoy: 363 artefactos superados, 24 G. Comparado con la poda de la tarde: **362 en común**. Volvieron otra vez, pese al arreglo de esta tarde. LA CAUSA de que el arreglo no sirviera: puse el `--exclude-from` del registro de podados sólo en `farm-sync.sh`, y el latido NO usa ese script. `cosecha-cron.sh` tiene su PROPIO rsync del store (línea 71), y corre cada 30 minutos por cron ⇒ una poda de 24 G quedaba deshecha en menos de una hora. Los tres commits de «cosecha granja» de la madrugada son exactamente eso. Y al ir a arreglarlo, en vez de parchear y seguir, busqué si había más: **son CUATRO** las rutas que bajan el store del worker — `cosecha-cron.sh`, `farm-sync.sh`, `harvest-go.sh` y `farm-down.sh`. Tenía protegida una de cuatro. El error de método vale más que el bug: arreglar una de varias copias y declarar victoria es PEOR que no arreglar, porque el síntoma se atenúa lo justo para dejar de mirar. Lo que lo delató fue comparar los manifiestos de dos podas (`comm -12`), no leer código. Dos podas con el mismo número no son coincidencia — ya está anotado como regla, y hoy la regla pagó dos veces. Las cuatro llevan ahora el mismo `--exclude-from` y una nota que dice que son cuatro, para que la quinta —si aparece— nazca protegida. Disco: 112 G → 136 G tras la poda de ahora. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
59c4973bd9 |
granja: el filtro de «ya sellado» miraba el nombre equivocado — lo arreglo antes de que cueste
El bloque que añadí hace un rato usaba `basename $f .toml` como nombre del artefacto. **Está
mal**: el artefacto en el store se llama por el campo `name` de la RECETA, no por el fichero,
y en 17 recetas del catálogo esos dos no coinciden. El arquetipo es
`recipes/incoming-kde/qtbase.toml`, cuyo `name` es `qt6-qtbase` ⇒ el artefacto es
`<hash>-qt6-qtbase` y mi `grep` lo buscaba como `<hash>-qtbase`.
Efecto: las 17 salían «no selladas» y el worker las habría reconstruido — exactamente el
desperdicio que este filtro existe para evitar. Se vio porque los 10 «pendientes» de
incoming-kde eran todos módulos Qt, que es demasiada coincidencia para ser casualidad; el
hub tenía el artefacto con el MISMO hash, sólo que bajo otro nombre.
Con el filtro correcto, la foto de la granja es exacta por primera vez:
· incoming-kde 205 recetas · 205 en el hub ⇒ **0 pendientes**
· incoming-gnome 90 recetas · 87 en el hub ⇒ 3 pendientes: gdm, gnome-session y
gnome-settings-daemon, o sea el trío bloqueado por GTK3
· incoming-go / incoming-clib: colas vacías
⇒ **La granja no tiene trabajo construible.** Y ya no lo tenía antes de esto: el worker no
posee ni UN artefacto que el hub no tenga (comparadas las dos listas: 0 exclusivos suyos),
así que sus últimas 22 horas produjeron cero valor neto. Lo que queda por hacer no es
construir sino AUTORAR recetas (wlroots y los WM ligeros, cups, bluez, las gráficas de
terceros), y eso es trabajo de hub.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
129aff9dd9 |
granja: el worker rehacía 87 de 90 recetas por ciclo — el sync del store es de UN SOLO SENTIDO
Fui a mirar los fallos de la granja y el número no cuadraba: de 20 «fallos» de incoming-gnome, 17 correspondían a recetas CON ARTEFACTO VIGENTE en el hub. Primero pensé que eran marcadores `.fail` rancios, y me equivoqué: `build-farm.sh` hace `rm -rf "$FARM"` al empezar cada ciclo, así que el status es siempre del ciclo actual. Después pensé que el worker corría recetas viejas, y también me equivoqué: comparados los md5, hub y worker tienen copias IDÉNTICAS. LA CAUSA REAL está en `farm-sync.sh`: sube el código EXCLUYENDO /store y baja el store del worker. El store viaja worker→hub y nunca hub→worker. O sea que **el worker no se entera de nada de lo que se sella en el hub**, y cada ciclo reintenta —y vuelve a fallar— trabajo ya hecho. Medido: de las 90 recetas de incoming-gnome, **87 ya están selladas en el hub**. El worker estaba quemando el 97% de esa cola en repetir lo hecho. EL ARREGLO no es mandarle los artefactos (gigabytes por un enlace de 8 Mbps) sino la LISTA: `work/farm-sellados.txt` son los nombres `<hash>-<paquete>` del store del hub, unos kilobytes. `farm-sync.sh` la regenera en cada sync (para que no envejezca sola) y `build-farm.sh` salta la receta cuyo hash vigente ya esté ahí. Ojo al detalle de rsync: la subida excluye /work, así que el manifiesto necesita un `--include` ANTES del `--exclude` — rsync aplica la primera regla que casa, y sin ese orden el fichero no viajaba y el arreglo no habría hecho nada en silencio. Y LAS TRES DEUDAS REALES DE LA GRANJA SON UNA. gnome-session, gnome-settings-daemon y gdm mueren todas en GTK3, que el frente GNOME aparcó a propósito. Verificado contra el meson.build de cada tag: gnome-session 48.0 lo pide incondicional; gnome-settings-daemon 48.1 pide gtk+-3.0 Y gtk+-x11-3.0 (dos de las tres deudas aparcadas, no una); gdm 48.0 lo tiene condicionado a `if have_xdmcp` pero da igual, porque muere construyendo gnome-session. De paso, gnome-session pasaba `-Dsystemd=false -Dsystemd_journal=false`, dos opciones que NO EXISTEN en 48.0 (sus opciones reales son seis: deprecation_flags, session_selector, systemduserunitdir, docbook, man, x11). Meson aborta en la primera opción desconocida, así que la receta ni llegaba a configurar y el fallo real quedaba tapado. Corregido, más -Dx11=false. El diagnóstico de fondo YA ESTABA en la receta desde el 2026-07-27 y era más completo que el mío (dice GTK3 **y** libsystemd); quité la nota duplicada que había añadido. Y en el respaldo: la comprobación inicial de SSH era de un solo intento y tiró la corrida al relanzarlo llegando a casa, con el wifi aún sin levantar. Es el peor momento para rendirse — quien relanza un respaldo interrumpido acaba de cambiar de red. Ahora reintenta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2c2ee951d6 |
licencias: resolver -only vs -or-later CON LA CITA — y tres «pruebas» que no probaban nada
Los identificadores obsoletos (`GPL-2.0`, `LGPL-2.1`, `AGPL-3.0`) no dicen si el proyecto
concede «sólo esta versión» o «ésta o cualquier posterior», y eso decide con qué se puede
combinar el paquete y bajo qué términos puede redistribuirlo quien lo reciba.
NO SE PUEDE MIRAR EL COPYING, que es la trampa evidente: el texto de la GPL es IDÉNTICO en
los dos casos —es la licencia, no la concesión— y encima su apéndice «cómo aplicar la
licencia» contiene literalmente «or (at your option) any later version», así que buscarla
ahí da SIEMPRE positivo y parece evidencia siendo plantilla. Misma trampa que la regla del
`.a` no-PIC, donde grep contaba reubicaciones de `.debug_*`. La concesión vive en las
cabeceras de los fuentes y en el README; ahí se busca, excluyendo los ficheros de licencia.
SE GUARDA LA CITA, no sólo el veredicto: fichero + frase exacta que decidió. Una licencia es
una afirmación legal y quien la revise tiene que poder ver POR QUÉ dice lo que dice sin
repetir el trabajo.
Y eso pagó de inmediato: de las 12 primeras resoluciones, TRES eran evidencia inválida y
sólo se vieron porque estaba la cita. Cada una de una clase distinta:
· caligula — la frase salía de `checks/headless/expected.iso`, un FIXTURE DE TEST;
· libnl — de `include/linux-private/linux/seg6.h`, una cabecera del KERNEL vendorizada.
La licencia del kernel no es la de libnl;
· lm-sensors — declarado GPL-2.0 y la cita decía «version 2.1 of the License», que es la
LGPL. La frase era real pero no sostenía lo que se le atribuía.
Las tres clases quedan filtradas en el script: ficheros de licencia, rutas de test/fixture, y
código vendorizado; más una comprobación nueva de que **la cita hable de la misma versión que
la licencia declarada**.
Sembradas las 9 auditadas (13 ficheros; algunas viven en varias colas). Ambiguas: 55 → 41.
Las 40 sin evidencia quedan listadas y siguen contadas — la mayoría son Go y la familia
cosmic, donde la frase no aparece fuera del COPYING.
Hashes verificados: 0 movidos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
6263d24788 |
store-gc + farm-sync: cortar el BUCLE DE CHURN de 24 G que hacía inútil la poda
Podé el store por la mañana: 362 artefactos superados, 24 G. Lo volví a podar por la tarde: 362 artefactos, 24 G. Comparados los dos manifiestos, **solapamiento del 100%: son los MISMOS 362**. No era casualidad ni recuento nuevo — es un bucle. LA CAUSA no estaba en el gc sino en `farm/farm-sync.sh`, que bajaba el store del worker ENTERO sin filtro. El worker nunca se poda, así que conservaba los superados y nos los devolvía en cada cosecha. Podar → cosechar → vuelven → podar. El disco pagaba 24 G por vuelta y el gc informaba éxito cada vez, lo que es peor que fallar: parecía que se avanzaba. Es la misma familia de fallo que el gc que reportaba borrados falsos, sólo que un nivel más arriba: entonces el borrado no ocurría, ahora ocurre y se deshace solo. En los dos casos el mensaje de éxito era cierto y engañoso a la vez. EL ARREGLO es darle memoria al sistema. `store-gc.sh` acumula lo podado en `work/store-gc-superados.txt` y `farm-sync.sh` lo usa como `--exclude-from` al bajar el store del worker. Es seguro porque el store es CAS: los nombres `<hash>-<paquete>` son inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda — y si retrocediera, se reconstruye, que es barato. Disco: 159 G → 182 G tras la poda de hoy, y esta vez debería quedarse. De paso, en el respaldo: rsync 24 («ficheros del origen desaparecieron») NO es un fallo, es justo lo que pasa al podar mientras se sube. El bucle de reintentos lo tomaba por error real y habría parado el respaldo entero por algo inofensivo — y encima cuando el disco aprieta, que es cuando menos conviene quedarse sin copia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
89c02db639 |
respaldo: rsync 24 (ficheros desaparecidos) no es un fallo — es podar mientras se sube
El bucle de reintentos trataba el 24 como «error que no es de red» y habría PARADO el respaldo entero. Pero 24 significa que ficheros del origen desaparecieron durante la transferencia, que en este proyecto es una combinación NORMAL: el respaldo dura ~19 h y `store-gc.sh` es la válvula del disco, así que podar mientras se sube va a pasar. Y pasaría justo cuando el disco aprieta, o sea cuando menos conviene quedarse sin copia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
be4e91086c |
licencias: el texto dentro de la imagen, con veto — y el SDD 19/20 se equivocaba de sitio
═══ LA CORRECCIÓN DE FONDO: no es `pack`, son las IMÁGENES ═══
El SDD 19 y el 20 decían «inyectar el texto de la licencia en `hammer pack`, que es aguas
abajo del ArtifactHash y sale gratis». La conclusión sobre el coste era correcta pero el
SITIO estaba mal, y por una razón que sólo se ve leyendo el código: `pack` produce un
`.swm`, que es una RECETA DE TRANSFORMACIÓN sobre fuente pública y por diseño explícito
«NUNCA transporta binarios cocidos»; e `install` REPRODUCE construyendo, con cache-hit del
corpus, en vez de bajar binarios. O sea que **el canal de paquetes de hammer no distribuye
binarios** y la obligación de acompañar-el-binario ahí casi no aplica.
Donde sí aplica, con toda su fuerza, es en la IMAGEN INSTALABLE: su rootfs se puebla con
`hammer install` sobre un prefix y de ahí sale un USB/ISO lleno de binarios que se le da a
alguien. Ése es el punto de entrega. (La obligación del espejo de FUENTES es otra y sigue
entera: las recetas apuntan a URLs de terceros que se caen.)
═══ LO QUE ENTRA ═══
· `scripts/licencias-textos.sh` + `licenses/`: los 44 textos CANÓNICOS de SPDX
(spdx/license-list-data), uno por identificador realmente en uso — la lista se saca de las
recetas, no de un fichero escrito a mano que se desincroniza. 764K.
· `scripts/licencias-rootfs.sh`: escribe /usr/share/licenses/<pkg>/ con el texto de CADA
licencia de la expresión (`Unlicense OR MIT` ⇒ los DOS textos: en un OR el destinatario
elige, y entregar uno solo le quita una opción que el autor le concedió), más un
MANIFEST.tsv. Medido: 1,0 MB para el perfil `cli` de 44 paquetes.
═══ Y SOBRE TODO: ES UN VETO ═══
Sale ≠0 si algún paquete del rootfs no declara licencia. Un informe que no puede bloquear no
cambia lo que ocurre; esto corta el paso. Y se ganó el sueldo al primer intento: reveló que
los perfiles `base` y `cli` —los dos más simples, los que primero se publicarían— llevaban
10 y 12 paquetes con binarios y licencia desconocida. Curados los 10 que se podían afirmar
(doas, iputils, less, mandoc, procps-ng, rsync, strace, sudo, tree, usbutils); ambos perfiles
bajan a 2.
Esos 2 NO se rellenan a propósito, y quedan vetando:
· lsof — licencia propia del proyecto, sin identificador SPDX. Toca `LicenseRef-lsof` con
su texto, que ya viaja en el tarball (la receta instala su COPYING).
· tzdata — dominio público por declaración de sus autores, y SPDX no tiene identificador
para eso (es una AUSENCIA de licencia, no una licencia). Toca
`LicenseRef-PublicDomain` documentado, no forzar uno de la lista.
De paso: normalizado `GPL-3.0+` (sufijo antiguo) a `GPL-3.0-or-later`, misma equivalencia
documentada que la barra de Cargo. Y el caso especial que puse para `Linux-syscall-note`
(«las excepciones viven en exceptions/») era una suposición razonable y FALSA: SPDX las
publica en el mismo `text/`; ese directorio no existe.
1063 de 1141 (93%). Hashes verificados: 0 movidos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
487e155f74 |
licencias: leer el LICENSE de verdad cuando GitHub dice «no sé» — 1053 de 1141 (92%)
Último recurso mecánico, `scripts/licencias-texto.sh`: para las 60 recetas donde la API de GitHub devuelve NOASSERTION (el repo TIENE un LICENSE pero licensee no lo reconoce: texto retocado, encabezado propio, dos licencias en un fichero), baja el texto y lo clasifica acá. 34 identificadas — 16 MIT, 10 Apache-2.0, 7 BSD-2-Clause, 1 BSD-3-Clause. SÓLO PERMISIVAS, Y NO ES PEREZA. MIT, Apache-2.0, BSD, ISC, MPL-2.0 y Unlicense se reconocen por frases inconfundibles y NO tienen variantes -only/-or-later ⇒ reconocer el texto da el SPDX completo. La familia GPL se deja al humano A PROPÓSITO, y la razón es sutil: el COPYING de la GPL es IDÉNTICO tanto si el proyecto es «sólo v3» como «v3 o posterior» — lo que las distingue vive en las cabeceras de los fuentes. Y encima el propio COPYING incluye, en su apéndice «cómo aplicar la licencia», la frase «or (at your option) any later version», así que buscarla da SIEMPRE positivo y parecería evidencia siendo texto de plantilla. Es exactamente la trampa de la regla del `.a` no-PIC, donde grep contaba reubicaciones de .debug_* y mentía. Comprobado a mano un caso que daba mala espina: el LICENSE de `conftest` empieza «Conftest — Write tests against your config files / Copyright (C) 2019 …», que es el formato típico de una cabecera GPL. Leído entero, dice «Licensed under the Apache License, Version 2.0». La clasificación era correcta; la sospecha, barata. Hashes verificados sobre las 36 tocadas: idénticos. Quedan 88, ya sin vía mecánica: los repos donde ni la API ni el texto deciden, la familia GPL sin desambiguar, y tarballs de sitios propios (gmplib, xiph, sr.ht, codeberg…). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c8d1357f06 |
licencias: la declaración del autor manda — y arreglo 5 recetas que ROMPÍ y commiteé
Dos cosas: una mejora de calidad y un fallo mío que hay que contar entero.
═══ EL FALLO: escribí un error JSON dentro de 5 recetas y las commiteé rotas ═══
En `a6a9d59`, cinco recetas quedaron con
license = "{"message":"Not Found","documentation_url":"...","status":"404"}"
Causa: cuando el repo da 404 (renombrado, borrado, privado), `gh api` escribe el CUERPO DEL
ERROR en stdout y el filtro `--jq` falla, así que la variable queda valiendo el JSON. Mi
`case` sólo descartaba ""/null/NOASSERTION/other ⇒ el JSON pasó como si fuera una licencia.
Las comillas ROMPEN el TOML, y esas 5 recetas dejaron de parsear y de tener ArtifactHash.
No lo vio nadie leyendo: lo cazó la comparación de hashes, al detectar que `cargo-sort`
había cambiado. Afectadas: cargo-sort, cargo-audit, cargo-binstall, waybackurls, usbutils.
La lección no es «se me pasó un caso», es que **validé los «no sé» conocidos en vez de la
FORMA del dato**. Ahora se acepta sólo lo que tiene forma de expresión SPDX, y la barrera
está en los DOS sitios que escriben (detectar y sembrar), más un GUARDIÁN en el informe que
sale ≠0 si alguna licencia tiene forma imposible. Un fallo aguas arriba disfrazado de dato
hay que verlo sin buscarlo.
Y de paso normalizadas 11 licencias con la barra antigua de Cargo (`MIT/Apache-2.0`), que
no es SPDX válido. Traducir la barra a OR no es inferir: Cargo documentó esa equivalencia.
═══ LA MEJORA: `scripts/licencias-cargo.sh` ═══
Lee la licencia que el AUTOR declara en su Cargo.toml, pidiéndola AL TAG QUE LA RECETA
PINEA (`?ref=v<version>`, con HEAD como último recurso y anotando cuál se usó). 176
obtenidas, 167 al tag exacto. Es la fuente más autoritativa de las que usamos y corrige los
dos defectos de la detección por API de una vez:
· las licencias DOBLES dejan de colapsar: `fd` pasa de `Apache-2.0` a `MIT OR Apache-2.0`,
`ripgrep` a `Unlicense OR MIT`, `bat` a `MIT OR Apache-2.0`;
· los SPDX AMBIGUOS caen de 71 a 55, porque el autor sí escribe -only/-or-later.
66 corregidas, 7 nuevas. Y aparecieron cosas que la detección había perdido: `eza` es
EUPL-1.2, una copyleft europea que se habría empaquetado creyendo otra cosa.
Jerarquía de evidencia, escrita en la cabecera del script: nombre (prohibido) < familia/URL
de fuente < API de licencias de GitHub < Cargo.toml del autor.
997 de 1141 (87%). Verificación COMPLETA sobre las 77 recetas tocadas —no una muestra—:
0 hashes cambiados, y 5 que ahora parsean y en HEAD no.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5e42d201b1 |
respaldo: reintentar los cortes de red — el primero murió a los 2,3 G
La primera corrida real subió el cerebro (estado + repo, lo irreemplazable) y 2,3 G de store, y entonces murió: `Connection reset by peer` / `Broken pipe`, rsync 255. Con ~19 h de subida sobre un enlace de oficina, que la conexión se corte NO es un accidente: es lo normal. Y un respaldo que hay que relanzar a mano cada vez que se cae no se completa nunca, porque nadie está mirando. Como rsync ya es incremental y `--partial-dir` conserva los trozos a medio subir, reintentar es barato y seguro: cada intento retoma donde quedó, no reempieza. El bucle insiste sólo ante fallos de RED (10/12/23/30/35/255) con espera creciente hasta 60 s; un error de verdad —permisos, destino lleno, ruta inexistente— sale a la primera en vez de repetirse 40 veces contra la misma pared. DE PASO, UN ERROR MÍO QUE VALE REGISTRAR: comprobé el respaldo con `pgrep -f respaldo-storagebox` y dijo que corría, cuando llevaba rato muerto — el pgrep se estaba matcheando A SÍ MISMO, porque la cadena buscada está en la propia línea de comando del shell que la ejecuta. Ya me había pasado hoy con el worker. La comprobación buena es mirar lo que el proceso PRODUCE (bytes en el destino, última línea del log), no si hay un proceso vivo. Es la misma lección que el guardián de store-gc: un `rm` que devuelve 0 no prueba que el fichero se fue, y un proceso vivo no prueba que esté avanzando. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a6a9d59e4f |
licencias: 936 de 1141 (82%) — detección por evidencia, con sus dos imprecisiones declaradas
Esta mañana eran 0. Tras la tabla curada (228) y este paso, 936. Ninguna receta se
re-hasheó: verificado en 30 de las 708 tocadas, 30 hashes idénticos, 0 cambiados.
DE DÓNDE SALE EL DATO, Y POR QUÉ NO ES ADIVINAR. `scripts/licencias-detectar.sh` consulta
la API /repos/{o}/{r}/license de GitHub para las 673 recetas cuya fuente vive allí. Eso
devuelve el resultado de DETECTAR el fichero LICENSE que el repo tiene de verdad
(licensee), no una etiqueta escrita a mano en una web: es la misma evidencia que veríamos
abriendo el tarball, obtenida sin bajar 673 tarballs por un enlace de 8 Mbps. 610 con SPDX
definido; las 63 que GitHub marca NOASSERTION/other se DESCARTAN — un «no sé» de la fuente
se propaga como hueco, no se redondea a una licencia plausible.
Las familias no-GitHub van curadas por la URL DE FUENTE, que es la evidencia que el nombre
no da. Y ahí cometí el error simétrico al que este mismo fichero advertía: había puesto
`knighttime` entre las «herramientas Go» POR SU NOMBRE, y su tarball sale de
download.kde.org — es un Framework de KDE, LGPL. Descartar por nombre falla igual que
aceptar por nombre. Corregido en la cabecera.
LAS DOS IMPRECISIONES, contables con `licencias.sh --revisar` en vez de escondidas:
1. SPDX OBSOLETOS Y AMBIGUOS (71 recetas). GitHub devuelve `GPL-3.0`, `LGPL-2.1`,
`AGPL-3.0`… identificadores que SPDX declaró obsoletos PRECISAMENTE porque no
distinguen `-only` de `-or-later`, y esa diferencia decide con qué se puede combinar el
paquete. NO se normalizan a ciegas: mapear GPL-3.0 → GPL-3.0-or-later sería inventar el
dato que falta. Quedan marcadas para resolver mirando el fuente.
2. LICENCIAS DOBLES COLAPSADAS. La API devuelve UNA sola licencia y muchos proyectos Rust
son «MIT OR Apache-2.0» (p.ej. `fd` quedó como Apache-2.0). No es falso —cumplir una de
las opciones concedidas basta— pero es incompleto. Lo resuelve el cierre estructural:
leer el campo `license` del Cargo.toml en la fase de fetch.
Y un fallo que habría escrito basura en silencio: `licencias-detectadas.tsv` tiene TRES
columnas (añade el owner/repo consultado, para poder auditar) y el sembrador leía dos, así
que la licencia se habría llevado pegado el slug — `license = "MIT<TAB>owner/repo"`, sin
que nada lo validara. Arreglado antes de sembrar.
Quedan 205 sin licencia y 71 por desambiguar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
346cd59706 |
licencias: campo license en la receta — de 0 a 228 de 1141, sin re-hashear nada
La deuda legal bloqueante del SDD 19 §2.1. Medido hoy: **0 de 1141 recetas** declaraban licencia, no «5 de 771» como decía el informe anterior. Los dos números estaban mal: los «5» eran falsos positivos de `grep license` (el paquete `addlicense`, el paquete `cargo-bundle-licenses`, una línea `install .../share/licenses/` y un comentario), y las recetas son 1141. Contar con `grep -l <palabra>` sobre TOML cuenta comentarios y nombres, no campos; `scripts/licencias.sh` cuenta el campo de verdad (clave en la raíz, antes del primer `[table]`). LO QUE HACE LA DEUDA PAGABLE: `Recipe::hash_inputs` es una LISTA BLANCA — sólo entran source, compiler, target, link, patches, flags, phases y deps. `license` no entra, igual que `evidence` y `slots`. Por eso se puede poblar en las recetas YA SELLADAS sin mover un solo ArtifactHash. Verificado, no supuesto: en 40 recetas modificadas se comparó el hash con y sin la línea — 40 idénticos, 0 cambiados. Si el campo entrara al hash, declarar la licencia costaría reconstruir el corpus entero y no se haría nunca. Clavado con el test `licencia_round_trip_y_no_afecta_el_hash`. TRAMPA DE TOML: una clave suelta después de un `[table]` pertenece a esa tabla. Puesta al final del fichero, `license` acaba dentro de `[deps]` y se pierde EN SILENCIO, porque serde ignora los campos que no conoce — no hay error, simplemente no está. Va arriba, junto a `name` y `version`; el sembrador la inserta tras `version`. NO SE ADIVINA. Declarar mal una licencia es peor que dejarla vacía: convierte un hueco visible en una afirmación falsa. Sólo se puebla desde una tabla curada entrada por entrada (`docs/licencias-conocidas.tsv`); lo que no tiene evidencia queda vacío y se CUENTA. Concretamente se descartó el atajo «k* = KDE ⇒ LGPL»: en este catálogo `kail`, `kind`, `ko`, `kopia`, `krew`, `kustomize`, `kyverno`, `katana`, `kibi`, `kmon` y toda la familia `kube*` son herramientas Go sin relación con KDE. El nombre no es evidencia. Quedan 913, casi todas CLIs Go/Rust importados en masa — y ésas sí son automatizables con evidencia real: Cargo.toml trae el campo `license` y los módulos Go traen su LICENSE en el árbol. El cierre estructural es capturarlo en la fase de fetch, que ya descarga y extrae cada tarball, y inyectar el texto en `hammer pack` (aguas abajo del ArtifactHash) en vez de en la fase install (que sí re-hashearía). De paso, respaldo-storagebox.sh reordenado por valor irreemplazable y con zstd: medido en la oficina, el uplink da 8 Mbps iguales por cable y por wifi ⇒ 128 G no caben en una sentada, así que sube primero el cerebro (estado + repo) y `--partial-dir` hace que cortar a mitad de un artefacto no tire lo ya subido. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
60d8ca9bbf |
respaldo: Storage Box BX11 en Hetzner — no dependemos más de un laptop
El usuario pidió respaldar en «el volumen, aunque sea montándolo aquí». No se puede: un HC Volume es un dispositivo de bloque por RED, sólo se adjunta a servidores de Hetzner del mismo DC, y sólo a uno a la vez. harkaq-cosecha es el disco del worker efímero y vvv está al 78%. El producto correcto es un Storage Box: se monta desde el laptop (SSHFS/CIFS) y habla rsync/borg/restic por SSH. BX11 = 1 TiB, €3,20/mes, sin alta. Creado en hel1 con protección de borrado y la clave github5. rsync plano y no borg: el store es CAS, los ficheros son inmutables y se nombran por hash, así que incremental es exactamente lo correcto y no hay repo ni claves que mantener. borg comprimiría 4x (79% del store es .debug_) pero 126 G en 1 TiB no aprieta. El store va SIN --delete a propósito: un respaldo que replica los borrados no protege del borrado por error, y store-gc.sh acaba de demostrar que puede equivocarse en silencio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
514ae44135 |
store-gc: guardián — no reportar «borrado» sin comprobarlo
El script informó «364 artefactos borrados · libres: 24G → 48G» y no había borrado ninguno: los 364 de su propio manifiesto seguían enteros en el store. `xargs rm -rf` salió con 0 y el echo se lo creyó. Se reprodujo: corriéndolo en SEGUNDO PLANO el borrado no se materializa; en primer plano sí. Da igual la causa: un rm que devuelve 0 no es prueba de que el fichero se fue. La válvula de escape del disco estaba rota EN SILENCIO y encima reportaba éxito, que es peor que fallar — el disco llegó al 98% mientras yo creía haber liberado 24G. Ahora recuenta sobre el filesystem y sale distinto de cero si sobrevivió algo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
14bed923ad |
granja: el worker no molía GNOME — faltaban dos colas en QUEUES
El default era fósil de cuando `incoming-gnome-onda1` era el frente entero: listaba esa cola (6 recetas) pero NO `incoming-gnome` (86, donde viven gtk4, mutter y gnome-shell) ni `incoming-gnome-onda2` (la isla dinámica, 22). El worker reportaba moler GNOME y molía 6 de 114. Se agregan las dos y se mueven las tres ANTES de incoming-kde: con 205 recetas KDE por delante, un ciclo se consumía sin darles turno aunque estuvieran listadas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
213ebfbb03 |
📶 metal: el enlace WiFi se caía solo y cortaba la depuración remota a mitad
Depurando la OptiPlex por SSH sobre un hotspot, la máquina desaparecía de la red cada
pocos minutos. No es «la WiFi anda mal»: son dos causas que se suman y ninguna deja
rastro en dmesg.
- `udhcpc -q` (el que usa wifi-up) pide la dirección UNA vez y termina. Nadie renueva
el lease.
- el AP olvida a los clientes ociosos y deja de contestar ARP. Desde fuera se ve «No
route to host» mientras la máquina se cree perfectamente conectada.
El síntoma engaña justo en la dirección peor: la máquina no reporta nada, así que parece
que se colgó lo que estabas depurando.
`wifi-keep` hace ping al gateway cada 10s, que resuelve las dos a la vez — detecta la
caída para reasociar Y mantiene viva la entrada del cliente en la tabla del AP.
Sin esto cada sesión remota se interrumpe sola y hay que volver físicamente al teclado,
que es exactamente lo que la red venía a evitar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
47d8848e37 |
🔌 wifi-up: sin ctrl_interface, wpa_cli no tenía puerta a la que llamar
El /tmp/wpa.conf que generaba sólo traía el bloque `network`. Sin `ctrl_interface=/run/wpa_supplicant`, wpa_supplicant no abre socket de control y `wpa_cli` no conecta — síntoma que parece un fallo de red y es una puerta que no existe. Peor: deja ciego el único dato que distingue «clave mal» de «DHCP mudo», que es wpa_state. Y la espera de asociación que acabo de añadir dependía de wpa_cli, o sea que habría fallado siempre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
89076a5106 |
📶 wifi-up: esperaba 6s fijos y culpaba a la clave de un problema de tiempo
En el OptiPlex la interfaz wlan SÍ aparece (ath10k cargó y el re-probe PCI funcionó), pero `udhcpc` salía a preguntar 6s después de lanzar wpa_supplicant — antes de que hubiera asociación. Sin lease, y el mensaje decía «¿clave correcta?», que manda a buscar exactamente donde NO está el problema. Ahora espera al HECHO: wpa_state=COMPLETED (techo 40s), informa el estado real mientras espera, y distingue los casos en el mensaje de fallo — 4WAY_HANDSHAKE es clave, SCANNING es que no ve la red. Y DHCP reintenta 3 veces, porque el AP a veces ignora el primer DISCOVER justo tras asociar. Tercera vez hoy que un `sleep` fijo produce un diagnóstico falso (initramfs 5s, cosmic-diag 50s, wifi-up 6s). El patrón: esperar al reloj en vez de al hecho no falla donde se prueba, falla en la máquina lenta — y miente sobre la causa. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
095b876bba |
📶 metal: WiFi por ath10k (QCA9377) + cosmic-diag esperaba con reloj en vez de con hecho
WIFI. El OptiPlex 3060 SÍ tiene WiFi: `pci 0000:02:00.0: [168c:0042]` = Qualcomm Atheros QCA9377. No aparecía interfaz porque el kernel sólo llevaba IWLWIFI. Añadido ATH10K/ATH10K_PCI a linux-metal-dual + el firmware QCA9377 a la imagen + `wifi-up`. El detalle que hace falta: con MODULES desactivado el driver va BUILTIN y prueba el dispositivo a los ~5s pidiendo firmware, pero el rootfs se monta a los ~13s ⇒ `Direct firmware load failed -2` y la tarjeta queda muda, sin módulo que cargar después. `wifi-up` fuerza un re-probe PCI (remove+rescan) con /lib/firmware ya montado. Es la MISMA carrera que la del initramfs con el USB, un piso más abajo. COSMIC-DIAG. La 1ª versión esperaba `sleep 50` y en esa máquina la sesión tarda ~105s en llegar al compositor ⇒ el dmesg «después» se capturó ANTES de que cosmic-comp arrancara y salió byte a byte idéntico al de antes. El viaje se gastó sin dato, y yo leí ese dmesg vacío como «no hubo segfault», que era una conclusión sin respaldo. Ahora espera al HECHO: que cosmic-comp aparezca y luego desaparezca (techo de 6 min). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
41e8b579d5 |
🩹 cosmic-diag: el patrón del grep daba un falso positivo con microcode:
`grep -i "Code: "` matchea `microcode: Current revision: 0x...`, o sea que reportaba como hallazgo una línea de arranque normal. Anclado a lo que el kernel imprime de verdad ante una señal: segfault, general protection fault, traps:, Killed process. Dato real de la corrida en el OptiPlex: NO hubo segfault ⇒ cosmic-comp no muere por señal, sale por su cuenta sin escribir nada. Cambia la búsqueda. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e125b7a7c3 |
🔬 cosmic/metal: cosmic-diag — una corrida, toda la evidencia, incluido el dmesg
En metal cada iteración cuesta un viaje físico, así que la captura no puede depender de que alguien recuerde los pasos. `cosmic-diag` corre la sesión y deja en /var/log/cosmic: dmesg ANTES, run.txt de la sesión, y dmesg DESPUÉS. El dmesg de después es lo que faltaba. Medido en el OptiPlex 3060: cosmic-comp toma seat0 (seatd lo confirma: "Opened client 1"), vive 4,9s y se DESCONECTA, pero su log se corta a los 0,4s sin panic y con RUST_BACKTRACE=1 activo. Eso no es un error manejado, es una señal — y si es SIGSEGV el kernel imprime `cosmic-comp[PID]: segfault at ... in <BIBLIOTECA>`, que ES el diagnóstico. Descartado antes de pedir otro viaje: la clausura está completa (iris_dri.so, libEGL y cosmic-comp resuelven todos sus NEEDED en el rootfs), y los errores de tema no son la causa — cosmic-panel sobrevive al mismo GetKey(list_button) y muere después, de NoCompositor. Los tres clientes que "no encuentran compositor" son consecuencia. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b854230863 |
🩻 cosmic: escribir a /dev/ttyS0 COLGABA el arranque en metal (open() espera DCD)
Diagnosticado por el usuario en el OptiPlex 3060 quitando los pipes de say(). Abrir un puerto serie sin portadora BLOQUEA en open() esperando DCD, salvo que el termios tenga CLOCAL. El kernel pone CLOCAL cuando registra el puerto COMO CONSOLA, y eso pasa en QEMU porque el cmdline lleva `console=ttyS0,115200`. En ese metal la firmware NO aplica el CONFIG_CMDLINE horneado —el kernel arranca con `Command line:` VACÍO— así que ttyS0 es un puerto común, sin cable: el PRIMER say() se colgaba ahí. Explica los tres síntomas que no cerraban: - /var/log/cosmic se creaba (el mkdir va antes) pero el .log nunca aparecía; - `cosmic-start > /salida.txt` daba VACÍO ⇒ parecía que el script no se ejecutaba, cuando estaba bloqueado en su primera línea de salida; - la corrida terminaba siempre en «log del compositor (primer tramo)»: no era el último paso, era el dump() bloqueándose en el mismo sitio. El serial no se pierde: cuando ES la consola, /dev/console YA ES ttyS0. Cuando no lo es, no había nadie del otro lado. En metal lo que sirve es el fichero de log. Con say() destrabado, la detección de GL quedó CONFIRMADA en metal por primera vez: == cosmic :: GL por HARDWARE — kernel drm=i915 + iris_dri.so (sin overrides) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
77f8b3e3ec |
🪵 cosmic: el log de MI validación viajaba horneado en el USB, y la salida salía doble
Dos defectos que juntos hicieron ilegible el 3er viaje. 1. LOG CONTAMINADO. Los logs de sesión van a disco para sobrevivir al apagón en metal — bien— pero validar la imagen en QEMU ESCRIBE DENTRO DEL FICHERO DE IMAGEN, y después se quema. El usuario grepeó /var/log/cosmic en su máquina y leyó `drm=virtio-pci`: mi corrida en QEMU, no la suya. Un log viejo que parece nuevo es peor que no tener log. Fix: la imagen crea /var/log/cosmic VACÍO como último paso. Y la regla —validar sobre una COPIA, o regenerar antes de quemar— queda escrita donde se comete. 2. SALIDA DOBLE. `say` tee'aba a /dev/console siempre. Lanzado a mano desde el getty, stdout YA es la pantalla, y /dev/console es tty0 = la MISMA pantalla cuando el cmdline llega vacío (la firmware del OptiPlex no aplica el CONFIG_CMDLINE horneado). Cada línea salía dos veces, entreverada con lo que arje-zero escribe a consola. Eso es lo que se leía como «basura de init»: no era ruido ajeno, era el propio script. Ahora /dev/console sólo si `[ -t 1 ]` es falso. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
86645c379c |
🖥 cosmic/metal: la imagen forzaba render por SOFTWARE — el viaje no podía ganar
Diagnóstico del 1er viaje al OptiPlex 3060 (Coffee Lake, UHD 630). El dmesg del propio USB muestra que la GPU estaba PERFECTA: [drm] Found coffeelake (device ID 3e92) ... Initialized i915 1.6.0 on minor 0 fbcon: i915drmfb (fb0) is primary device Lo que fallaba era el script de arranque. `cosmic-start-qemu.sh` exportaba LIBGL_ALWAYS_SOFTWARE=1 y MESA_LOADER_DRIVER_OVERRIDE=kms_swrast INCONDICIONAL — correcto sobre virtio-gpu, letal sobre Intel real: la mesa del corpus va con -Dgallium-drivers=iris -Dllvm=disabled y NO EXISTE ningún kms_swrast_dri.so ⇒ EGL falla. Y en el mejor caso habría sido peor: si arrancaba, componía por software y ScreenCast fallaba igual que en QEMU ⇒ el viaje NO PODÍA contestar su pregunta. El origen es un comentario que yo escribí en metal-desktop-image.sh afirmando que el script no tenía supuestos del emulador. Los tenía, y el nombre del fichero lo decía. Ahora: - `cosmic-start-qemu.sh` → `cosmic-start.sh` (se llama por lo que hace). - El modo de GL se DETECTA con dos patas: driver DRM del kernel Y .so de mesa presente. Si no hay ninguna, lo DICE en vez de morir dentro de EGL. - La imagen VERIFICA que el script instalado no fuerce kms_swrast (falla el build si vuelve a pasar). Y correr cosmic-start sobre la imagen de metal POR PRIMERA VEZ (antes sólo se validaba que llegara al prompt) destapó que le faltaba entera la preparación de sesión que sólo tenía qemu-desktop-image.sh: sin grupo `video` no arranca seatd, sin usuario `messagebus` no arranca el bus de SISTEMA — y sin bus de sistema NO HAY PORTAL, o sea que portal-probe screencast no habría tenido con quién hablar aunque el GL fuese perfecto. Copiada: arje-logind-compat + política, video, messagebus, setuid del launch-helper, /var/run→/run, COSMIC_MODE, libc.musl-x86_64.so.1. Además, para que el próximo fallo en metal no cueste otro viaje a ciegas: - Los logs van a /var/log/cosmic (ext4) y no a /tmp (RAM, se evapora al apagar). `dump()` también, que iba SÓLO al serial — donde se perdió la explicación de los tres fallos de arriba. - authorized_keys horneada: sshd escuchaba en :22 pero era INALCANZABLE (PasswordAuthentication no, sin clave) ⇒ ahora se depura por red. - netup-wait: el r8169 levanta el enlace a los 20,2s y el ente sshd pedía DHCP a los 13,7s, sin reintento. En QEMU no se ve: virtio-net tiene carrier desde el instante cero. - firmware i915 de otras generaciones (la base sólo traía tgl_*; el Coffee Lake pedía kbl_dmc_ver1_04.bin). Validado arrancando como usb-storage: seatd OK, bus de sistema OK, login1 OK, pipewire+wireplumber OK, compositor OK. La línea de GL dice la verdad en QEMU. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
08440220b6 | estado: cosecha granja 2026-08-05T19:00:17Z — avance del árbol KDE | ||
|
|
705a70c33a |
🩺 EFI: el initramfs perdía la carrera contra la enumeración USB — y en silencio
Síntoma en metal ajeno: la pantalla se congela justo tras `sdc: sdc1 sdc2 sdc3 sdc4` / `Attached SCSI removable disk`. No estaba colgado. Prueba de que el root nunca se montó: con `ignore_loglevel` un montaje ext4 imprime `EXT4-fs (...): mounted filesystem` en pantalla sí o sí, y no aparecía. Dos causas encadenadas: 1. CARRERA. El rdinit arranca en cuanto se desempaqueta el initramfs, pero el bus USB enumera asíncrono y tarda segundos (reset de hub, settling de 1s por dispositivo, scan SCSI). Los 5 reintentos de 1s no alcanzaban. En QEMU el disco es virtio y está desde el instante cero ⇒ la carrera NUNCA se veía en validación. Es el `rootwait` que no podemos usar porque no hay root= en el cmdline. Ahora espera 60s. 2. CEGUERA. El cmdline horneado es `console=tty0 console=ttyS0,115200` y /dev/console = la ÚLTIMA `console=` ⇒ el serial. El `exec sh` de rescate nacía invisible. Es el MISMO bug que costó el 1er viaje físico de KDE, una capa más temprano: allá se arregló para después del switch_root (getty en tty1) y el initramfs quedó ciego. Ahora el log del pivote va a /dev/tty0, el marcador INIT-OK también, y el rescate abre shell en tty1 listando los bloques visibles. + `timeout 15` a `hammer boot menu`: corre ANTES del exec de arje-zero ⇒ colgarse ahí deja un arranque sin PID1 ni pantalla, indistinguible de un kernel muerto. El `|| true` protegía del fallo, no del bloqueo. Validado arrancando la imagen COMO USB (qemu-xhci + usb-storage, -serial null, con pantalla): marcadores del pivote visibles, motd y prompt `/ #`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
11e6a3c78c |
🔩 cosmic/metal: imagen EFI con iris HW — el viaje que cierra ScreenCast
Hermano de kde/metal-desktop-image-dual.sh, pero con la decisión INVERTIDA y ése es el motivo de existir. La de KDE va por mesa-llvmpipe a propósito (tenía que aguantar también una NVIDIA Pascal, cuya mesa HW sólo existe como binario Alpine ⇒ rompería soberanía). Acá el objetivo es cerrar ScreenCast, y el diagnóstico de hoy midió que lo que falla es el COMPOSITOR en una llamada EGL —8 ms antes del `Paused`— porque en QEMU mesa es kms_swrast: software, SIN exportación dmabuf. El `mesa` del corpus se construye con -Dgallium-drivers=iris y YA está en el rootfs de COSMIC; en QEMU nunca se usaba, en un TigerLake es el driver correcto y trae dmabuf de verdad. O sea que este viaje no es "lo mismo pero en metal": es la única forma de saber si ScreenCast está bien. ⚠ Por eso NO se inyecta llvmpipe: mezclarlos no es aditivo — llvmpipe trae su propio libgbm/libEGL/libgallium y sobrescribirlos DESACTIVA iris. Si la máquina no tuviera Intel, la imagen correcta sería la de KDE, no ésta con un parche. Y lo que NO hace falta inyectar, medido: la de KDE mete libLLVM.so.18 + libgcc_s + libstdc++ porque el JIT de llvmpipe los NEEDea. Acá NO — iris_dri.so pide sólo libglapi/libdrm/libz/libzstd/libc (mesa va con -Dllvm=disabled) y cosmic-comp ocho NEEDED sin C++. CERO binarios ajenos de Alpine. Verificado con clausura ELF completa del rootfs: 472 objetos escaneados, 42 raíces de runtime, 0 con NEEDED sin proveedor (los 3 huecos son llvm-*/perl, herramientas de build que el escritorio no arranca). Se instala EL MISMO cosmic-start validado en QEMU, no una variante: nada de lo que hace es específico del emulador, y que corra el mismo fichero es lo que hace que "validado en QEMU" signifique algo para el metal. VALIDADO EN OVMF CON PANTALLA, que es la regla de oro que costó un USB en el 1er viaje de KDE (-serial null para simular metal sin puerto serie): arje-zero PID 1, las 4 particiones montadas, el motd VISIBLE y el prompt `/ #` en la pantalla — o sea que el getty de tty1 hace su trabajo. El shell responde: iris_dri.so presente, /dev/dri/card0, y cosmic-start/portal-probe/wireplumber en PATH. Falta quemar el USB, que es destructivo y necesita que el usuario confirme el device. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8d10bccf34 |
🔇 wireplumber: construido, corriendo… y NO era el bloqueo
b3:b8f3baf0, selló a la primera. Arranca desde cosmic-start ("wireplumber OK (pid
192) — hay gestor de sesión"), se conecta al grafo (dos clientes en wpctl status) y
wpctl pasa a mostrar una sección Video que antes no existía.
Y el Start de ScreenCast SIGUE sin Response a los 60s. Mismo resultado exacto.
LA HIPÓTESIS QUEDA MATADA POR MEDICIÓN. "Falta el gestor de sesión que mueva el nodo
de Paused a Streaming" era mía y era razonable; no era cierta. Queda escrita junto a
su refutación porque una hipótesis descartada CON EVIDENCIA vale más que una lista de
sospechosos: acota el próximo paso a lo que queda, que es el backend mismo.
El dato nuevo, que es por dónde seguir: con wireplumber corriendo, `wpctl status`
lista `Video → Streams` VACÍO durante el handshake. O sea que el nodo
`cosmic-screencast` que SÍ existe en `pw-cli ls Node` no llega a wireplumber como
stream gestionado. Próximo movimiento: RUST_LOG=trace sobre screencast_thread, no
otra dep.
── por qué receta propia y no la de GNOME ──────────────────────────────────────
La de incoming-gnome funciona pero NEEDea libglib/libgobject/libpipewire de la ISLA
DINÁMICA de GNOME: traerla metería una segunda glib y una segunda pipewire en la
imagen. De 19 deps, 14 dan hash idéntico; las que divergen divergen a propósito —
sobre todo `pipewire`, que acá apunta a la de COSMIC (338d1d8c), la que el escritorio
realmente ARRANCA. Un gestor de sesión contra otra pipewire que la que corre no
gestiona nada.
El cambio de fondo es glib → glib-shared. COSMIC usa la glib ESTÁTICA del corpus, que
le alcanza al portal porque es un ejecutable. Acá no: wireplumber produce un .so y 17
módulos, y libglib-2.0.a tiene 44.275 reubicaciones R_X86_64_32/32S ⇒ no es PIC. La
regla del frente GNOME (readelf -r ANTES de gastar el build) ahorró uno.
Y la salida no fue una campaña nueva sino una MEDICIÓN: incoming-kde/glib-shared
copiada a esta cola resuelve al MISMO hash (0584ce1f) y ya estaba sellada ⇒ cero
rebuild. Idem pcre2-shared, lua y libelogind. Las dos glib coexisten sin pisarse (.a
y .so son ficheros distintos) y el portal conserva sus 3 NEEDED.
escritorio-cosmic: 84 → 89 recetas, 0 faltantes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ee037912a9 |
🎥 ScreenCast: el stream SE CREA — nodo cosmic-screencast vivo en pipewire
La receta de la sonda (b3:af49d32d) y lo que midió. Es la única receta de la campaña
cuyo producto no es una pieza del escritorio sino un instrumento para medirlo: entra
a la imagen porque un handshake de portal sólo se puede ejercer DESDE la sesión, con
bus y compositor vivos, y sale ESTÁTICA (0 NEEDED) porque un instrumento no debe
depender de aquello que mide.
[1/4] CreateSession → Response 0, session_handle ✓
[2/4] SelectSources → Response 0 ✓
[3/4] Start → el backend ABRE "Share your screen", con
miniatura EN VIVO del framebuffer y el output
Virtual-1; se elige, se pulsa Share…
y NO llega Response en 60s ✗
El log del backend da la línea exacta:
screencast_thread: state-changed 'Connecting' -> 'Paused'
Y `pw-cli ls Node` durante la espera da el veredicto INDEPENDIENTE:
node.name = "cosmic-screencast" media.class = "Video/Source"
EL NODO DE VIDEO EXISTE EN EL GRAFO DE PIPEWIRE. La cadena entera —cliente →
frontend → backend → compositor → demonio— funciona hasta crear y negociar el stream.
Lo único que no ocurre es el Response de vuelta tras quedar en `Paused`. Eso es un
lugar muy distinto del de esta mañana ("no hay demonio con quien negociar").
Sospecha para el próximo paso, y es HIPÓTESIS no medición: falta `wireplumber`. Sin
gestor de sesión nadie mueve el nodo de Paused a Streaming y el backend parece
esperarlo. Construirlo la confirma o la mata.
Gotchas que costaron corridas y quedan escritos:
· matar el backend se lleva puesto al frontend (ambos pierden dueño del bus);
· el lanzador busca por NOMBRE VISIBLE: `cosmic-term` no matchea, `Terminal` sí —
escribir el nombre del binario abre otra app;
· `| head -N` bufferiza y deja la terminal en blanco: parece colgada y está esperando;
· pkg-config OMITE los -L de dirs "estándar" y zig cc cross NO los busca ⇒
`-ldbus-1` pelado da "unable to find static system library", que suena a librería
faltante cuando lo que falta es la ruta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
28dc473aca |
🧹 store-gc: el recolector que hammer gc no tiene — 702 artefactos, 34G
El disco llegó a 100% y el gordo no estaba en `work/`: el store guarda un artefacto
por cada SELLADO, no uno por receta. Cuando una dep cambia, el ArtifactHash de todo
lo que cuelga cambia y el sellado nuevo SE SUMA al viejo. Medido: 1996 artefactos
para 1130 recetas — 12 copias de libqalculate, 9 de qt6-qtdeclarative, 8 de gtk4.
51G de 74G eran versiones anteriores.
El conjunto VIVO es `hammer hash` sobre todas las recetas de todas las colas: la
respuesta a "¿cuál es el hash vigente HOY?", que el store solo no puede dar. Lo que
no está ahí es rancio — pero rancio son DOS cosas muy distintas, y confundirlas es
la diferencia entre podar y perder:
SUPERADO — su nombre TIENE un artefacto vigente presente. Versión anterior de algo
ya sellado al día. Se reconstruye desde la receta, que está en git.
HUÉRFANO — ningún artefacto con ese nombre es el vigente. El hash de hoy NO está
sellado y éste es el ÚNICO ejemplar. Borrarlo sí pierde.
Por eso el default borra sólo los superados; `--huerfanos` hay que quererlo aparte.
Aplicado: 702 superados = 34G, de 5,4G libres a 43G. La verificación de que el
criterio era correcto no fue el `df`: fue que `hydrate-cosmic.sh` siguió dando 83/83
recetas y 0 faltantes.
Los 255 huérfanos (17G) quedan INTACTOS y son un hallazgo aparte: casi todos KDE
(kio×8, kparts×8, kcmutils×8), o sea que las recetas KDE se movieron después del
último sellado y el escritorio hidratado vive de artefactos que ya no son vigentes.
Dos avisos que el script lleva escritos porque cuestan al descubrirlos solos: el
espacio NO siempre se libera (los rootfs de work/ son hardlinks al store — borrar el
dir no rompe nada pero tampoco libera hasta que el rootfs se vaya), y `--store` por
defecto apunta a /store, no a ./store.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
e43ce95786 |
🎥 cosmic: pipewire ARRANCA, y el portal ya figura como cliente suyo
El bloqueo decisivo de ScreenCast no era una receta que faltara: `/usr/bin/pipewire`
estaba en la imagen desde que entró como dep de `cosmic-settings-daemon`, pero NADIE
lo lanzaba. En una distro con systemd de usuario eso lo hace `pipewire.socket`; acá
no hay tal cosa, así que es tarea de `cosmic-start` — el mismo patrón que el bus de
sistema y que login1.
Va justo después de asegurar XDG_RUNTIME_DIR y NO junto al bus de sesión, a
propósito: pipewire no necesita D-Bus para arrancar, sólo el runtime dir donde abre
su socket. Ahí sirve para los dos modos (bare y session) sin duplicar el bloque. Y se
espera EL SOCKET, no el pid: un demonio que muere al instante deja el pid existiendo
un rato, y esperar por él da un OK falso.
Medido dentro de la VM, cada paso una pregunta distinta:
pipewire OK (socket pipewire-0, pid 189) ¿abre socket?
pw-cli info 0 → Core/4, 1.2.7, core.daemon ¿SIRVE a un cliente?
pw-cli ls Factory → "client-node" ¿tiene lo que ScreenCast usa?
pw-cli ls Client → "xdg-desktop-portal" ¡el portal YA está conectado!
La última línea es el veredicto: el frontend del portal aparece como cliente del
demonio, una conexión que antes no podía existir y que es justo la que `Start`
necesita para negociar el nodo. `client-node` es la factory con que el backend lo crea.
Sin regresión, con la cadena entera: `cosmic-screenshot` levanta la UI INTERACTIVA
del portal (región/ventana/pantalla + Capture + Save to Pictures) y el click deja un
PNG de 53.590 bytes. Esa UI no estaba documentada — el registro anterior sólo probaba
la ruta no-interactiva. Escritorio completo: 2414 colores (el panel mutilado da 130).
Lo que sigue faltando son DOS cosas distintas, y ninguna es un one-liner:
· el handshake de punta a punta pide un cliente `ashpd` PERSISTENTE — el portal
asocia la sesión al *sender*, así que CreateSession con un dbus-send y
SelectSources con otro se rechazan por venir de nombres únicos distintos;
· wireplumber, que separa "hay stream de video" de "hay audio". Ni construido está.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
2e26a45060 |
🎥 cosmic: ScreenCast — dos bloqueos, y el que importa es que NADIE ARRANCA PIPEWIRE
BLOQUEO 1: dbus-send no puede manejarlo, al revés que FileChooser. El
selector se abre con UNA llamada; ScreenCast necesita un handshake de tres
pasos con estado (CreateSession → SelectSources → Start) y la sesión queda
atada a la CONEXIÓN que la creó — cada dbus-send es una conexión distinta
que muere al terminar, así que el paso 2 nunca encuentra la sesión del paso
1. Es límite de la herramienta, no del portal.
Lo que sí se midió: CreateSession FUNCIONA. El frontend devuelve
/org/freedesktop/portal/desktop/request/1_106/sc1 con nuestro handle_token.
BLOQUEO 2, y éste no lo arregla ningún cliente: NO HAY DEMONIO DE PIPEWIRE.
ls /usr/bin/ | grep -i pipewire → pipewire, pipewire-aes67,
pipewire-avb, pipewire-pulse
ps ax | grep -c pipewire → 1 (el propio grep)
Los binarios están —el backend del portal enlaza libpipewire-0.3.so— pero el
demonio no corre: cosmic-start no lo lanza y no hay systemd de usuario. O
sea que aunque se escribiera el cliente ashpd, Start no tendría con quién
negociar el stream. Arreglo: lanzarlo desde cosmic-start. Para AUDIO además
falta wireplumber, que ni está construido.
Y se corrigió un comentario DESACTUALIZADO de cosmic-start que decía que
cosmic-settings-daemon «todavía no se puede construir acá (arrastra
pipewire)»: es falso hace tiempo, ese daemon está sellado y corre. Un
comentario viejo manda a buscar un bloqueo que ya no existe, y eso cuesta
igual que un bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
1e786aaf42 |
🚪 cosmic: el BACKEND del portal sella (b3:7ad0f124) — cadena completa en la imagen
69 min. NEEDED: libgbm, libpipewire, libxkbcommon, libc. Instala el binario en /usr/libexec, el .service de D-Bus con @libexecdir@ sustituido, el cosmic.portal que declara las cinco interfaces (Access/FileChooser/Screenshot/Settings/ScreenCast) y sus iconos. clang18 se construyó en la granja y se cosechó al laptop (b3:62c6bfb5), pero el backend NO se pudo construir allá: el worker no tiene la cola COSMIC horneada —el snapshot golden es del 2026-07-15 y toda esta cola es posterior— así que intentó reconstruir pipewire y murió buscando glib. Es la regla de «rootfs laptop ≠ worker» pero con el STORE: lo que en el laptop es cache-hit, en el worker es un build entero con sus propias deps. Y destapó que la receta de pipewire NO lleva --wrap-mode=nodownload: ante una dep faltante intenta bajarse un subproyecto de internet y, sin red, el error que sale no es «te falta glib» sino «Unhandled python exception / This is a Meson bug». Queda anotado como deuda. --offline SIN --locked, y no es descuido: el sed del parche corre en la fase compile, o sea DESPUÉS del cargo vendor, así que cargo ve el Cargo.toml cambiado y con --locked se niega. Quitar una feature sólo puede ENCOGER el conjunto de crates, así que lo que haga falta ya está vendorizado; la hermeticidad la da --offline + el árbol vendorizado, no el --locked. Dos raíces en el perfil y en la hidratación, porque son DOS procesos que se encuentran por D-Bus en runtime y ningún [deps] los relaciona. Imagen en 83 recetas (eran 74). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
520271ea2f |
📷 cosmic: cosmic-screenshot sellada y ROTA A PROPÓSITO — y la glib compartida YA existe
b3:8b57ec9b, 9 minutos, NEEDED = libc.so y nada más. Un solo NEEDED, y el único `[deps] build = []` de la campaña: 130 líneas de Rust que no dibujan ni hablan wayland. Se incluye en la imagen sabiendo que falla, para que el hueco sea MEDIBLE en vez de supuesto. Error exacto capturado corriéndola desde cosmic-term dentro de la VM: panicked at src/main.rs:76:10: failed to send screenshot request: Portal(ZBus(MethodError(ServiceUnknown, "The name org.freedesktop.portal.Desktop was not provided by any .service files"))) Misma forma que la deuda de org.freedesktop.locale1: un nombre de D-Bus que nadie sirve. LA CADENA DEL PORTAL SON TRES ESLABONES, NO DOS. Medido en el fuente: xdg-desktop-portal-cosmic declara DBUS_NAME = "org.freedesktop.impl.portal.desktop.cosmic" (src/main.rs:27) — es BACKEND, no toma el nombre que los clientes buscan. Falta también el frontend xdg-desktop-portal, y ninguno de los dos está en ninguna cola. ⚠ CORRECCIÓN AL PROPIO RUNBOOK: decía que `gvfs` exigía «una campaña propia». Es falso desde que KDE selló las piezas. Verificado con `hammer hash` desde las dos colas —el método correcto para saber si una receta se comparte—: glib-shared 2.88.1 (b3:0584ce1f) y pcre2-shared (b3:f254abaa) resuelven IDÉNTICO desde incoming-cosmic ⇒ cero rebuild, los artefactos sirven tal cual. zlib-shared ya estaba en la cola. libffi entra estático dentro de la .so (su Requires.private lo ignora pkg-config fuera del modo estático). Lo que queda de `gvfs` NO es técnico sino una decisión: mete una segunda glib en la imagen —lo que GNOME midió y evitó— y re-hashea cosmic-files, que arrastra a cosmic-term y cosmic-edit porque lo usan como crate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
04146ec77b |
🛍 cosmic: la tienda LISTA — el catálogo AppStream hubo que fabricarlo, y el <provides> la vaciaba
Validado con pantalla: «Popular apps» poblada (App Library, Files,
Launcher, Settings), «Recently updated», y la búsqueda `text` devolviendo
COSMIC Text Editor. 2564 colores sobre un escritorio completo.
DOS TRAMPAS ENCADENADAS, y la primera invalida lo que dije al escribir la
receta:
1. El backend `pkgar` NO lee /usr/share/metainfo/*.metainfo.xml —los
ficheros que instala cada receta—. Lee el CATÁLOGO, o sea
{/usr/share,/var/lib,/var/cache}/{swcatalog,app-info}/{xml,yaml}/, que en
una distro normal genera `appstreamcli compose`. Sin ese fichero la
tienda abre perfecta y no lista NADA. Lo genera ahora
qemu-desktop-image.sh agregando los metainfo en un <components>: doce
líneas de python, cero AppStream y cero glib.
2. Con el catálogo puesto listaba 2 de 7. Los metainfo que genera `xdgen`
escriben <provides> con los envoltorios <mimetypes>/<binaries>, y el
crate `appstream` espera los hijos PLANOS del esquema de catálogo: muere
con «The tag provide doesn't have a value» y DESCARTA EL COMPONENTE
ENTERO, callado. Los dos que se veían eran justo los que no tienen
<provides>. Se elimina el bloque al agregar.
Cómo se diagnosticó, que es lo reutilizable: desde el lanzador la salida del
proceso no llega al log de la sesión. Hay que abrir cosmic-term DENTRO de la
VM y correr `rm -rf ~/.cache/cosmic-store; RUST_LOG=info cosmic-store`. El
rm no es opcional: la tienda cachea el parseo en bitcode y sin borrarlo
repite el resultado viejo.
Y el deadline del panel es una CARRERA, no un umbral: un arranque de esta
tanda salió mutilado (130 colores) con el anfitrión ocioso y -smp 8. Si sale
mutilado con carga baja, rearrancar antes de culpar al propio cambio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
b51f90ffea |
🛍 cosmic: cosmic-store — sella (b3:a06437f7), y BoringSSL cruza a musl sin cmake
La quinta aplicación, y la primera de la suite que choca con que hammer es
OTRA distro: una tienda es la cara de un gestor de paquetes y el de abajo no
es el suyo. De los cuatro backends de `src/backend/`:
flatpak pide libflatpak (GObject) ⇒ la cadena glib COMPARTIDA más
ostree/libsoup/gpgme: la torre de C que esta campaña no tiene
packagekit Rust puro (packagekit-zbus) pero necesita el DEMONIO en el bus
rpm-ostree irrelevante
pkgar el único que arranca: ni demonio ni enlace, lee el AppStream
del sistema — o sea los .metainfo.xml que instalan las recetas
ALCANCE HONESTO: con pkgar NAVEGA, no INSTALA. Ninguna operación está
cableada al .swm de hammer (Etapa F). El camino para arreglarlo quedó
identificado y no cuesta un solo .so de C: servir
`org.freedesktop.PackageKit` sobre .swm, mismo patrón que
arje-logind-compat.
HALLAZGO REUTILIZABLE: `aws-lc-sys` (BoringSSL, C + ensamblador) compiló
bajo zig-cc/musl SIN cmake y SIN perl — ninguno de los dos está en los 182
binarios de work/builder-rootfs/usr/bin. Tomó el camino de bindings
pregenerados (`cargo:rustc-cfg=universal`) y compiló los 21 MB de
libaws_lc_crypto.a con el crate `cc`. El `Compiling cmake v0.1.58` del log
es el crate ayudante, que se compila aunque el binario no se invoque. Esto
destraba cualquier receta futura que entre con rustls por defecto, que hoy
es casi todo lo que use reqwest.
NEEDED: libxkbcommon.so.0 y libc.so, igual que cosmic-edit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
539e2acd5b |
✎ cosmic: cosmic-edit VALIDADO en QEMU — y el deadline del panel sí mira la carga del anfitrión
El editor abre desde el lanzador (Super → «text» → primer resultado) y se
ESCRIBE: la evidencia tiene `hammer edita` tecleado sobre la línea 1 y el
punto de modificado en la pestaña. 2105 colores, icono en el dock con el
punto de «en ejecución». Hidratación 72 recetas (era 71).
CORRECCIÓN al runbook, medida acá: decía «no es carga del anfitrión (falla
con el anfitrión ocioso)», cierto para 4 vCPU pero se leía como que con
`-smp 8` el anfitrión daba igual. Dos arranques de la MISMA imagen con la
MISMA línea de QEMU:
load ≈ 6 (otro agente compilando) → 130 colores, mutilado, `deadline has
elapsed` en el log
anfitrión ocioso (load ≈ 1,8) → 1671 colores, completo
Este anfitrión tiene 8 núcleos, así que `-smp 8` le da la máquina entera a
la VM y cualquier compilación compite por los mismos hilos. Receta: mirar
/proc/loadavg antes de creerle a un arranque mutilado.
También: con las cuatro aplicaciones en el dock el escritorio completo da
~1670 colores, no 894. El umbral útil es «cientos vs miles».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
232f23c5a8 |
⚙ cosmic: cosmic-settings — el panel de control, y NO arrastra medio sistema
Sellada b3:716dac53. Cola 33/33, escritorio-cosmic 71/71. Abre desde el lanzador con la barra lateral entera (Red, Bluetooth, Accesibilidad, Escritorio, Pantallas, Sonido, Energía, Entrada, Aplicaciones, Fecha y hora, Sistema y cuentas). Tarda ~20s con render por software; no está colgado. La sorpresa buena: nada de NetworkManager, libpulse, udisks ni accountsservice — habla con los daemons por zbus, que es Rust puro. Se verificó ANTES de escribir la receta listando los crates -sys del Cargo.lock, que es donde vive la verdad sobre qué C hace falta: sólo drm-sys, input-sys, libudev-sys, dav1d-sys, wayland-sys (dlopen) y gettext-sys. Regla barata: grep '^name = ".*-sys"' Cargo.lock antes de adivinar deps por lo que el programa hace. Volvió a morder la doble barra de cosmic-protocols// (404 de GitHub, muere en el fetch con tres reintentos). Segunda víctima ⇒ es patrón de la suite, no rareza de un repo. Instala 32 .desktop, uno por página. Y default_schema va con find, no cp plano: es el árbol <Componente>/v1/<clave> de cosmic-config, donde cada clave es un fichero con su nombre. Deuda que deja su propio log: org.freedesktop.locale1 no lo sirve nadie ⇒ la página de idioma no podrá cambiar nada. Mismo tipo que login1, mismo arreglo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
23c0e82527 |
📁 cosmic: cosmic-files — la 2ª aplicación, y «ya está compilado» era falso por una línea
Sellada b3:48d2b072. Cola 32/32, escritorio-cosmic 70/70. Abre desde la biblioteca y lista el sistema de ficheros real de hammer (bin 72 ítems, dev, ente, etc, lib, lost+found). Entró creyendo que era casi gratis porque cosmic-term ya la compila como crate. No lo era: cosmic-term la declara con default-features = false. Lo que un paquete cuesta depende de con qué features lo pide quien lo usa, así que «ya se compiló» puede ser falso. Dos features apagadas, las dos por medición: · gvfs trae gio/glib y el enlace final pide las glib COMPARTIDAS; el corpus sólo las tiene estáticas. Se pierden montajes remotos, no la navegación local. Por eso tampoco se construye cosmic-files-applet: su Cargo.toml fija gvfs a mano. · wgpu desbordó el filesystem dos veces (No space left on device en /src/target) con wgpu+naga+ash+glow+spirv a codegen-units=1. Sin él el árbol queda en ~2 GB, y no se pierde nada: el resto de la suite pinta con tiny-skia y la imagen no tiene GPU. Y la regla que dejó el intento con glib declarado: el error fue «Package libpcre2-8, required by glib-2.0, not found» — el que falla no es la dep sino lo que su .pc declara en Requires. Declarar una dep trae su artefacto, no su clausura de pkg-config; se lee con grep ^Requires sobre los .pc ANTES de gastar el build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e39610588d |
🖥 cosmic: cosmic-term — la primera APLICACIÓN, y la biblioteca deja de estar vacía
Sellada a la primera (b3:d0ea1c1e). Cola 31/31, escritorio-cosmic 69/69. Dentro de la ventana: bash-5.3#, uname -srm devuelve Linux 6.16.12 x86_64 —el kernel propio de hammer— y qalc -t '6*7' devuelve 42, o sea la calculadora que empaquetamos una hora antes. Se abre con click en su icono en la biblioteca, que hasta hoy salía vacía con razón: los 20 .desktop de la imagen eran NoDisplay=true. Éste no lo es. Lo que arrastra y no se adivina: su Cargo.toml depende de cosmic-files, o sea que el gestor de ficheros entra como LIBRERÍA (selector y drag-and-drop) y compilar la terminal compila medio gestor de ficheros. En esta suite las aplicaciones se usan unas a otras como crates, y el grafo de Cargo no se parece al mapa de componentes. De paso, empaquetar cosmic-files como aplicación queda casi gratis. wgpu viene en default, al revés que el resto de los clientes: se deja, porque wgpu y naga ya se habían compilado enteros para cosmic-workspaces y apartarse de las features por defecto es apartarse de la única combinación que upstream prueba. Cierra con dos NEEDED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |