atuq b3:fab2fbfb, 340 M, libxul de 227.043.776 bytes: el mismo del motor con el
perfil de 46 páginas. Verificado que ARRANCA desde una hidratación limpia de
escritorio-sway, 0 errores de relocación.
El corpus queda en 860/862 sellados —los dos que faltan son `ajeno`, que es
frontera y no deuda— y el vigía de sonames en CERO huecos en los cinco perfiles.
sin PGO v1 (36 pág) v2 (46 pág)
DOM/maquetación 23.285 ms 21.050 (-9,6%) 20.640 (-11,4%)
SunSpider 3d-raytrace 16.003 ms 15.846 (-1,0%) 15.857 (-0,9%)
La mejora en maquetación NO se afirma por las medianas sino por la separación:
SEIS de las siete muestras de v1 son más lentas que TODAS las de v2; sólo una
cae dentro del rango de v2.
En SunSpider v1 y v2 son INDISTINGUIBLES —los valores se entrelazan por
completo—, que es exactamente lo esperable: ese camino lo ejecuta el JIT y el
PGO no lo toca. Un corpus mejor no puede mejorar lo que el PGO no alcanza. El
-0,9% frente al -1,0% no dice que v2 sea peor ahí: dice que no se distingue.
Una de las siete corridas de v2 dio 120.029 ms contra ~20.600 de las otras seis.
La mediana lo ignora por diseño; una media lo habría convertido en «v2 es
catastróficamente peor». Fue la razón de elegir mediana antes de ver un número.
Y lo que el banco no puede afirmar, escrito: las 10 páginas nuevas ejercitan los
mismos subsistemas que la página de medición, así que el parentesco con lo que
ahora se entrena es mayor que antes. El -11,4% es real para ESTA carga;
generalizarlo a «cualquier página» sería el mismo error que cometía el corpus de
Mozilla, en la otra dirección.
`scripts/test-atuq-inicio.py` le pregunta al motor por sus overrides en vez de
mirar el XPI en el disco: moz-extension://…/inicio.html en las dos, contra
about:home / about:newtab cuando se le saca el XPI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
La v0.3 las puso por extensión de sistema y quedó como afirmación. Que el XPI
esté en el artefacto no dice nada: el override lo puede rechazar el gestor de
extensiones, lo puede pisar una política, o el navegador puede arrancar con el
chrome viejo cacheado. Ninguna de las tres falla ruidosamente — se ve una pestaña
nueva perfectamente normal, que es de otro.
Se mide preguntándole AL MOTOR, no mirando el disco: una sonda con permiso
`browserSettings` lee `homepageOverride` y `newTabPageOverride`.
positivo moz-extension://…/inicio.html (las dos)
control about:home · about:newtab
El control negativo borra el XPI de `inicio` dentro del overlay temporal —el
rootfs real no se toca— y exige el resultado contrario.
Y una escotilla `ATUQ_DIR` en las dos sondas nuevas, con su aviso a gritos: el
corpus es compartido y hoy mismo el perfil PGO v2 de otro frente re-hasheó
`firefox` y con él `atuq`, así que el artefacto VIGENTE no existe en ningún store
hasta que alguien pague un build de horas. Sin escotilla no se puede correr una
sola prueba de atuq en esa ventana; con ella se corre contra un artefacto viejo A
SABIENDAS, y por eso el resultado no se puede citar como «atuq de hoy pasa».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Estaba en un scratchpad y se perdía; ahora vive en el repo con el porqué de cada
guarda, porque las tres se pagaron durante la primera corrida:
1. --unshare-pid en el bwrap. Sin él los hijos SOBREVIVEN al sandbox: un
http.server quedó vivo 19 HORAS, retuvo el puerto y envenenó las corridas
siguientes con «Address in use».
2. Puerto aleatorio por corrida. Elimina la colisión de raíz en vez de
detectarla: si otro proceso tiene un puerto, este arranque usa otro.
3. Marcador único por corrida, servido desde una copia ESCRIBIBLE del corpus.
La versión anterior horneaba el marcador como constante del script, y eso
rompía justo lo que el chequeo existe para probar: el servidor huérfano
servía el MISMO fichero de marca y el chequeo lo daba por bueno. Verificaba
«alguien sirve este contenido», no «este servidor es el mío». Con el marcador
por corrida, un servidor ajeno devuelve otra cosa y se aborta.
Y queda escrito por qué los sandboxes de Firefox van apagados en el
entrenamiento: con ellos los hijos mueren con signal 11, el proceso que renderiza
no nace, el servidor registra 0 GET y el único .profraw es el del padre
arrancando — un perfil de nada, con todo en verde. Es lo que hace el
profileserver.py de Mozilla. Sólo aplica al entrenamiento.
La espera al servidor va con reintento y no con un sleep fijo: 2 s concluían
«no responde» sobre uno que sí iba a responder.
El corpus se amplió por una MEDICIÓN, no por corazonada. Con las 36 de Mozilla
la ganancia era -9,4% en DOM/maquetación y -1,0% en 3d-raytrace, y la razón es
que un benchmark JIT-bound es casi ciego al PGO: el bucle caliente lo ejecuta
código que el JIT emite en runtime, y el PGO optimiza el intérprete, el GC y el
propio JIT, no lo que el JIT produce. El corpus de Mozilla está dominado en
número de páginas por SunSpider, o sea que entrenaba justo donde menos rinde.
Con las 10 de scripts/pgo-corpus/ sumadas, el efecto se ve EN EL PROPIO PERFIL:
ejecuciones registradas 5.885.254.799 -> 38.498.366.335 (6,5x)
máximo por función 416.219.136 -> 1.552.416.768 (3,7x)
Funciones y bloques totales no cambian (501.521 / 3.734.991) porque son la
estructura estática del binario instrumentado, no lo que se ejecutó.
Blob nuevo publicado y verificado bajándolo del mirror por el mismo camino que
usa hammer: 17.525.708 bytes, sha256 95472411.
Antes de escribir el crate del host en tawasuyu había que saber si el mecanismo
funciona en nuestro build. Funciona, y el cuadro deja las cuatro cosas que hacen
falta para escribirlo: dónde va el manifiesto (/usr/lib/mozilla, no el appdir),
qué instala de verdad la extensión (el escaneo de distribution/extensions, NO el
install_url de la política), que el host tiene que ser un proceso largo, y que el
único error que nombra la causa es el del puerto de connectNative.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Todo el §6 del SDD 26 —sct, descargas al CAS, archivo con RAG, torrent— pasa por
un solo mecanismo: un proceso Rust hablando native messaging con la extensión.
Antes de escribir ese crate en tawasuyu conviene saber si el camino existe en
NUESTRO build, que es propio, rebrandeado y con MOZ_REQUIRE_SIGNING vacío.
Existe. Una extensión de diez líneas y un host de tres:
positivo MENSAJE {"ok":true}
control DESCONECTADO No such native application puente_atuq
El control no sólo falla: NOMBRA la causa, que es lo que confirma que la ruta del
manifiesto es la que se probó.
TRES COSAS MEDIDAS QUE NO ERAN OBVIAS:
1. El manifiesto va en `/usr/lib/mozilla/native-messaging-hosts/`, NO en el
appdir. Gecko lo busca por `XRESysNativeManifests`, que en Linux sale de un
`/usr/lib/mozilla` compilado, y el rebranding a atuq no lo mueve.
2. Un host que escribe y SALE pierde el mensaje. La primera sonda hacía printf y
terminaba: el puerto llegaba a onDisconnect «sin error y sin mensaje», o sea
el peor informe posible — parece que el camino no existe. Con un sleep detrás
del printf, el mensaje aparece. El host de verdad es un proceso largo, así que
en producción no se nota; en una prueba, sí.
3. `ExtensionSettings.install_url` con `file://` NO instala nada, y sin una línea
de log. Probado con normal_installed y con force_installed, y con el XPI dentro
y fuera del appdir: ninguna instala. Lo que instala las extensiones de atuq es
el ESCANEO de `distribution/extensions/`. La política sirve para fijarlas y
configurarlas; leerla como «esto es lo que las instala» es un error fácil,
porque los dos mecanismos apuntan a los mismos ficheros y se tapan uno al otro.
`sendNativeMessage` no sirve para diagnosticar: devuelve «An unexpected error
occurred» para todo. El error del puerto de `connectNative` es el único que
nombra la causa.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Medida la ganancia del PGO ayer: -9,4% en una página de DOM/maquetación y -1,0%
en 3d-raytrace de SunSpider. La razón de la diferencia es que un benchmark
JIT-bound es casi CIEGO al PGO: el bucle caliente no lo ejecuta el C++ de
SpiderMonkey sino el código máquina que el JIT emite en runtime, y el PGO
optimiza el intérprete, el GC y el propio JIT — no lo que el JIT produce.
El corpus de Mozilla está dominado en número de páginas por SunSpider, o sea que
entrenaba mucho justo donde el PGO menos rinde. Estas diez ejercitan el camino
que sí es C++ de punta a punta:
flex flexbox anidado, wrap, alineaciones
rejilla CSS Grid: pistas, áreas nombradas, auto-fit
tablas tablas grandes con table-layout FIJO y AUTO (dos algoritmos)
texto columnas, shaping, reflujo por cambio de ancho
selectores DOM profundo contra 600 reglas, muchas que NO casan
pintado degradados, sombras, opacidad, mix-blend-mode
transformar transforms y contextos de apilamiento
svg paths, degradados, clip
desbordes overflow anidado, position:sticky, scroll programático
reflujo layout thrashing: leer y escribir geometría alternadamente
Reglas que cumplen todas, y no son de estilo:
· DETERMINISTAS (LCG propio, ni Math.random ni Date ni red) — dos corridas hacen
lo mismo, así que dos perfiles difieren por el timing de los contadores y no
por haber visitado caminos distintos.
· AUTOCONTENIDAS — el perfilado corre sin red.
· NUESTRAS — nada de páginas ajenas capturadas, que traerían licencia y
fragilidad.
· ACOTADAS — 16 a 32 s cada una; el perfilado arranca un navegador POR PÁGINA.
Validadas las diez contra el firefox sellado: todas renderizan y producen
captura. `flex` hubo que acortarla de 40 a 12 cajas raíz: con 40 la página medía
68.241 px de alto y la captura moría con «Failed to allocate a surface due to
invalid size». El layout ocurría igual, pero sin captura no se ejercita el
pintado, que es justo la mitad que se venía a entrenar.
Esta prueba dio «NO concluyente — la pestaña sin contenedor no salió directa»
dos veces seguidas, y la causa no tenía nada que ver con atuq: **otro frente de
esta misma máquina tenía levantado un `python3 -m http.server 8099`** desde hacía
dos horas. La oreja del destino no podía atarse, no llegaba nada, y el informe
publicaba un fallo inexistente. En un repo que comparten varios agentes, un
puerto fijo es estado compartido sin dueño.
Y había un segundo bug que es el que lo hizo dañino: el `bind` vivía DENTRO del
hilo de la oreja, donde `fatal()` no puede matar el proceso — `SystemExit` en un
hilo secundario sólo termina ese hilo. Así que la prueba imprimía «el puerto está
ocupado, la medición no valdría nada» y **seguía adelante hasta publicar un
veredicto**. Un guardián que avisa de que no puede medir y mide igual es peor que
uno que no mide.
Ahora las dos orejas se atan en el hilo principal, con el puerto 0: lo elige el
kernel. Si no hay puertos, no hay prueba.
Con eso, y contra el atuq de hoy (d36ae188, el del arreglo del LD_LIBRARY_PATH):
positivo destino 40843 · proxy 38503 → GET /directo al destino, SOCKS5 al proxy
control destino 40917 · proxy 37113 → las dos directas, cero al proxy
O sea que el ruteo por contenedor del §6.8 sigue en pie y no lo rompió nada de
hoy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Con `--via-atuq` el emisor es `new Notification(...)` dentro del navegador, o sea
las cinco piezas: libxul dlopeando libnotify.so.4 (invisible para cualquier
auditor de ELF), D-Bus, la activación y dunst dibujando. Veredicto por el
`onshow` del motor: MOSTRADA #33 con el .service, «ERROR al mostrar» sin él.
Y deja probado de paso que en el proceso de atuq la GLib NO se duplica — usa la
cadena `-shared` que arrastra su GTK3, la misma contra la que enlaza
libnotify.so.4. El que la duplicaba era dunstify.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
El modo por defecto prueba la cadena del SISTEMA (`notify-send` → bus → dunst).
Éste prueba la que motivó todo el hilo del §6.10: una página llama a
`new Notification(...)`, `libxul` hace `dlopen("libnotify.so.4")` —que no es
NEEDED de ningún ELF, o sea invisible para cualquier auditor—, eso habla D-Bus,
el bus ACTIVA dunst y dunst dibuja. Cinco piezas, y la única forma de saber que
están las cinco es verlo.
El veredicto acá no puede ser el diff de píxeles solo: el navegador ocupa la
pantalla y repinta por su cuenta. Lo decisivo es el `onshow` del objeto
Notification, que es el motor diciendo que el sistema ACEPTÓ la notificación; el
diff queda como corroboración y la captura «antes» se toma con atuq ya pintado.
--via-atuq MOSTRADA #33 · 151.174 píxeles
--via-atuq --negative-control «ERROR al mostrar» · 0 píxeles
El control negativo también se lee distinto según el modo, y no por comodidad:
sin el .service, lo que tiene que faltar en el modo navegador es el onshow.
Captura en docs/evidencia/atuq-notificacion-web-sway-2026-09-07.png: dos globos
de dunst sobre la ventana de atuq, en sway headless, sin que nadie haya lanzado
el daemon a mano.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
`dunst` 1.12.2 atiende `org.freedesktop.Notifications` en el cuarto escritorio.
La prueba no es que la receta selle: el bus ACTIVA al daemon y el daemon DIBUJA
—14.832 píxeles cambiados con el .service puesto, 0 sin él—.
Y queda anotada la media función que apareció de paso: dunstify segfaultea por
las dos GLib (estática del corpus + compartida que arrastra libnotify.so.4), o
sea que en este corpus quién enlaza qué GLib es parte del contrato.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Era la única mitad que quedaba abierta del mapa del §6.10: KDE atiende
`org.freedesktop.Notifications` con plasma-workspace, GNOME con gnome-shell y
COSMIC con cosmic-notifications; sway no tenía a NADIE, así que una página que
pedía notificar mandaba el mensaje al bus y se lo comía el silencio.
`dunst` 1.12.2 y no `mako` —que es el de la casa wlroots— por dos razones
medidas: el nombre `mako` YA ESTÁ OCUPADO en el corpus por el motor de plantillas
de mesa, y el cierre del perfil incluye las herramientas de build, así que la
imagen terminaría con dos artefactos homónimos peleando por las mismas rutas; y
mako habla D-Bus por sd-bus, que en musl es una receta nueva (basu), mientras
dunst habla por GDBus, que ya está sellado. Cero dependencias nuevas.
DOS GUARDIANES EN LA PROPIA RECETA, porque las dos cosas que importan no se ven
en que compile:
- `X11=0` es una intención; el hecho se lee en el ELF. dunst compila los dos
backends por defecto y su config.mk avisa: sin wayland «forzará xwayland». En
esta distro eso sella un binario que arranca, toma el bus y NO PINTA NUNCA. La
fase exige `libwayland-client` entre los NEEDED y prohíbe `libX11`.
- El `.service` de D-Bus es la mitad que importa: sin systemd, al daemon lo
levanta EL BUS. Se comprueba que reclame `org.freedesktop.Notifications` y se
le saca la línea `SystemdService=`, que acá no puede significar nada.
Y `DUNSTIFY=0`: entró primero en 1 y el binario **segfaultea hasta en
`--help`** (rc=139 en las tres pruebas). La causa es el cuadro que este repo ya
tiene escrito: nuestro `libnotify.so.4` es compartido y arrastra la cadena glib
`.so`, mientras esta cola enlaza la GLib estática ⇒ dos copias de GObject en un
proceso. No se pierde nada: el emisor de la casa es `notify-send`, que viene
DENTRO del artefacto de libnotify y ya está en las cuatro imágenes. Hay un
guardián que frena el día que alguien vuelva a poner DUNSTIFY=1 sin leer.
LA PRUEBA, en `scripts/wlr/dunst-headless.sh`: sway headless + bus de sesión +
`notify-send`, y NADIE lanza dunst a mano — lo activa el bus por el .service,
que es el mismo camino de una página web en atuq. La evidencia es un diff de
píxeles antes/después acotado al cuadrante donde dunst dibuja, porque un log en
verde es compatible con una pantalla vacía y el color no distingue (swaynag pinta
su barra de error en el mismo rojo).
positivo 14832 píxeles cambiados · servidor: dunst knopwob 1.12.2 1.2
control 0 píxeles · ServiceUnknown · notify-send rc=1
El control negativo borra el .service dentro del overlay temporal y EXIGE que no
se dibuje nada: probado en los dos sentidos.
Captura en docs/evidencia/dunst-sway-notificacion-2026-09-07.png.
Vigía de sonames: escritorio-sway 203 → 204 nodos, 512 sonames, 0 sin proveedor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Su `/usr/bin/firefox` tiene el MISMO lanzador que tenía atuq: el appdir una sola
vez en `LD_LIBRARY_PATH` ⇒ Gecko se come ese primer elemento al lanzar el RDD ⇒
el ffvpx bundleado no carga ⇒ AV1 no reproduce, en silencio.
No se aplica todavía a propósito: el lanzador vive dentro de la fase `install`,
así que tocarlo mueve el ArtifactHash y obliga a reconstruir firefox entero
(LTO+PGO, ~4 h) y con él atuq. Ningún perfil declara `firefox` —el navegador que
se shipea es atuq, ya arreglado—, así que hoy es latente. Se paga en el próximo
re-hash por otro motivo, que es cuando cuesta cero.
Comprobado que el comentario NO mueve el hash: b3:1d730334 antes y después.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
DOM+layout+strings, FUERA del corpus: 23.540 -> 21.327 ms -9,4%
SunSpider 3d-raytrace, DENTRO: 16.034 -> 15.878 ms -1,0%
Mismo rootfs, mismo script, corridas intercaladas A/B/A/B, mediana de 7,
calentamiento descartado; lo único que cambia entre variantes es un --ro-bind de
/usr/lib/firefox. Los rangos no se solapan en ninguna de las dos, así que ambas
diferencias son reales y no ruido.
Yo esperaba lo contrario: que medir sobre el conjunto de ENTRENAMIENTO inflara
la ganancia. Da el número MÁS BAJO, y la razón es mejor que la predicción:
3d-raytrace es aritmética pura en un bucle caliente, y ese bucle no lo ejecuta
el C++ de SpiderMonkey sino código máquina que el JIT genera en runtime. El PGO
optimiza el intérprete, el GC y el propio JIT — no lo que el JIT emite. Un
benchmark JIT-bound es casi ciego al PGO por construcción.
Donde se ve es en DOM/layout/arranque, que es C++ de punta a punta, y que además
es lo que el usuario percibe: la medición cubre el ciclo completo del proceso
(arrancar, renderizar, capturar, salir), no el régimen de una página cargada.
Corolario anotado: el corpus de entrenamiento está sesgado hacia JS justo donde
el PGO menos rinde. Un corpus con más maquetación probablemente daría más. Es su
propia unidad de trabajo.
«El más caro para el usuario es ffmpeg: sin él, un sitio que sirva H.264 no
reproduce» — el hueco más caro del mapa no existía. De los cuatro «sin receta»
quedan tres y ninguno es un códec.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
El §6.10 preguntó «¿qué NO puede hacer?» y contestó leyendo cadenas de sonames.
Media pregunta. Al hacer la otra mitad —¿se ve el vídeo?— salieron tres cosas:
DOS ERRORES DEL MAPA, los dos por leer una etiqueta en vez de medir:
- «no hay receta de ffmpeg» era falso: la hay, sellada, y ya viajaba en la
clausura de los cuatro escritorios por `mpv`. Lo que faltaba era la lista de
raíces del runner.
- «VA-API sin receta» también: `libva` está y está en las cuatro imágenes. El
hueco es real pero es el DRIVER — las tres mesa van `-Dgallium-va=disabled`.
Y queda anotado que ahora el mapa ya no lo puede ver, porque la librería
presente silencia el aviso sin encender la función.
UN BUG REAL: AV1 no reproducía. Gecko se come el primer elemento de
`LD_LIBRARY_PATH` al lanzar el RDD ⇒ sin appdir ⇒ ffvpx no carga ⇒ se cae al
ffmpeg del sistema, que no trae AV1 por software. Sin error, sin NEEDED
faltante, sin cadena ausente: `readyState=1` para siempre.
Y lo que quedó MEDIDO, por el lanzador de verdad y headless: H.264+AAC, VP9+Opus,
AV1, MP3 y FLAC reproducen, con el tiempo avanzando y no con «se creó el
decodificador» — que en AV1 se creaba igual y no entregaba un cuadro.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
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
Gecko SE COME EL PRIMER ELEMENTO de `LD_LIBRARY_PATH` al lanzar el proceso RDD
—el que decodifica vídeo—. El lanzador ponía `/usr/lib/atuq` una sola vez, o sea
primero, así que el RDD arrancaba sin él, no encontraba `libmozavcodec.so` /
`libmozavutil.so` (el ffvpx bundleado, donde vive dav1d) y anotaba:
PlatformDecoderModule FFVPX: Link result: NoProvidedLib
El navegador NO falla ahí: se cae al ffmpeg del sistema, que cubre
H.264/AAC/VP8/VP9/MP3/FLAC/Opus. Pero AV1 por software NO lo cubre —el
decodificador `av1` de ffmpeg es sólo hwaccel y nuestra receta va sin dav1d—, así
que un vídeo AV1 se quedaba en `readyState=1` para siempre, sin un error, sin un
NEEDED faltante y sin una cadena ausente. Media función apagada en silencio, que
es la forma de fallo de esta casa.
Tres corridas que sólo cambian esa variable, con el mismo artefacto:
LD=/usr/lib/atuq:/usr/lib:/lib → RDD NoProvidedLib AV1 ✗
LD=/usr/lib/atuq:/usr/lib/atuq:/usr/lib → RDD Success AV1 ✓
LD=/relleno:/usr/lib/atuq:/usr/lib → RDD Success AV1 ✓
El arreglo es repetir el appdir. Feo y correcto mientras no haya `patchelf` en el
corpus para grabar `RUNPATH=$ORIGIN`, que borraría la variable entera.
Y el guardián que sale de este punto ciego, `scripts/test-atuq-codecs.sh`: abre
cinco muestras versionadas en `scripts/fixtures/codecs/` y mira si `currentTime`
AVANZA — «se creó el decodificador» no es «decodifica». El veredicto sale por
`dump()` al stdout, así que no necesita ni red ni servidor, y corre headless para
que sirva en el worker. Con `--negative-control` se saltea el lanzador y EXIGE
que AV1 falle: probado en los dos sentidos, 5/5 y control ✓.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
Tres cosas en el mismo fichero, las tres medidas hoy:
1. `ffmpeg` y `libva` entran a las raíces. NO son recetas nuevas: las dos están
selladas en el corpus y ya viven en la clausura de los cuatro escritorios,
arrastradas por `mpv` (verificado con `yupana.membresia`, no leyendo el TOML).
Faltaban acá, o sea que el runner abría un rootfs sin libavcodec y desde ahí
se concluía que la imagen no tiene códecs. El instrumento otra vez, no el
artefacto.
2. El bucle de hidratación salía 1 SIEMPRE: `$VIGENTES` termina en `\n`, `echo`
agrega otro, la última vuelta lee la línea vacía, `[ -n "$d" ]` da 1 y con
`set -e` el script moría sin imprimir una sola línea, justo después de
hidratar bien las 41 raíces. Ahora `continue` en la vacía y un `hydrate` que
falla grita.
3. La comparación del sello NUNCA daba igual: el lado izquierdo lleva su `\n`
final y `$(cat …)` lo recorta ⇒ «DESACTUALIZADO» en cada corrida y 3013
ficheros rehidratados de más. Los dos lados pasan ahora por la misma
sustitución de comandos.
Y `ROOTFS_ONLY=1`, que corta después de hidratar: lo necesita el guardián de
códecs, que corre headless y tiene que usar ESTA lista de raíces y no una copia.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
El corpus queda entero tras la jornada: los dos únicos no sellados son `ajeno`
(steam-runtime-sniper, xwayland), que son frontera y no deuda.
atuq reconstruido sobre el firefox con PGO (b3:0acf6f58): 340 M, libxul de
227.802.816 bytes — el mismo del motor. La tesis del derivado sostenida: el
navegador propio se rehace en segundos sobre un motor nuevo.
Verificado que arranca desde una hidratación limpia de escritorio-sway, 0
errores de relocación. Y verificado que el PGO viaja EN EL ARTEFACTO y no sólo
en el configure: buildconfig.html dentro del omni.ja de atuq contiene
`profile-use`, junto a `wasi-sysroot` y `lto=cross`.
Nota de método: la CAPTURA no servía para esto. Salió byte a byte idéntica a la
de ayer porque la sección «Configure options» cae bajo el viewport — o sea que
una imagen igual no probaba que nada hubiera cambiado. El artefacto sí.
verificar-repro.sh: REPRODUCEN 1, DERIVA 0, NO-DETERMINISMO 0.
Era el riesgo real de esta unidad, no el rendimiento. El perfil es no
determinista por naturaleza; congelarlo como fuente pineada era el ARGUMENTO de
por qué el build seguiría siendo reproducible, y esto es la MEDICIÓN. Si
-fprofile-use hubiera metido cualquier decisión dependiente del orden o del
timing, habríamos cambiado velocidad por el invariante del proyecto.
El §3.ter daba dos muros —el display y el no-determinismo del perfil— y los dos
eran ciertos. Aparecieron tres más, ninguno visible sin construir:
1. libclang_rt.profile.a no existía: el lab trae clang pero NINGUNA runtime de
compiler-rt. El instrumentado murió en el minuto 38:56.
2. Los sandboxes de Firefox matan la corrida de perfilado con signal 11: el
proceso que renderiza no nace, 0 GET, y el .profraw que queda es el del
padre arrancando. Un perfil de nada, con todo en verde.
3. El worker no puede bajar del mirror: tiene mirror-env.sh pero no la clave.
Y queda escrito cómo se verificó que el PGO llegó, que NO pudo ser como RLBox.
Ahí el artefacto delata la jaula (símbolos w2c_*, cero -> 634). Acá el
discriminante análogo —las secciones .text.hot que clang emite al particionar
por temperatura— NO SIRVE: con lld y ThinLTO el enlazador las fusiona y dan cero
con PGO y sin él. La evidencia válida está un nivel bajo el configure: 189
invocaciones del compilador con -fprofile-use, llvm-profdata encontrado, cero
avisos de perfil que no cuadra, y libxul creciendo 1,7 MB.
Es evidencia de BUILD y no de ARTEFACTO, y conviene decirlo así en vez de
presentarla como si fuera lo mismo.
merged.profdata: 501.521 funciones, 3.734.991 bloques, 5.885.254.799 ejecuciones
registradas. Recogido corriendo firefox-instrumentado sobre el corpus de
entrenamiento de Mozilla (build/pgo: blueprint para maquetación, js-input y
sunspider para el motor JS), 35 de 36 páginas servidas.
VIVE COMO FUENTE PINEADA, NO COMO RECETA QUE LO GENERA. El perfil NO es
determinista —los contadores dependen del timing—, así que una receta que lo
produjera tendría un ArtifactHash estable sobre bytes cambiantes: firefox
consumiría cosas distintas en la misma dirección y dejaría de reproducir sin que
nada lo dijera. Es el modo de fallo del lab fuera de hash_inputs, fabricado a
propósito. Sellarlo una vez y pinearlo por sha256 hace que el insumo sea no
determinista y el build vuelva a serlo.
El blob (17.426.112 bytes, xz) está publicado en hammer/fuentes/<sha256>.tar del
Storage Box y verificado bajándolo de vuelta por el mismo camino que usa hammer.
La URL upstream no resuelve por DNS a propósito: es el patrón que el ADR 0013 ya
prueba, y como la URL nunca estuvo en hash_inputs, mover el objeto de origen no
re-hashea nada.
Dos cosas que costaron y quedan escritas:
· strip_components = 0. hammer recorta un componente al extraer (los releases
GNU traen un proyecto-version/ de más) y este tarball lleva el fichero en la
raíz, así que el default se llevaba lo único que había. El error era
«cp: cannot stat merged.profdata» y no menciona el recorte por ningún lado.
· SIN --with-pgo-jarlog, y es una mitad que falta, no un olvido. Alpine pasa
además un jarlog que reordena omni.ja para acelerar el ARRANQUE; lo emite el
profileserver.py de Mozilla con su extensión Quitter, que la corrida headless
no tiene. Hoy se gana la disposición de CÓDIGO y no el orden del omni.ja.
Y VIAJA UN ARREGLO QUE NO ES DE PGO, porque firefox se reconstruye igual: EL
ARTEFACTO NO ARRANCABA. Medido sobre el sellado: `firefox --version` moría con
«Error loading shared library libnspr4.so / Couldn't load XPCOM». mach install
deja /usr/bin/firefox como symlink pelado, el binario no trae RUNPATH y el
cierre no publica ld-musl-x86_64.path, así que las librerías que viven junto al
binario no las encuentra nadie. atuq andaba sólo porque su receta añade este
mismo lanzador; firefox, que va en las CUATRO imágenes de escritorio, no lo
tenía. Es el NEEDED colgante un escalón más allá —no falta la librería, falta el
modo de encontrarla— y por eso vigia-sonames.py no puede verlo: las librerías SÍ
están en el artefacto y las cuenta como propias.
firefox: b3:8116bdec -> b3:1d730334.