`rsync` (404 de samba.org) y `musl` (musl.libc.org no responde) no se pueden construir hoy, y no por
culpa nuestra. Es el estado estacionario: una distro que construye TODO desde fuente tiene tantos
puntos de fallo como fuentes, y son servidores de terceros que nadie nos prometió mantener.
LA MEDIDA, peor de lo que parecía. Sobre 1167 fuentes (561 tarball + 606 git, 79 hosts):
github.com sostiene 742 — el 64% del corpus depende de UN host. Doce hosts sostienen el 89%. Y 43
hosts sostienen exactamente UNA receta cada uno: ahí es donde muerde el bit-rot lento.
El vigía, en su primera corrida: 8 URLs muertas de 1167. Tres de ellas —busybox, freetype,
freetype-shared— están SELLADAS Y EN USO: son el shell y las fuentes del escritorio que se capturó
hoy. Se salvan sólo porque el tarball sigue en la caché local de esta máquina.
LA URL NUNCA FUE LA IDENTIDAD, y el código ya lo sabía: `hash_inputs` usa `tarball:{sha256}` /
`git:{commit}` y el `..` descarta la URL; la caché se nombra `{sha256}.tar` con un comentario que
dice literalmente que cambiar de mirror no la invalida. Añadir mirrors NO re-hashea NADA. Faltaba el
mecanismo, no el diseño.
Orden: caché local → mirror propio → upstream. El mirror va ANTES, no como rescate: el sha256 se
verifica igual, así que no hay diferencia de contenido posible, y un mirror que sólo se usa cuando
upstream falla es un mirror que nadie prueba — se descubre roto el día que hace falta.
LO QUE HAY QUE HACER BIEN. Un mirror que sirve calladamente lo que upstream perdió convierte un
fallo ruidoso en silencio. Por eso construir y vigilar van SEPARADOS: `hammer build` nunca avisa
(sería ruido en 561 recetas), y `fuentes-vigia.sh` pide cabeceras, escribe
docs/state/fuentes-vigia.json y lo corre el latido. Sin ese contrapeso las URLs se mueren una a una
y el corpus queda irreconstruible con todo en verde — el mismo modo de fallo que dejó el grafo de
wlr 17 días anunciando un 121/121 falso.
⛔ PROHIBIDO cambiar el sha256 para "arreglar" una URL muerta. Es la tentación natural ante un 404 y
no arregla una descarga: cambia lo que la distro construye. Otro sha256 es otro contenido, y la
receta seguiría diciendo `rsync 3.4.4` mientras construye otra cosa.
VERIFICADO DE PUNTA A PUNTA. El primer intento —construir busybox con el mirror puesto— dijo BUILD
OK y NO PROBÓ NADA: cache-hit del artefacto, cero bytes descargados. La prueba válida usa una receta
efímera con un sha256 que sí está en el mirror y una URL que ni resuelve por DNS. Sin HAMMER_MIRROR:
`curl (6) Could not resolve host`. Con él: sella, con el contenido real.
Mirror poblado: 126 objetos en el Storage Box que ya se paga. `cargo test -p hammer-build`: 5/5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
sway es `link = "dynamic"` A PROPÓSITO —un compositor dlopea los drivers DRI de mesa en runtime— y
sale con doce NEEDED. Nueve los cubría la clausura (pixman, drm, evdev, input, udev, wayland-server,
wlroots, xkbcommon, libc), porque esas recetas ya son dinámicas. Los otros tres —libz.so.1,
libexpat.so.1, libffi.so.8— venían de recetas `--disable-shared`, así que NINGÚN artefacto sellado
producía ese `.so` y el rootfs los resolvía contra el sysroot Alpine DEL LAB.
POR QUÉ ERA PEOR QUE UNA DEP FALTANTE. Una dep ausente falla ruidosamente. Ésta no: el lab NO entra
en `hash_inputs`, así que el store daba el artefacto por bueno mientras el binario sólo arrancaba en
una máquina que tuviera Alpine debajo. La fuga era invisible para todo el sistema de medición.
`zlib-shared` ya existía en el corpus y sólo faltaba declararla. `expat-shared` y `libffi-shared` son
nuevas, calcadas del patrón: autotools con --enable-shared, sin el truco de --whole-archive que
zlib-shared necesita porque SU configure aborta bajo zig cc. SONAMEs verificados contra lo que pide
el ELF: libexpat.so.1, libffi.so.8, libz.so.1. Exactos.
NO se tocó `link` en las canónicas: entra en `hash_inputs` y habría re-hasheado expat, libffi y todo
lo que los lista en deps —fontconfig, dbus, mesa, glib, python3, los crates `-sys`— cientos de
recetas selladas por libs que ya están bien. El nombre `*-shared` es distinto del canónico, así que
conviven. Verificado antes de construir: recalculadas las 794 recetas, **0 hashes cambiados**, sólo
las 2 nuevas.
Van al CORPUS y no a incoming-wlr/ porque la resolución de deps es hermano→padre: una receta del
corpus no ve una cola. Es donde ya viven zlib-shared, fontconfig-shared, freetype-shared…
VERIFICADO CON PÍXELES, no con el log. Rootfs rehidratado desde cero (126/126), CERO ficheros de
`.dev-fs/alpine`. Cero «Error relocating». La captura tiene el MISMO sha256 que la de las libs de
Alpine: 177fea396caa9dca, 1280x720, 383 colores. Tercera vez que la evidencia sale bit a bit igual
al cambiar la procedencia — la soberanía no costó ni un píxel.
Perfil: escritorio-sway 126/128. Faltan strace (linux-headers del lab) y rsync (404 de upstream).
QUEDA UN RESTO: el loader `/lib/ld-musl-x86_64.so.1` todavía se toma del host. `recipes/musl.toml`
existe en el corpus pero está EN DEUDA y fuera de todo perfil — mismo agujero de declaración, un
nivel más abajo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
`dejavu-fonts` y `xkeyboard-config` entran en `recipes/incoming-wlr/` y en las raíces de
`escritorio-sway`. La clausura pasa de 123 a 125 y ambas ya estaban selladas: cero builds.
POR QUÉ TENÍAN QUE SER RAÍCES Y NO PODÍAN LLEGAR POR CLAUSURA. Son DATOS, no binarios: ninguna
receta depende de una fuente ni de un mapa de teclado para COMPILAR. El grafo puede seguir todas las
aristas que quiera y no va a alcanzarlas nunca. Si no se declaran, no están — y el perfil sigue
dando 100%, porque mide la clausura de las raíces declaradas.
Copias BYTE-IDÉNTICAS a las de incoming-kde, a propósito. Ya había tres copias iguales de
xkeyboard-config (kde, gnome, cosmic) y dos de dejavu-fonts (kde, cosmic); ésta es la cuarta. No se
consolidan al corpus ahora porque eso toca las colas de otros tres frentes que otros agentes
trabajan. Mantenerlas idénticas deja esa consolidación como un `git mv` trivial en vez de un merge:
cuatro ficheros iguales se unifican de un tirón, cuatro casi-iguales exigen revisión.
VERIFICADO de punta a punta, no por el log: rootfs rehidratado DESDE CERO con sólo lo que la
clausura declara (123/123), y `sway-headless.sh` sin una línea de inyección manual de fuentes ni
XKB. Cero errores de xkbcommon, cero de fcft. La captura resultante tiene el MISMO sha256 que la de
ayer con todo inyectado a mano — 1280x720, 383 colores. La declaración es exactamente equivalente a
la inyección, y el pipeline reproduce.
Queda como deuda real (no de declaración): libz.so.1 / libexpat.so.1 / libffi.so.8, que sway pide
por estar enlazado dinámico y el corpus sólo produce como `.a`. Y `fonts.conf` NO era deuda: lo trae
el propio artefacto de fontconfig.
Perfil: escritorio-sway 123/125. Los 2 que faltan son strace (choca con los linux-headers del lab) y
rsync (404 de upstream), ninguno del escritorio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o