Commit Graph
5 Commits
Author SHA1 Message Date
Sergio 8730aad34e takana etapa 3b: las 49 invocaciones pasan a ./target/release/takana
Los llamadores EJECUTABLES: scripts/ (incluida toda la granja), los runbooks y
CLAUDE.md. Seguro porque la 3a ya garantiza que el worker emite los dos
binarios, y porque en farm-lab-sync.sh el cargo build remoto precede a la
invocación remota en el mismo script.

Verificado: sintaxis de los 49 (bash -n / py_compile — ojo que
why-differs-barrido.sh es Python con extensión .sh) y `takana hash` devuelve
hash real sobre el store.

NO se toca en esta etapa, a propósito:
- La variable de entorno HAMMER=. Es interfaz de los scripts entre sí y hay
  llamadores que la fijan; renombrarla va con la etapa 4.
- docs/evidencia/ y los HANDOFF: son REGISTRO de lo que se corrió ese día.
  Reescribir un comando dentro de una evidencia la falsifica.
- docs/state/: es generado, se regenera solo.
- Los ADR y los docs de diseño: texto, y `hammer` sigue funcionando. Van con
  la etapa 5, que es la de churn de texto.
2026-09-09 18:25:58 +00:00
SergioandClaude Opus 5 f3aaab75c6 test-atuq-rootfs: el triaje decía «sin receta de ffmpeg» y era falso
La familia `libavcodec.so.*` estaba clasificada como hueco con el motivo «sin
receta de ffmpeg en el corpus». `recipes/ffmpeg.toml` existe, está sellada,
publica `libavcodec.so.61` —uno de los once sonames que sondea libxul— y ya
viajaba en la clausura de los cuatro escritorios arrastrada por `mpv`. Pasa a
ruido: lo que falta son las OTRAS versiones del soname, y Firefox recorre la
lista hasta que una carga.

`libva` igual: la receta está y ahora también en el rootfs del runner. Sigue
siendo hueco, pero por la otra mitad —las tres mesa van con `-Dgallium-va=disabled`
y `-Dvideo-codecs=` vacío, así que no hay un solo `*_drv_video.so` que cargar—,
y el motivo ahora lo dice.

Con eso el mapa pasa de 47 cadenas sin proveedor a 7 huecos reales.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
2026-09-07 15:39:36 +00:00
SergioandClaude Opus 5 6886b1541c libnotify: el primer hueco que el mapa del §6.10 encontró Y cerró
`libxul.so` lleva la cadena `libnotify.so.4` adentro y la abre por dlopen cuando
una página pide permiso para notificar. Nadie en el corpus la proveía, así que la
función quedaba apagada SIN UN SOLO MENSAJE: el navegador arranca, la web pide
notificaciones, y no pasa nada. No lo veía ninguna herramienta porque no hay
NEEDED en ningún ELF — es el tercer escalón, y sobrevivió a vigia-sonames con los
cinco perfiles en CERO.

Receta nueva, 0.8.8, SÓLO en variante compartida y eso no es un olvido: a un
`dlopen("libnotify.so.4")` una `.a` no le sirve de nada, así que una receta
estática sería un artefacto que nadie puede consumir.

Dos cosas que la receta se comió y quedan escritas:

1. `libpng-shared` hace falta porque las deps de hammer NO son transitivas: el
   `.pc` de gdk-pixbuf-2.0 declara `Requires: libpng` y el meson muere con un
   mensaje que nombra a gdk-pixbuf —que sí está— en vez de a lo que falta.
2. El guardián informaba «18 bytes» y parecía una librería vacía: `stat -c%s` no
   sigue el symlink, y `libnotify.so.4` apunta a `libnotify.so.4.0.0`. El
   artefacto estaba bien y el MENSAJE mentía. Con `-L` son 162.928 bytes. Se
   arregla el mensaje porque es lo que alguien va a leer a las tres de la mañana,
   y de paso el guardián exige el fichero real, no sólo el nombre.

⚠ Y lo que NO arregla, escrito en la receta para que nadie lea de más: libnotify
no trae daemon, manda org.freedesktop.Notifications por D-Bus. Tenerla resuelve
la mitad —que firefox la encuentre—; la otra mitad es que en la imagen haya
alguien escuchando ese nombre.

Verificado con el propio mapa: `--dlopen` pasa de 25 huecos a 24 y libnotify.so.4
desaparece; el soname viejo `.so.1` se reclasifica de «hueco» a «ruido», que es lo
que ahora es. El guardián normal sigue en CERO con un soname más pedido (66).

Queda pendiente la membresía de perfil en `docs/state/targets.toml`, que es
catálogo compartido y no lo toco sin decidirlo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-07 00:51:56 +00:00
SergioandClaude Opus 5 7a81b6480c atuq §6.10: el tercer escalón — qué NO puede hacer el navegador, y por qué
hammer-9f nombró el punto ciego que ni su vigía ni mi guardián podían cubrir:
una librería que sólo aparece como CADENA LITERAL dentro de un dlopen(). No hay
NEEDED en ningún ELF, así que ningún auditor de readelf la encuentra. La
jerarquía queda:

    NEEDED del ejecutable          -> lo vemos los dos
    NEEDED de un .so dlopeado      -> lo vemos los dos
    dlopen("libfoo.so.1") literal  -> NO LO VE NINGUNO

Y un navegador vive de eso. Firefox sondea ffmpeg, VA-API, vulkan y libnotify
por nombre, y cuando no están NO FALLA: apaga la función y sigue. No hay línea
roja; hay una función que nadie ofrece y nadie reclama. Es la forma que ya costó
caro con OBS y su dlopen("libGL.so.1").

`--dlopen` busca esas cadenas y las cruza contra el rootfs. Es un HEURÍSTICO y se
declara como tal —una cadena no prueba un dlopen y su ausencia no prueba que no
lo haya—, así que no falla nunca: imprime un mapa triado. Lo afirmable es lo
contrario, que es lo útil: si la cadena está y el fichero no, esa función no
existe en esta imagen.

De 85 cadenas, 47 sin proveedor. Siete son huecos de verdad:

    códecs del sistema (H.264/AAC)  sin receta de ffmpeg — el más caro
    notificaciones web              sin receta
    llavero (libsecret)             RECETA YA EXISTE en incoming-gnome
    sonidos (libcanberra)           receta en incoming-kde
    WebGPU (vulkan-loader)          receta en incoming-kde
    vídeo por hardware (VA-API)     sin receta
    lectura en voz alta             sin receta

Tres de los siete son promoción, no autoría. Ocho son decisiones ya tomadas
(libGL por Wayland-only sin GLX; libcurl porque sólo lo usa el pingsender de
telemetría) y siete son ruido de musl o sonames viejos. El triaje va en una tabla
del propio script, no escondido en un `if`, porque es criterio y no medición: ahí
se puede discutir.

Sin triar: 0. Si aparece una cadena nueva, el informe la marca «SIN TRIAR» en vez
de tragársela.

Lo que esto cambia de fondo: hasta hoy la pregunta era «¿arranca?» y la respuesta
era sí. La que faltaba era «¿y qué NO puede hacer?», que ninguna métrica del repo
respondía porque todas miran presencia y ésta mira ausencia declarada por el
propio binario.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-07 00:43:29 +00:00
SergioandClaude Opus 5 a20d1b7453 atuq: un guardián para el rootfs del runner — y encontró SIETE sonames más cayendo al lab
La lista de raíces de `atuq-nested.sh` se mantiene A MANO y la jaula monta
`.dev-fs/alpine` como capa de abajo. Esas dos cosas juntas hacen que una raíz que
falte NO se note: el navegador arranca igual, resolviendo contra el lab. Ayer eso
costó `gcc-libs`. La regla del repo es que cada punto ciego se convierte en un
guardián, así que acá está el guardián en vez del parche.

`scripts/test-atuq-rootfs.py` mira el objeto que la jaula monta de verdad —el
directorio HIDRATADO— y no el grafo de recetas, que es lo que ya cubre
`vigia-sonames.py`. Son preguntas distintas: la lista del runner no sale del
grafo, así que el grafo no puede auditarla.

Lo primero que hizo fue encontrar SIETE sonames más que se estaban resolviendo
contra el lab, y no son cosmética:

    libexpat.so.1, libzstd.so.1   <- los pide mesa (iris_dri, libEGL, libgbm)
    libdbus-1.so.3                <- lo pide pipewire; lo trae `dbus-shared`, no `dbus`
    libbz2.so.1                   <- freetype
    libudev.so.1                  <- libspa-alsa
    libsndfile.so.1, libncursesw.so.6

Los siete tienen proveedor en el corpus. Agregados a las raíces: el rootfs pasa
de 7 huecos a CERO, y la única excepción que queda es `libc.so`, que va en una
lista explícita porque ningún artefacto lo provee — las imágenes lo copian del
devfs. Si algún día hay receta que lo provea, esa lista se achica y el guardián
se vuelve más estricto solo.

CONTROL NEGATIVO incluido, que sin él esto no probaría nada:
`--negative-control` esconde libstdc++.so.6 y exige que el guardián lo cace.
Corrido: lo caza, y nombra a quién lo pide (atuq, atuq-bin).

Y `scripts/test-atuq-ruteo.py` vuelve a pasar entero contra el rootfs completo,
o sea que el ruteo por contenedor está probado ahora sobre un rootfs que no le
pide nada al lab salvo el intérprete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-07 00:38:16 +00:00