Corrida real contra gioser: los 21 repos fallaron al crearse y el guión dijo «no se pudo crear» A
SECAS, porque mandaba el stderr de `gh` a /dev/null. La causa era el LÍMITE DE LA API DE GITHUB
agotado —que se resuelve esperando— y sin el mensaje eso parecía un problema de permisos, de nombre o
del propio guión: hubo que reproducirlo a mano para enterarse de algo que la herramienta ya sabía.
Dos arreglos:
· El error de `gh` se muestra, recortado a su primera línea.
· Ante «rate limit» se CORTA en el primero: los 20 restantes van a fallar igual y cada intento gasta
cuota. Dice a qué hora se repone (leído de `gh api rate_limit`) y recuerda que reintentar es
seguro, porque el guión es idempotente.
No quedó estado parcial de la corrida fallida: el `pushurl` se agrega DESPUÉS de crear el repo, así
que los 21 clones siguen con su remoto original intacto. Verificado en tres de ellos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
El vigía escribe `docs/state/subcomandos.txt` en cada ciclo del latido desde ayer, y decía
`TOTAL: 44 herramientas selladas que no se pueden invocar` a secas. Un número grande, constante y sin
explicación al lado entrena a saltearse el informe — y entonces el hueco NUEVO, que es el único que
pide acción, pasa desapercibido entre el ruido del viejo. Es la misma trampa que ya tienen resuelta
`static-audit.sh` (deuda decidida) y `--auditar-raices`.
Los 44 son todos del mismo hueco, y ahora el informe dice cuál y por qué:
• falta el binario `cargo` (el toolchain de Rust)
PENDIENTE DE DECISIÓN, no olvido. `cargo`/`rustc` viven hoy en el LAB y en la cadena de
selfhost (mrustc→1.91.1), no como receta del catálogo. Empaquetarlos es una decisión de
TOOLCHAIN —qué Rust publica la distro, y desde qué cadena— no trabajo de granja.
TOTAL: 44 … (44 de hueco CONOCIDO, 0 NUEVAS) ⇒ exit 0
⚠ **«PENDIENTE DE DECISIÓN» y no «deuda decidida»**, que es distinto: en el audit estático las tres
mentiras tienen un arreglo evaluado y descartado por su coste; acá nadie decidió nada todavía. La
etiqueta tiene que decir cuál de las dos cosas es, o dentro de tres meses se lee como cerrada.
El código de salida ahora refleja sólo los huecos NUEVOS. Probado con los dos controles: sacando
`cargo` de la tabla salen `44 NUEVAS` y exit 1; restaurándola, `0 NUEVAS` y exit 0.
⚠ Y el primer intento de ese control estaba MAL y daba verde: escribí `CONOCIDOS = {} or {…}` para
vaciar la tabla, y en Python `{}` es falsy ⇒ la expresión devuelve el segundo dict y la tabla seguía
llena. El control «pasaba» sin probar nada. Rehecho renombrando la clave.
Hace por un árbol entero lo que `scripts/espejo-setup.sh` hace por takana: `pushurl` doble sobre
`origin`, de modo que cualquier `git push origin` que ya exista empiece a espejar sin tocar ningún
script. Crea el repo en GitHub como PRIVADO.
Cuatro decisiones:
· **Por defecto NO hace nada**: imprime qué haría; hay que pedirlo con `--apply`. Crear repos y
empujar código a un servicio externo no es algo que deba pasar por correr un script sin leerlo.
· **Varios clones del MISMO origen van a UN repo remoto.** En gioser hay cinco de
`tawasuyu/tawasuyu`; cinco repos distintos multiplican la confusión y empujarlos todos al mismo
`main` los haría chocar. El primario empuja normal; un secundario sólo se empuja —bajo
`refs/heads/clon/<nombre>/*`— SI aporta historia propia, y eso se comprueba preguntándole al
primario si ya tiene esos objetos (`cat-file -e`). Medido: los cinco tienen CERO commits que no
estén en su remoto, así que los cuatro secundarios se saltan y se dice por qué.
· **Sólo agrega; nunca toca el `fetch` de `origin`**, que sigue siendo el canónico.
· **Lo sucio no viaja, y se dice.** Un espejo guarda historia commiteada, no el árbol de trabajo, y
`tawasuyu` tiene 980 ficheros sin commitear. Creer que está a salvo cuando la mitad del trabajo no
está commiteado es justo el fallo que este guión existe para evitar.
⚠ Y un criterio que escribí mal y corregí midiendo: el primario era «el clon con más commits
alcanzables», y contra los cinco de tawasuyu dio un EMPATE EXACTO en 9343 — `rev-list --all` cuenta
las refs de `origin`, que los cinco comparten, así que la métrica estaba dominada por lo común y no
medía nada. El desempate quedaba en el orden del `find` y elegía `tw-deploy` (HEAD del 6/9) sobre
`tawasuyu` (del 12/9), que es el que se está trabajando. Ahora manda la fecha del HEAD en ISO, que
distingue y ordena como texto.
Ensayo sobre gioser: 25 repos sin copia fuera ⇒ 21 a espejar, 4 saltados por redundantes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
Tras devolver protobuf y abseil a `zig-cc`, qdrant cayó a deuda y se reconstruyó en el worker:
2 ficheros, sólo `usr/`, 70 M, `qdrant 1.19.1`. La limpieza del `/out/src` —el DESTDIR global que
heredaba el `make install` anidado de protobuf-src— aguanta el cambio de dep. REPRODUCE bit a bit.
El corpus queda en 907/907 sellado con deuda 0.
networkmanager b3:c36414ece23e322ef007d0acc7ce6f65b8b822ae55c38b38c9afa4b3eb340adb (52 MB)
Arreglo del segundo hueco del barrido: `networkmanager` figuraba SÓLO en escritorio-kde, y desde
GNOME, COSMIC o sway no se alcanza (cola hermana). Pero promover la receta de KDE tal cual habría
sido peor que no hacerlo, y sólo se vio mirando el ARTEFACTO:
· `-Dwifi=false` ⇒ un gestor de red que no maneja interfaces inalámbricas. Allá es correcto
(esa receta existe para dar libnm.so a plasma-nm y nada más); acá habría puesto en tres
imágenes un demonio que arranca, dibuja el applet y no lista una sola red.
· sin `nmcli` ⇒ ni una herramienta con la que manejarlo. En sway eso es la diferencia entre
tener red y no tenerla.
La variante del corpus cambia tres cosas, y la tercera hizo falta MEDIRLA:
1. `-Dwifi=true`. Habla nl80211 por libnl (ya era dep) y delega el handshake WPA2 en
`wpa_supplicant`, que desde hoy está en perfil.base ⇒ va también en [deps] runtime.
2. `readline` en [deps] build.
3. `-Dnmcli=true -Dreadline=libreadline`. Con sólo la dep, nmcli NO se construyó: `-Dnmcli` vale
true de fábrica pero `-Dreadline` es un COMBO que por defecto va en `auto`, y sin decirle
dónde mirar resolvió a «none» ⇒ meson apagó el CLI EN SILENCIO. Comprobado sobre el store.
Y `readline-shared` en runtime, cazado con provee.py sobre las NEEDED del binario: nmcli sale con
`NEEDED libreadline.so.8` y la readline canónica del corpus es sólo .a. La dep de BUILD y la de
RUNTIME son artefactos distintos — tercer caso del día tras cmus y bluez. No mueve el hash.
⚠ Se queda `-Dcrypto=null`, y es una limitación real escrita en la receta: sin cripto no valida
802.1X / WPA-Enterprise (el WiFi de una oficina). WPA2-PSK funciona porque ése lo hace el
supplicant. Encender nss o gnutls arrastra dos cadenas enormes: unidad de trabajo aparte.
── targets.toml ────────────────────────────────────────────────────────────────────────────────
escritorio-gnome, escritorio-cosmic, escritorio-sway += networkmanager (+ servicio)
escritorio-kde, escritorio-sway += wireplumber (+ servicio)
⚠ networkmanager NO se declara en escritorio-kde: allá plasma-nm ya arrastra la variante de la
cola, y dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib. Que la de
KDE vaya sin WiFi es deuda del frente KDE, y queda escrita donde toca.
libtool b3:5c09bff565b24c0a8edb306e0d4ca4e5e8e2a8c8e4a2b8ac047c8816b6883dcb
libndp b3:51be8f4336084b181e94a7f4a4178b87602cd39e975c2ddbd3125e7b782b9189
Entran como escalón para promover `networkmanager` al corpus (el barrido de perfiles midió que
GNOME, COSMIC y sway no alcanzaban ningún gestor de red: la receta vivía sólo en incoming-kde, que
desde ellos es una cola hermana).
La comprobación que decide una promoción NO es `diff` de las recetas sino `takana hash`, porque la
resolución de deps es hermano→padre y dos ficheros idénticos en colas distintas PUEDEN sellar
distinto. Acá sellan IGUAL desde las dos partes ⇒ misma dirección del store ⇒ las dos entraron por
CACHE-HIT, cero builds y cero rebuilds. (networkmanager, en cambio, sí difiere: resuelve varias
deps contra el corpus en vez de contra la cola de KDE.)
Las copias de `incoming-kde` se DEJAN donde están, a propósito: sellan en la misma dirección, así
que no hay dos artefactos ni colisión de nombre, y retirar ficheros de la cola que otro agente está
moliendo ahora mismo no es algo que este barrido tenga que hacer. Jubilarlas es trabajo del frente
KDE, el día que le convenga.
libglvnd b3:e27d3b2833a6aa6a6b4a4caad5ef2121e7be31a3c7ae59d0db22b01d696ffc99
obs-studio b3:29d95ddf50ef79692b48cae493758def564202ef81eba236b6cb279757621403
── libglvnd: se apagan egl, gles1, gles2 y headers ─────────────────────────────────────────────
La receta se escribió para la campaña completa —glvnd toma libEGL y mesa pasa a ser vendor detrás—
y la campaña NO SE HIZO: las tres mesa siguen con -Dglvnd=false. El resultado, medido en
/mnt/cosecha/escritorios/kde-rootfs ya hidratado, era el cuadro que la propia receta anunciaba:
/usr/lib/libEGL.so.1 -> libEGL.so.1.0.0 1.440.624 B (mesa: el driver)
/usr/lib/libEGL.so.1.1.0 323.144 B (glvnd: despacho, HUÉRFANO)
Dos libEGL.so.1 distintos en la MISMA ruta, más libGLESv2.so.2 y los headers GL/, EGL/ y GLES2/
duplicados y con BYTES DISTINTOS (gl.h: 79.877 vs 80.393). Quién gana depende del orden de
hidratación y nada lo guarda.
Y no hacía falta: OBS, que es la razón de que la receta exista, NO usa glvnd en runtime.
libobs-opengl.so sale con NEEDED libEGL.so.1 y nada más, y sus indefinidos son todos egl* —ni un
gl[A-Z] sin resolver— o sea que su glad resuelve por eglGetProcAddress contra la EGL de mesa. Lo
único que glvnd le da es satisfacer el OpenGL::GL de CMake AL ENLAZAR.
Ahora sella libGLdispatch.so.0 + libOpenGL.so.0 + opengl.pc y nada más: CERO ficheros en común con
mesa. Radio medido: 1 dependiente.
⚠ Esto quita el DAÑO, no cierra el muro: libOpenGL.so.0 enlaza y en runtime sigue sin haber vendor
(no hay libEGL_mesa.so.0 ni egl_vendor.d). Cerrarlo es re-sellar las TRES mesa con -Dglvnd=true en
un movimiento, y el precio está medido: `yupana radio mesa` = 148 directos, 154 transitivos, 153
sellados que caen a deuda, las CINCO imágenes. Decisión de distro, no arreglo de paso.
── obs-studio: el cache-hit tapaba una regresión ───────────────────────────────────────────────
Al forzar su rebuild, el configure murió pidiendo `qrcodegencpp`, que no es receta de ninguna cola.
La causa NO era libglvnd. La receta dice «[source] de takana NO clona submódulos ⇒ sus directorios
llegan VACÍOS» y por eso hacía `touch plugins/obs-websocket/CMakeLists.txt`. Esa premisa dejó de
ser cierta: los submódulos YA se materializan, así que `touch` no vacía nada —sólo toca la mtime de
un fichero que existe— y el CMakeLists de verdad entra pidiendo su dependencia.
Corregido a `mkdir -p` + `: >` (truncar), que funciona con submódulos y sin ellos: la receta deja
de depender de una premisa que puede cambiarle bajo los pies. Construye y sella en el hub.
wireplumber b3:bd5471f00f5cbd229fcb252ceced41c35547725733708426bf7a42ea3e39a7bd
Arreglo del hueco que destapó el barrido de servicios. `pipewire` está en los cuatro escritorios y
arranca, pero se construyó con `-Dsession-managers=[]`: sin gestor de sesión no hay política de qué
es entrada, qué es salida ni qué se enlaza con qué, y el sink que ve la aplicación es `auto_null`
—el nodo de descarte—. Audio que parece vivo y no suena.
wireplumber existía en `incoming-gnome` y en `incoming-cosmic`, y desde KDE o sway NO SE ALCANZA:
una receta resuelve sibling-first y después el catálogo PADRE, nunca una cola hermana. Por eso la
copia va al corpus, que es el único sitio desde el que las cuatro imágenes la ven — el mismo
argumento por el que viven acá `mpv` y `atuq`.
Medido antes de copiar, no supuesto:
- `yupana radio wireplumber` = **0 dependientes** ⇒ esta copia no fuerza un solo rebuild ajeno.
- Las tres variantes sellan DISTINTO (gnome b3:71afcef4…, cosmic b3:7ad25184…, corpus b3:bd5471f0…).
No es la enfermedad de las dos glib: ninguna imagen hidrata dos wireplumber, cada una toma la
suya. Es la figura de las tres `pulseaudio`, que también se quedaron a propósito.
- Consolidar las tres en una es el trabajo siguiente y NO se hace de paso: el método está probado
(es lo que se hizo con pipewire el 2026-09-03) pero GNOME y COSMIC están validados y cambiarles
el artefacto de audio sin probarlos sería pagar con su imagen una limpieza que no urge.
bluez b3:6036ab1585ce2af12e7596edd16709d1baa906f8c0ca816aed44957a038fbd66
11/11 ejecutables `statically linked` · `bluetoothctl: 5.87` corre · 31 ficheros (no se perdió nada)
El sello de ayer decía `static` y salía con los ONCE ejecutables dinámicos; el audit del cron lo
marcó esa misma noche (docs/state/static-audit.txt, 2026-09-12T21:04Z) y dejó el veredicto global
en rojo: 715 honestas y las dos nuevas mintiendo. Acá se puede ser estático de verdad —el artefacto
NO trae ningún plugin .so, o sea que nadie hace dlopen (a diferencia de cmus)— así que el arreglo
es el arreglo, no una re-declaración.
Dos mitades, y la segunda no se ve venir:
1. `LDFLAGS=-all-static` en compile **Y** en install: bluez enlaza con libtool, que lee el `-static`
del lab como «preferí los .a de libtool» y además RELINKEA al instalar.
2. `LIBS=-lncursesw`. Con -all-static el enlace de bluetoothctl corta con
`undefined symbol: tgoto ... in archive /usr/lib/libreadline.a`: readline usa termcap y su
readline.pc lo declara en `Requires.private: ncurses`, que **sólo se expande con
`pkg-config --static`**. El configure de bluez busca readline con AC_CHECK_LIB, no con
pkg-config ⇒ la transitiva nunca entra. En dinámico no se nota: la resuelve el loader.
Y se QUITA el `runtime = ["readline-shared"]` que se había añadido horas antes: hacía falta
mientras bluetoothctl era dinámico, y con el estático pasó a ser la mentira simétrica —declarar una
dep que no se usa—. No mueve el hash (las deps de runtime no entran en hash_inputs).
cmus b3:9a540d4270674ec74679dffbeed11d98796c51801663bde88e87affc041213ab
La primera versión decía `static` y era MENTIRA MEDIBLE: el audit del cron
(docs/state/static-audit.txt, medido 2026-09-12T21:04Z) la marcó — «dice static, es DINÁMICO →
libncursesw.so.6 libc.so» — y dejó el veredicto global en rojo. 715 honestas y las dos nuevas de
ayer mintiendo.
Pero el arreglo NO es hacerlo estático: **cmus no puede serlo**. El artefacto trae cinco plugins
—usr/lib/cmus/ip/{wav,cue,ffmpeg}.so y usr/lib/cmus/op/{alsa,oss}.so— y los abre por dlopen. Un
binario estático no puede abrirlos: quedaría un reproductor que no reproduce nada. Es la misma
razón por la que `sway` va dinámico (dlopea los drivers DRI de mesa).
⇒ `dynamic` es la declaración CORRECTA, no una concesión. Una declaración que el build ignora es
peor que no declarar, porque las herramientas río abajo la creen.
La prueba que decide, y cuesta dos segundos:
find store/<hash>-<nombre> -name '*.so' | wc -l
0 plugins → se puede y se debe ser estático. N plugins → dynamic es lo honesto.
Comprobadas las NEEDED de los plugins contra el corpus con scripts/provee.py, que es la otra mitad:
libasound.so.2 (alsa-lib), libavformat/libavcodec/libswresample .so (ffmpeg) y libncursesw.so.6
(ncurses-shared) los publica el corpus ⇒ no hay NEEDED colgante. Las tres primeras llegan por
[deps] build; ncurses-shared va declarada en runtime desde el commit anterior.
Tres preguntas del usuario, contestadas midiendo, y dos de las mediciones CORRIGEN cosas que este
mismo fichero afirmaba.
1. CENSO: de ~50 apps muy usadas el catálogo tiene 9, y CINCO de las nueve son de KDE (o sea que
llegan a una de las cuatro imágenes).
2. ⚠ CORRECCIÓN al «tercer defecto del método». Este documento dice, con fecha 2026-09-03, que
ninguna cola publica libGL.so ni gl.pc. Dejó de ser cierto AL DÍA SIGUIENTE: recipes/libglvnd.toml
es del 2026-09-04 y `provee.py --desde corpus opengl` da ✓ (libOpenGL.so.0 + opengl.pc). Y la
corrección trae su propia corrección: la campaña quedó A MEDIAS. Las tres mesa siguen con
-Dglvnd=false, no hay libEGL_mesa.so.0 ni egl_vendor.d, y en el rootfs KDE hidratado conviven DOS
libEGL.so.1 distintos (mesa 1.440.624 B gana el symlink; el de glvnd, 323.144 B, queda huérfano).
OBS —la razón de escribir libglvnd— NO la usa en runtime: libobs-opengl.so sale con NEEDED
libEGL.so.1 y ni un gl[A-Z] indefinido, o sea que su glad resuelve por eglGetProcAddress.
⇒ el `opengl ✓` es verdad DE ENLACE. El muro pasó de «no enlaza» a «enlaza y no corre».
Corolario: darktable estaba MAL clasificada como caso de GL. Su UI es GTK3+cairo; su muro es la
cola de deps (lensfun, libgphoto2, openexr, imath, libraw, osm-gps-map, portmidi + cuatro que
sólo viven en colas). Blender sí es el caso de GL.
3. ELECTRON: `grep -rni electron docs/ recipes/` da CERO. Nunca estuvo planeado. Y no existe un
«Electron con base Firefox» porque Gecko no tiene API de embebido desde XULRunner (SDD 26 §1) —
pero eso es exactamente lo que es atuq, y ya está: artefacto derivado + chrome propio +
extensiones + host de native messaging en Rust. Falta un modo SSB, que es más barato que UN port.
GenOffice concretamente: Apache-2.0 (salvo ee/), acepta endpoints OpenAI-compatible locales ⇒ la
mitad de IA es una línea de config contra el llama-cpp que YA viaja en los cuatro escritorios. La
mitad de runtime es qorpa. ⚠ Y el muro real es el MODELO: Qwen2.5-1.5B no sostiene IA agéntica
sobre un .xlsx.
4. QUÉ REHACER, con criterio escrito: sólo cuando el bloqueo es estructural Y el valor vive en un
protocolo o formato abierto. Pasan tres: el shell de apps sobre atuq, una bóveda de contraseñas
(la mitad cara —la integración con el navegador— ya está construida), y un cliente Matrix si
fractal no construye. Thunderbird es el más desaprovechado de la lista de EMPAQUETAR: es Gecko.
Deja escrito lo que el barrido midió y, sobre todo, CÓMO se midió, para que el recuento se pueda
repetir en vez de re-derivar.
Lo que aporta que no estaba en ningún lado:
1. **La política «catálogo ≠ imagen».** El repo ya la aplicaba de hecho y no la había escrito, y sin
ella «620 hojas sin perfil» se lee como deuda. No lo es: 529 son volcado crudo del importador y
355 son herramientas Go de nube. Una receta sellada y no declarada es CATÁLOGO — se instala por
nombre. Se vuelve deuda sólo cuando lo que falta es una CAPACIDAD.
2. **El método en tres preguntas encadenadas**, y por qué el orden importa: ¿está en algún perfil?
→ ¿podría llegar por clausura ajena (`dependientes_total>0`)? → ¿fue curada alguna vez (la
cabecera «PUNTO DE PARTIDA» del importador)? Las hojas son el hueco; las librerías no.
3. **El triaje completo de las 91 hojas trabajadas a mano**, en seis clases que suman 91 exactas.
Incluye las que NO se declaran de oficio y por qué: `openrc` compite con arje-zero, `uutils` y
coreutils/grep/findutils compiten con los applets de busybox, `waterfox` es un segundo Gecko,
y valkey/opensmtpd/step-ca/qdrant definen QUÉ CLASE de servidor es el perfil `servidor`.
4. **Dos hallazgos que no eran el objetivo del barrido:**
- `networkmanager` existe SÓLO en incoming-kde ⇒ GNOME, COSMIC y sway no lo alcanzan. Con
wpa_supplicant la WiFi se destraba, pero se configura a mano y el indicador de red del
escritorio no tiene con qué hablar.
- `ia-modelo-embeddings` no está en ningún perfil: hay motor (llama-cpp) y hay chat
(ia-modelo-chat) en los cuatro escritorios, y NO hay búsqueda por significado. La mitad
semántica del §6.3 de atuq no viaja.
5. **Una trampa del instrumento**, que casi hace escribir mal las listas de servicios:
`scripts/targets.py <perfil>` imprime las RAÍCES, no la clausura. Cruzar los [[service]] contra
esa salida dejaba fuera a pipewire y colord en GNOME, que sí están en la imagen. La clausura la
da el campo `perfiles` de los build-state*.json.