Commit Graph
100 Commits
Author SHA1 Message Date
Sergio f9d4572077 compiler-rt-profile: la runtime de perfilado que el lab no trae (b3:32dff740)
libclang_rt.profile.a, 129.178 bytes — la que implementa los __llvm_profile_* y
escribe los .profraw. Hermana nativa de wasi-compiler-rt: mismo tarball de LLVM
22.1.8, mismo patrón, mismo sitio de aterrizaje; cambia el target (x86_64 en vez
de wasm32) y el componente (profile en vez de builtins).

Lo destapó el build del firefox instrumentado, en el minuto 38:56:

    ld.lld: error: cannot open
      /usr/lib/llvm22/lib/clang/22/lib/x86_64-alpine-linux-musl/libclang_rt.profile.a

El lab trae clang 22.1.8 con su include/ pero el lib/ del resource dir NO EXISTE
— que es exactamente lo que wasi-compiler-rt ya documentaba para el caso wasm.
O sea: el PGO no estaba bloqueado por el display (eso se despejó ayer) sino por
una pieza de toolchain que nunca hizo falta hasta ahora.

Se apaga todo menos profile: compiler-rt trae sanitizers, xray, memprof, orc y
ninguno tiene destinatario acá.

La primera corrida murió en el configure con «CMAKE_C_COMPILER_TARGET must also
be set when COMPILER_RT_DEFAULT_TARGET_ONLY is ON» — un error de cmake que dice
exactamente qué falta, cosa que no se puede dar por supuesta esta noche.

Dos guardianes. El primero exige la RUTA LITERAL del error y no «alguna
libclang_rt»: el layout per-target es una opción de cmake y equivocarla deja la
librería donde clang no mira, que es el mismo fallo que la libstdc++ en
/usr/lib64 de hace unas horas. El segundo es la prueba del consumidor: que
DEFINA __llvm_profile_write_file y compañía, para que un .a vacío no se descubra
al final de una corrida de perfilado.
2026-09-06 23:58:53 +00:00
SergioandClaude Opus 5 26a0dd31c7 SDD 26: restauradas §2.ter–§2.sexies, que mi commit anterior borró sin querer
Al reescribir el §6.8 corté el texto entre «lo que NO está probado» y `## 7`, y
entre medio no había sólo eso: vivían ahí §2.ter (el modo `[source] dir`),
§2.quater (los tres fallos que destapó abrirlo en pantalla), §2.quinquies (los
tres mecanismos de la v0.3) y §2.sexies (lo que quedó probado y con qué). Ciento
veinte líneas de método pagado con horas.

Recuperadas de HEAD~1 y comprobadas byte a byte con `diff` contra el original, no
a ojo.

La causa, que es la de siempre en este repo: usé un ANCLA («hasta el próximo
`## 7`») dando por hecho que el documento estaba en el orden en que yo lo
imaginaba. El §6.8 lo había insertado yo mismo antes del §2.ter hace unas horas,
así que el ancla saltaba por encima de cuatro secciones. Un ancla es una
etiqueta, no una propiedad del documento — la misma familia que la cabecera de
hunk que nombra la función anterior.

Lo que lo hizo visible en el mismo minuto fue mirar el `git show --stat`: 210
líneas cambiadas en un fichero donde yo había escrito 45. La regla 2 del
CLAUDE.md manda mirarlo para el ALCANCE del commit, pero sirve igual para el
tamaño del cambio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 23:48:19 +00:00
SergioandClaude Opus 5 2e093f9cab atuq §6.8: probado que el paquete sale por el proxy del contenedor — y con control negativo
Era lo único que la v0.5 dejó sin probar, y las tres puertas que había
encontrado siguen cerradas: no hay flag de línea de comandos para el
userContextId, Marionette no toma `-remote-allow-system-access` en la jaula, y
`tabs.create({cookieStoreId})` exige el permiso `cookies` — que no se le da a la
extensión del proxy sólo para que pueda probarse a sí misma.

La cuarta puerta estaba abierta: el fichero de SESIÓN guarda el userContextId de
cada pestaña y Gecko lo restaura. Se fabrica a mano — `mozLz40\0` + tamaño + un
bloque LZ4, y un bloque de sólo literales es LZ4 válido: veinte líneas, sin
librería.

Y la medición no le pregunta nada al navegador: le pone dos oídos en la red y
mira a cuál llama. Dos pestañas piden la MISMA url, una sin contenedor y otra en
«Banco»:

    al destino (8099)  GET /directo   <- la de sin contenedor, directa
    al proxy   (9099)  \x05\x01\x00   <- saludo SOCKS5 de la de «Banco»

Ese saludo sólo aparece si Gecko decidió hablar con un proxy para esa petición, y
el único que se lo pudo indicar es proxy.onRequest mirando el cookieStoreId. La
pestaña sin contenedor no es decorativa: sin ella, «todo fue por el proxy» y «el
ruteo anda» se verían iguales.

CONTROL NEGATIVO, que es lo que este repo se exige desde hoy: con
`--negative-control` no se configura el proxy y se exige lo contrario — las dos
pestañas directas y nadie llamando al proxy. Corrido: pasa. La única diferencia
entre las dos corridas es una línea de configuración y el observable se da vuelta
entero. Sin ese modo, una prueba que se hubiera vuelto ciega se vería idéntica a
una que funciona.

Tres detalles sin los cuales la prueba mide un silencio y se lee como fallo:
restore_on_demand=false, allow_hijacking_localhost=true, y el
triggeringPrincipal_base64 en cada entrada de sesión.

El artefacto se resuelve por `hammer hash` y no por glob, que es la lección de
hammer-03 de esta madrugada: con dos artefactos de la misma receta en el store,
`ls | head -1` es una ruleta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 23:47:08 +00:00
Sergio 7f912c1e22 estado: cosecha granja 2026-09-06T23:33:11Z — avance del árbol KDE 2026-09-06 23:33:11 +00:00
Sergio 391d60eb90 estado: cosecha granja 2026-09-06T23:03:02Z — avance del árbol KDE 2026-09-06 23:03:02 +00:00
Sergio 44b6032522 vigia-sonames: dos falsos positivos menos — los cinco perfiles en CERO
NO hacía falta una receta de perl. `libperl.so` está en el artefacto, son 9,6 MB
en usr/lib/perl5/5.40.2/x86_64-linux/CORE/, y el binario la encuentra por su
RUNPATH, que apunta justo ahí. El vigía no la veía porque perl la construye con
-Duseshrplib y NO le pone SONAME: el código sólo registraba el nombre del fichero
`if son:`. Un ELF compartido sin SONAME se resuelve POR NOMBRE DE FICHERO, y eso
es exactamente lo que había que indexar.

El otro falso positivo era el último «hueco» del informe:
usr/lib/go/src/debug/elf/testdata/libtiffxx.so_ — un ELF de MUESTRA que Go
shipea para los tests de su propio paquete debug/elf, pidiendo libc.so.6 (glibc)
en una distro musl. Un binario que nadie ejecuta. `testdata/` no es cierre.

Importa arreglar un falso positivo aunque «sólo» sea ruido: un vigía que grita
en falso enseña a ignorarlo, y así es como libstdc++.so.6 —que SÍ rompía el
navegador en los cuatro escritorios— estuvo en esta misma salida sin que nadie
la mirara.

    escritorio-mirada    41 nodos ·  231 sonames ·  0 sin proveedor
    escritorio-kde      306 nodos · 2083 sonames ·  0 sin proveedor
    escritorio-gnome    190 nodos ·  657 sonames ·  0 sin proveedor
    escritorio-cosmic   161 nodos ·  498 sonames ·  0 sin proveedor
    escritorio-sway     201 nodos ·  509 sonames ·  0 sin proveedor

PROBADO CON UNA ROTURA A PROPÓSITO, porque un vigía todo-verde se ve igual que
uno roto. Quitando `runtime = ["gcc-libs"]` de firefox SOLO no pasa nada —atuq
lo declara también y es raíz de los mismos perfiles, así que la librería entra
igual; el primer control estaba mal montado—. Quitándolo de las DOS, kde y
cosmic vuelven a reportar libstdc++.so.6 y libgcc_s.so.1 exactamente. Restaurado
después.
2026-09-06 23:00:59 +00:00
Sergio 841abef25f estado: cosecha granja 2026-09-06T22:32:57Z — avance del árbol KDE 2026-09-06 22:32:57 +00:00
Sergio 02facebe28 deps.runtime: que ALGUIEN las lea — de 29 huecos de soname a 6
Tres arreglos que son el mismo: un campo que nadie lee es un campo que miente.

1. yupana._deps() leía SÓLO `deps.build`. Como yupana es la base de todas las
   herramientas de grafo, una dep de EJECUCIÓN no existía para ninguna: ni el
   vigía de sonames, ni la membresía de perfiles, ni el rootfs hidratado.
   Medido: firefox declaraba `runtime = ["gcc-libs"]` y el cierre de las cuatro
   imágenes seguía sin libstdc++.so.6, así que el navegador no arrancaba y el
   vigía lo seguía reportando como hueco DESPUÉS de haberlo arreglado.
   `deps.runtime` está en el esquema de hammer desde siempre. La unión es la
   definición de cierre: para CORRER hacen falta las dos.

2. build-state.py, lo mismo y por lo mismo.

3. El vigía entra en el LATIDO y deja docs/state/sonames.txt. Existía desde
   antes y contesta la pregunta que el grafo no contesta —no «¿está sellado?»
   sino «¿arranca?»— pero NADIE LO CORRÍA: no estaba en cosecha-cron y no dejaba
   fichero de estado. Por eso libstdc++.so.6, que rompía el navegador en los
   CUATRO perfiles, estuvo en su salida sin que nadie lo leyera, y se
   redescubrió arrancando atuq a mano. Un vigía que hay que acordarse de invocar
   no se distingue de no tenerlo.

Y se BORRA scripts/audit-needed.sh, que escribí ayer sin ver que vigia-sonames.py
ya hacía exactamente esto, con la misma frase en la cabecera. Dos herramientas
que miden lo mismo divergen y la que nadie mira es la que miente; la que se
queda es la que ya existía, que además reporta POR PERFIL y encontró más cosas.

python3 declara sus cuatro deps de ejecución (readline/sqlite/lzma/bz2): son
módulos de la stdlib que se cargan por dlopen, así que no rompen el arranque
sino un `import` — un fallo que aparece lejos y no menciona a python.

Resultado, con todo aplicado: 29 huecos -> 6, y los que quedan son otra clase.
`libperl.so` es empaquetado de la receta perl; `libc.so.6` lo pide el `go`
prebuilt y es un soname de GLIBC en una distro musl, que es un síntoma distinto.
Los dos quedan anotados en docs/state/sonames.txt, que ahora se regenera solo.
2026-09-06 22:28:17 +00:00
Sergio b2ff678301 readline-shared: la .so que python3 y sqlite piden (b3:79355aad)
libreadline.so.8, 970.144 bytes. Hermana de ncurses-shared y por la misma fuga:
python3 y sqlite-shared salen con NEEDED libreadline.so.8 y la readline del
catálogo es --disable-shared, así que sólo produce libreadline.a. En el rootfs
hidratado eso se resolvía contra el sysroot Alpine DEL LAB.

Versión 8.2 y no la 8.3.3 de Alpine, a propósito: es la que ya usa
recipes/readline.toml y tener las dos variantes en versiones distintas es pedir
que diverjan sin que nadie lo note.

TRES GUARDIANES, y el segundo es de Alpine casi literal. readline sale MAL
ENLAZADA por diseño de upstream —libreadline.so no enlaza contra ncurses salvo
que se lo fuerces, y queda con tgetent y compañía sin resolver—; Alpine lo
arregla con un parche y con un check que falla si el NEEDED de ncurses no
aparece. Acá se consigue sin parche con SHLIB_LIBS=-lncursesw, que es la perilla
que el propio Makefile expone. El guardián verifica que quedó enlazada, porque
si no el fallo reaparece en el consumidor, a un eslabón de distancia y sin
nombrar a esta receta.

La dep es ncurses-shared y no ncurses: con la estática, libreadline.so.8 se
llevaría los objetos de libncursesw.a dentro en vez de declarar el NEEDED — la
misma lección que el cairo estático de waterfox.

Se promueve además sqlite-shared de incoming-gnome al catálogo canónico: python3
la necesita como dep de ejecución y una receta de recipes/ no ve las de una
cola. Sin variantes en conflicto y ya sellada, así que la promoción no cuesta un
build.
2026-09-06 22:27:59 +00:00
Sergio 54729e01e8 estado: cosecha granja 2026-09-06T22:02:09Z — avance del árbol KDE 2026-09-06 22:02:09 +00:00
Sergio 15639dd9a6 estado: cosecha granja 2026-09-06T21:31:39Z — avance del árbol KDE 2026-09-06 21:31:39 +00:00
Sergio 1ac1ed81bd estado: cosecha granja 2026-09-06T21:02:07Z — avance del árbol KDE 2026-09-06 21:02:08 +00:00
Sergio 387f4e9256 estado: cosecha granja 2026-09-06T20:33:04Z — avance del árbol KDE 2026-09-06 20:33:04 +00:00
Sergio 5b9777bdbd declarar gcc-libs como dep de EJECUCIÓN en las seis — y el navegador arranca
Las seis recetas que el audit daba por colgantes declaran ahora
`runtime = ["gcc-libs"]`. `deps.runtime` NO entra en el ArtifactHash —medido:
firefox sigue en b3:8116bdec, atuq en b3:f2960991, waterfox en b3:88b5a762—
así que esto NO reconstruye nada. Lo que cambia es la CLAUSURA, que es lo que se
hidrata en la imagen.

EVIDENCIA DE QUE ARREGLA EL FALLO REAL, no de que compila: `atuq` bajo sway
headless, sin GPU, renderizando about:buildconfig con sus tablas y la barra de
contenedores de la v0.5. Antes moría con decenas de «Error relocating:
_ZNKSt5ctypeIcE13_M_widen_initEv: symbol not found».
docs/evidencia/atuq-arranca-con-gcc-libs-2026-09-06.png

Y el audit se corrige, porque SUB-REPORTABA en dos formas:

  · Miraba sólo ejecutables y `head -12` por artefacto. El siguiente NEEDED que
    faltaba estaba en una LIBRERÍA (libmozsandbox.so pedía libnspr4.so), así que
    daba «6 recetas» cuando el artefacto de firefox declara 30 sonames. Ahora
    recorre TODOS los ELF.
  · No contaba lo que el propio artefacto TRAE. Firefox bundlea su nspr/nss y
    las encuentra por el LD_LIBRARY_PATH que fija su lanzador; contarlas como
    colgantes era un falso positivo.

Con las dos correcciones y gcc-libs en el store, el corpus baja de 25 artefactos
colgantes a TRES, y son otra cosa:

    sqlite-shared   libreadline.so.8
    python3         libreadline.so.8
    naabu           libdl.so.2 libpthread.so.0 libc.so.6

Los dos primeros piden una receta que falta (readline). El tercero pide sonames
de GLIBC, que en una distro musl es un síntoma distinto y merece su propia
mirada. Quedan anotados, no arreglados.
2026-09-06 20:09:10 +00:00
Sergio 22bf487b1a gcc-libs: las librerías de ejecución que faltaban (b3:aa605af4)
libstdc++.so.6 (20.850.968 bytes) y libgcc_s.so.1 (824.776), construidas desde
el tarball de GCC 15.2.0 — la MISMA versión del lab, porque firefox se enlaza
contra la libstdc++ compartida de ese gcc y otra versión traería de vuelta el
mismo fallo disfrazado. sha512 del tarball verificado byte a byte contra el
APKBUILD de Alpine.

Tres decisiones que no son de estilo:

  --disable-symvers   Alpine lo pasa. El versionado cambia la tabla de símbolos;
                      nuestros binarios se enlazaron contra la libstdc++ SIN
                      versionar del lab.
  link = "dynamic"    hammer inyecta LDFLAGS=-static por defecto y acá el
                      producto ES una librería compartida: con el default no
                      saldría ni un .so. Misma perilla que apagaba los threads
                      de openssl, vista desde el otro lado.
  --disable-bootstrap Sólo `all-target-libgcc all-target-libstdc++-v3`. GCC
                      entero multiplica el tiempo por cinco para tirar el
                      compilador a la basura.

Los guardianes se ganaron el sueldo en las dos primeras corridas:

  1ª  «!! falta libstdc++.so.6» — y las librerías estaban perfectas: GCC instala
      en /usr/lib64 porque su t-linux64 fija MULTILIB_OSDIRNAMES=../lib64, y
      --disable-multilib NO lo apaga. Un .so impecable en el directorio que el
      loader no mira es EXACTAMENTE el fallo que esta receta viene a arreglar,
      un directorio más allá. Se normaliza a lib en el install.
  2ª  «!! no definen los símbolos» — y sí los definían. --disable-symvers apaga
      el versionado de libstdc++, pero libgcc tiene su PROPIO libgcc.map y
      versiona igual: nm los imprime como _Unwind_Backtrace@@GCC_3.3 y el grep
      anclado al fin de línea no los veía. El fallo era del guardián. Verificado
      contra el libgcc_s.so.1 DEL LAB —el que satisface a firefox hoy— y
      coinciden hasta en la etiqueta de versión.

gmp/mpfr/mpc ya existían pero sólo en recipes/incoming-*, con DOS variantes cada
una. Promovidas las de incoming-kde y no las de cosmic: la terna tiene que ser
internamente coherente porque mpc enlaza su .so contra las otras dos, y con las
de cosmic (estáticas sin PIC) muere en «relocation R_X86_64_32 ... recompile
with -fPIC». La de kde va con link=dynamic las tres y además ya estaba SELLADA
entera, así que la promoción no costó ni un build.
2026-09-06 20:08:53 +00:00
Sergio c5df480b36 estado: cosecha granja 2026-09-06T20:03:27Z — avance del árbol KDE 2026-09-06 20:03:27 +00:00
Sergio 4219d412ee estado: cosecha granja 2026-09-06T19:32:17Z — avance del árbol KDE 2026-09-06 19:32:17 +00:00
Sergio 2be672415c audit-needed: cazar deps de ejecución colgantes — firefox no arranca en la distro
Encontrado corriendo el navegador, no leyendo. Lancé `atuq` bajo el sway
headless y murió con decenas de «Error relocating: _ZNKSt5ctypeIcE...: symbol
not found». El binario SELLADO de firefox declara:

    NEEDED libstdc++.so.6      NEEDED libgcc_s.so.1      NEEDED libc.so

y las dos primeras NO EXISTEN en el store ni en ningún cierre hidratado: sólo en
.dev-fs/alpine, o sea en el LAB. Es exactamente la lección que la memoria
`quitar-dep-no-apaga-funcion` describe: el proyecto la encuentra en el sysroot
del lab y el artefacto sella con un NEEDED que nadie provee.

LO QUE LO HACE CARO ES QUE NINGUNA MÉTRICA LO VE. build-state.json dice
`sealed`; verificar-repro.sh dice que REPRODUCE bit a bit; los tres guardianes de
la receta pasan (RLBox en el binario, MOZ_REQUIRE_SIGNING, BuildID determinista)
— y el binario no arranca fuera del lab. El verde responde «¿construye?», nunca
«¿corre?».

El audit recorre el store, indexa qué sonames PROVEE el corpus y resta los NEEDED
de cada ELF. No mira el lab a propósito: mirarlo diría que todo está bien, que es
justo la ilusión que causó el problema.

Resultado de hoy: 25 artefactos colgantes, 6 recetas distintas —atuq, firefox,
waterfox, librsvg, spidermonkey, mesa-llvmpipe— y sólo DOS sonames faltan en todo
el corpus. O sea que falta UNA receta que provea las libs de runtime de gcc.

Gravedad: firefox y atuq son raíces de los CUATRO perfiles de escritorio
(cosmic, gnome, kde, sway), así que hoy las cuatro imágenes llevan un navegador
que no puede arrancar. La receta de firefox razona sobre esto en su comentario
—«clang++ de Alpine usa la libstdc++ COMPARTIDA ⇒ el NEEDED existe»— pero cierra
el requisito de BUILD y deja abierto el de EJECUCIÓN. hammer tiene
`[deps] runtime` y ninguna de las seis lo declara.
2026-09-06 19:23:02 +00:00
Sergio 04a5ff4be9 estado: cosecha granja 2026-09-06T19:03:28Z — avance del árbol KDE 2026-09-06 19:03:29 +00:00
Sergio c13410428e estado: cosecha granja 2026-09-06T18:32:26Z — avance del árbol KDE 2026-09-06 18:32:26 +00:00
Sergio 98d0ad49cc estado: cosecha granja 2026-09-06T18:02:12Z — avance del árbol KDE 2026-09-06 18:02:12 +00:00
Sergio 0aa0ab6e48 estado: cosecha granja 2026-09-06T17:02:08Z — avance del árbol KDE 2026-09-06 17:02:08 +00:00
Sergio b2df1fbac2 estado: cosecha granja 2026-09-06T16:32:26Z — avance del árbol KDE 2026-09-06 16:32:26 +00:00
Sergio e88e4ab3d7 SDD 26 §3.ter: el muro del display del PGO está despejado, con evidencia
El PGO necesita correr el navegador para juntar el perfil, y las distros lo
hacen bajo xvfb-run. Nosotros no tenemos X11 (Wayland-only), así que el plan
decía «la salida es un sway headless». Ya no es plan: se corrió.

sway con WLR_BACKENDS=headless y WLR_RENDERER=pixman arranca en gioser —un LXC
SIN /dev/dri, o sea sin GPU ninguna— y dibuja píxeles reales. La captura va como
evidencia: sway 1.10 / wlroots 0.18.2 / foot 1.27.0, los tres binarios nuestros,
salida HEADLESS-1 a 1280x720. Ciclo completo ~2 min.

Dos cosas que costó y quedan escritas para no repetirlas:

· Hidratar BAJO el montaje del store. hydrate-profile.py enlaza, y un hardlink
  no cruza montajes: con --into work/... sale EXDEV porque el store está en otro
  disco. Con --into store/.rootfs/sway son 193/193 nodos y 4,9 G que no ocupan.

· Inyectar el loader de musl. sway salió DINÁMICO y pide
  /lib/ld-musl-x86_64.so.1, que el cierre no trae. El error es
  «exec: /usr/bin/sway: not found», que se lee como «falta el binario» cuando lo
  que falta es su INTÉRPRETE. En musl el libc.so ES el loader, así que se
  resuelve con un --ro-bind. Misma familia que el resto de la noche: el mensaje
  nombra lo que buscó, no lo que falta.

Queda el segundo muro del PGO, que es de diseño y no de infra: el profdata no es
determinista, así que hay que generarlo UNA vez y sellarlo como artefacto propio
consumido por hash.
2026-09-06 16:09:24 +00:00
Sergio ff9dffaff6 estado: cosecha granja 2026-09-06T16:02:19Z — avance del árbol KDE 2026-09-06 16:02:19 +00:00
Sergio 1d2f7b7686 SDD 26: waterfox también reproduce bit a bit
`scripts/verificar-repro.sh recipes/waterfox.toml`: REPRODUCEN 1, DERIVA 0,
NO-DETERMINISMO 0.

Con esto el guardián del BuildID queda probado en las DOS direcciones que exige
la doctrina del repo, y no por diseño sino porque los hechos cayeron así: cazó
una rotura real —el artefacto anterior selló en verde con BuildID=20260906062042,
la hora del build— y pasa un control positivo, que es el arreglado reproduciendo
de verdad. Un guardián con sólo la primera mitad no se distingue de uno que mata
siempre; con sólo la segunda, de uno que no mira nada.

Los dos Gecko del corpus (firefox con RLBox y waterfox) reproducen.
2026-09-06 16:00:36 +00:00
Sergio 349da65086 SDD 26: el firefox con RLBox REPRODUCE bit a bit
`scripts/verificar-repro.sh recipes/firefox.toml`: reconstruido y comparado
contra el sellado da REPRODUCEN 1, DERIVA 0, NO-DETERMINISMO 0.

No es un trámite. Encender RLBox mete una cadena de compilación ENTERA nueva
—wasi-libc, libc++ a wasm32, los builtins, wasm2c traduciendo a C— dentro del
build de firefox, y la duda razonable era si algo de eso metía una fecha, un
orden de tabla hash o una ruta absoluta. No lo hace.

Es además la distinción que esta misma noche costó cara en la receta de al lado:
waterfox SELLÓ EN VERDE sin reproducir, porque el ArtifactHash es
input-addressed y no se mueve por un BuildID que sea la hora del build. «Selló»
y «está bien» son dos preguntas distintas, y la segunda cuesta un comando.
2026-09-06 15:24:20 +00:00
Sergio 100ecc9211 estado: cosecha granja 2026-09-06T15:02:24Z — avance del árbol KDE 2026-09-06 15:02:24 +00:00
Sergio 96c71de490 estado: cosecha granja 2026-09-06T14:32:06Z — avance del árbol KDE 2026-09-06 14:32:06 +00:00
Sergio 00d305de2f estado: cosecha granja 2026-09-06T14:02:14Z — avance del árbol KDE 2026-09-06 14:02:14 +00:00
Sergio f0b8d9025e estado: cosecha granja 2026-09-06T13:32:11Z — avance del árbol KDE 2026-09-06 13:32:11 +00:00
Sergio 757f4fb26d estado: cosecha granja 2026-09-06T13:02:08Z — avance del árbol KDE 2026-09-06 13:02:08 +00:00
Sergio 8dc8bb3a3e estado: cosecha granja 2026-09-06T12:32:22Z — avance del árbol KDE 2026-09-06 12:32:22 +00:00
Sergio b086382c44 estado: cosecha granja 2026-09-06T11:31:54Z — avance del árbol KDE 2026-09-06 11:31:54 +00:00
Sergio 384d9fe793 estado: cosecha granja 2026-09-06T11:02:18Z — avance del árbol KDE 2026-09-06 11:02:18 +00:00
Sergio 624745ed5f estado: cosecha granja 2026-09-06T10:31:54Z — avance del árbol KDE 2026-09-06 10:31:54 +00:00
Sergio 5755c6c401 estado: cosecha granja 2026-09-06T10:02:22Z — avance del árbol KDE 2026-09-06 10:02:22 +00:00
Sergio 70ca353653 estado: cosecha granja 2026-09-06T09:02:08Z — avance del árbol KDE 2026-09-06 09:02:08 +00:00
Sergio 8149036e2a estado: cosecha granja 2026-09-06T08:31:56Z — avance del árbol KDE 2026-09-06 08:31:56 +00:00
Sergio 00bee2645c estado: cosecha granja 2026-09-06T08:01:54Z — avance del árbol KDE 2026-09-06 08:01:54 +00:00
Sergio 7e9c43975e estado: cosecha granja 2026-09-06T07:31:52Z — avance del árbol KDE 2026-09-06 07:31:52 +00:00
Sergio 25934f52f3 SDD 26: waterfox sella — unidad 1 cerrada, y el corpus con cero deuda
b3:88b5a762, 377 M, 43 ficheros, BuildID=19700101000001 (determinista, con
guardián). Con esto el grafo queda 850/852 sellados y los dos que faltan son
`ajeno` (steam-runtime-sniper, xwayland), que son frontera y no deuda.

Los cuatro muros que costó, y ninguno era de la receta:

  1. Moría en 4 s por un submódulo git — era el binario de hammer del worker,
     13 h más viejo que el arreglo que los materializa.
  2. El linker moría sin mensaje — era el watchdog del worker-loop barriendo el
     objdir a los 2 minutos, antes de que nadie pudiera mirarlo.
  3. `multiple definition` de cairo — la receta traía la lista de deps anterior
     a la migración de firefox a las variantes -shared.
  4. Iconos faltantes al empaquetar — inconsistencia interna de Waterfox: el
     manifiesto pide ocho tamaños y branding-common.mozbuild instala cinco.
     Rotura sólo de Linux.

Y una quinta que el sello NO habría delatado: selló primero con
BuildID=20260906062042, o sea sin reproducir, y en VERDE.
2026-09-06 07:02:58 +00:00
Sergio 87a86c1e45 estado: cosecha granja 2026-09-06T06:32:04Z — avance del árbol KDE 2026-09-06 06:32:04 +00:00
Sergio 699bbe8475 waterfox: BuildID determinista — SELLÓ EN VERDE sin reproducir
waterfox selló (b3:e65c0221, 377 M, 43 ficheros) y el artefacto NO reproduce:

    BuildID=20260906062042

o sea la hora del build. Y selló en VERDE, que es lo peor: el ArtifactHash es
input-addressed y no se mueve por esto, así que build-state.json lo contaba como
bueno. Es EXACTAMENTE el modo de fallo que el comentario de firefox.toml
describe desde ayer —«el artefacto deja de reproducir SIN QUE NADA LO DIGA»—
ocurriendo en la receta de al lado por no haber copiado cuatro líneas.

Se exporta MOZ_BUILD_DATE en las TRES fases (son shells distintos y mach
re-ejecuta configure) y se pone el mismo guardián que firefox: si el BuildID no
es el que obliga SOURCE_DATE_EPOCH, el build muere.

Quedan escritas en la receta otras dos cosas medidas en el application.ini del
sellado, que son decisión y no bug, y por eso se documentan en vez de cambiarse:

  1. El branding «oficial» de ESTE commit es el de MOZILLA, no el de Waterfox.
     El comentario de la receta afirmaba lo contrario. Comprobado en el árbol:
     branding/official/configure.sh pone MOZ_APP_DISPLAYNAME=Firefox, brand.ftl
     da -brand-full-name = Mozilla Firefox, y el ID es el canónico de Firefox.
     Hoy esto sella un binario parcheado que se presenta como «Mozilla Firefox»,
     justo lo que firefox.toml evita yendo sin marca. La salida probable es
     `unofficial`, pero es política de marcas, no una perilla.

  2. Colisiona de ruta con firefox: instala en usr/lib/firefox/ y
     usr/bin/firefox porque no se fija MOZ_APP_NAME. Hoy no rompe nada porque
     ninguna imagen incluye las dos; el día que una lo haga, las dos capas
     overlay se pelean el mismo fichero y gana una en silencio.
2026-09-06 06:23:47 +00:00
Sergio c6e5a0b803 estado: cosecha granja 2026-09-06T06:02:13Z — avance del árbol KDE 2026-09-06 06:02:13 +00:00
Sergio cabb106c93 waterfox: instalar los tres iconos que su propio empaquetador exige
Con las deps -shared el enlace de libxul.so YA PASA (el choque de cairo se
acabó) y el build avanza hasta el empaquetado, donde muere así:

    error: browser/installer/package-manifest.in:239: Missing file(s):
           bin/browser/chrome/icons/default/default22.png
                                        :240: default24.png
                                        :245: default256.png

No falta arte y no es culpa nuestra: es una inconsistencia INTERNA del árbol de
Waterfox 6.7.1.1. Bajo `#ifdef MOZ_GTK` el manifiesto pide ocho tamaños (16, 22,
24, 32, 48, 64, 128, 256) y la rama gtk de branding-common.mozbuild lista sólo
cinco. Los tres ficheros existen en los CUATRO brandings del árbol
(official/aurora/nightly/unofficial); lo que falta es la línea que los instala.

Es una rotura sólo de Linux, que es la razón plausible de que upstream no la
note: la rama de Windows del mismo fichero está completa.

El hunk está GENERADO con `diff -u` sobre el fichero real, no escrito a mano: la
primera versión aplicaba «with fuzz 2» y la segunda «with fuzz 1». Un parche con
fuzz es uno que puede agarrar donde no debe, y verificar que aplica limpio cuesta
un `patch --dry-run`.

Va en recipes/waterfox-patches/ y no en firefox-patches/ porque es de waterfox:
firefox no lo necesita.
2026-09-06 05:44:09 +00:00
Sergio 800c80a8f7 estado: cosecha granja 2026-09-06T05:32:13Z — avance del árbol KDE 2026-09-06 05:32:14 +00:00
Sergio 0ef8320c6c waterfox: las variantes -shared de la cadena GUI, que es lo que mataba el enlace
El build llegaba al minuto 36 y moría enlazando libxul.so con un `collect2:
error: ld returned 1 exit status` y NADA delante — ni undefined reference, ni
cannot find -l, ni DSO missing. No era OOM ni disco. El filtro de salida de mach
clasificaba y descartaba justo el diagnóstico.

Rehecho el mismo objetivo llamando a gmake directo, el linker habló:

    ld: /usr/lib/libcairo.a(cairo-ft-font.c.o): in function `_cairo_ft_to_cairo_error':
        multiple definition of `_cairo_ft_to_cairo_error';
        gfx/cairo/cairo/src/cairo-ft-font.o: first defined here

…y así decenas de símbolos de cairo-ft-font, cairo-pdf-surface y
cairo-toy-font-face. O sea: el libcairo.a ESTÁTICO de la dep entra al enlace por
el -lcairo que arrastra el .pc de gtk3, y sus objetos chocan con el cairo
BUNDLEADO del propio árbol de Gecko.

La receta traía la lista de deps ANTERIOR a la migración de firefox: cairo,
pango, gdk-pixbuf y harfbuzz estáticas donde firefox ya usa las -shared. Y el
propio firefox.toml lo tiene escrito desde el 2026-09-05: declarar las estáticas
junto a un gtk3 que trae las compartidas son dos artefactos peleando por el
mismo .pc, «van las dos listas al mismo sitio o ninguna». Waterfox se quedó a
mitad de camino.

Con la variante compartida, -lcairo resuelve a libcairo.so y los símbolos
bundleados se quedan donde tienen que quedarse.

NO se tocan clang18/llvm18: firefox los quitó porque tapaban el LLVM 22 del lab
y con ThinLTO el bitcode no es compatible hacia atrás; acá el compilador es gcc,
así que ese razonamiento no aplica y cambiarlo sería otra unidad de trabajo.

Se deja puesta la rama de diagnóstico del compile: si mach falla, rehace el
enlace con gmake directo para que el linker hable. No cuesta nada en el camino
bueno (`&& exit 0`) y convirtió un fallo indiagnosticable en uno diagnosticado
en una sola corrida.
2026-09-06 05:03:57 +00:00
Sergio 96bfa8fe5d estado: cosecha granja 2026-09-06T05:02:07Z — avance del árbol KDE 2026-09-06 05:02:07 +00:00
Sergio 409eab1ffe estado: cosecha granja 2026-09-06T04:33:26Z — avance del árbol KDE 2026-09-06 04:33:26 +00:00
Sergio dc1391e611 siembra: anclar la exclusión de PNG — sin anclar rompe las recetas que traen PNG
La siembra excluía `*.png` sin anclar, o sea PNG en CUALQUIER sitio, incluido
recipes/. Dos consecuencias y las dos malas:

  a) rsync PROTEGE DE BORRADO lo que excluye. Un directorio cuyo único resto es
     un .png no se puede vaciar y por lo tanto no se puede borrar nunca, aunque
     el hub lo haya eliminado. Medido: recipes/atuq/extension/atuq128.png
     sobrevivió a un rename hecho en el hub, y el log del latido venía repitiendo
     `cannot delete non-empty directory: recipes/atuq/extension` sin que nadie lo
     leyera como un error.
  b) Un PNG NUEVO de una receta tampoco viaja. Y hay recetas cuyo `[source] dir`
     lleva PNG dentro: los 9 iconos de recipes/atuq/branding/icons/.

El efecto medido: `atuq` daba b3:f2960991 en el hub y b3:b33e81a5 en el worker,
por ESE único fichero de más. El worker no podía coincidir con el hub ni
construyendo bien.

Lo bueno es que no era silencioso, y por diseño: hammer SÍ hashea el árbol de un
`[source] dir` (recipe.rs, `dir:<hash>`), así que la divergencia sale como otro
ArtifactHash en vez de como dos bytes distintos en la misma dirección. Es
exactamente la propiedad que le falta al lab y a la versión de hammer.

Lo que se quería ahorrar eran las capturas: docs/evidencia/ (4,4 M) y el PNG
suelto de la raíz (1 M). Eso ahora se excluye POR RUTA. Los PNG de recipes/
viajan, que es lo correcto: ahí son fuente, no adorno.

Borrado el fichero rancio del worker; hub y worker vuelven a coincidir en
b3:f2960991.
2026-09-06 04:29:43 +00:00
Sergio 1578eb16f5 estado: cosecha granja 2026-09-06T04:02:18Z — avance del árbol KDE 2026-09-06 04:02:19 +00:00
Sergio 7acdc5489b estado: cosecha granja 2026-09-06T03:32:07Z — avance del árbol KDE 2026-09-06 03:32:07 +00:00
Sergio b6a35bc2bc worker-loop: que el watchdog no se lleve el post-mortem
El watchdog barre work/sources/* cada 180 s con un suelo de 2 minutos, así que
el árbol de una receta que ACABA DE FALLAR se destruye antes de que nadie lo
mire. Medido hoy: waterfox murió a las 03:20:15 tras 36 minutos y 23.980 pasos,
enlazando libxul.so con un `collect2: error: ld returned 1 exit status` SIN
mensaje del linker delante — o sea justo el caso en que hace falta el objdir. A
las 03:22 work/sources/ estaba vacío. Reproducirlo cuesta otros 36 minutos.

Lo que más molesta es que la doctrina ya estaba escrita en el repo, en dos
sitios, y este watchdog la contradecía sin que nadie lo notara:

  campana-deuda.sh:138  «NO se poda tras un ✗: el árbol de una receta que falló
                         es el post-mortem»
  poda-fuentes.sh       suelo de 24 h, «deja el post-mortem del día»

No se arregla subiendo el suelo a secas: este watchdog existe para que una tanda
Go no llene el disco vendoreando, y ahí los minutos importan de verdad (80 G en
una tanda, con el I/O-wait disparando el load). Se arregla siendo agresivo SÓLO
cuando el recurso escasea: con el disco por debajo de DISK_HIGH el suelo pasa a
SUELO_FRIO_MIN (120 min por defecto, configurable); en cuanto el disco aprieta
vuelve a los 2 minutos de siempre. Un árbol frío con disco al 37% no le hace
daño a nadie.

Probado en los dos sentidos: árbol de 5 min -> se borra con suelo 2, protegido
con suelo 120; árbol de 3 h -> se borra con los dos (ya no es post-mortem).

Ojo al leerlo: `df --output=pcent` da el porcentaje USADO, no el libre. La
primera versión de este parche llamaba `libre` a esa variable.
2026-09-06 03:30:25 +00:00
Sergio dbd8c65282 estado: cosecha granja 2026-09-06T03:02:22Z — avance del árbol KDE 2026-09-06 03:02:22 +00:00
Sergio 7638e4e2be worker-loop: recompilar hammer POR CICLO, no sólo al arrancar
El bloque de rebuild que ya existía corre una sola vez, al arrancar el loop, y
este loop vive días — hoy el worker llevaba 8 de uptime. El source SÍ sigue
llegando fresco cada 30 min (la siembra rsync-ea el repo), así que el worker
acaba con FUENTE NUEVO y BINARIO VIEJO.

Eso es peor que el fósil de la golden que motivó el bloque original, porque nada
lo delata: la versión de hammer NO entra en el ArtifactHash, así que el build se
comporta distinto y build-state.json sigue en verde. Misma familia que el lab
fuera de hash_inputs, sin siquiera el aviso del lock.

Caso real que lo motiva, de hoy: el worker corría un hammer de las 04:54 y el
arreglo que materializa los submódulos git había entrado a las 15:11 (114aeba).
`waterfox` moría en CUATRO SEGUNDOS por waterfox/browser/locales/moz.build
inexistente, y el diagnóstico apuntaba a la receta, a los once parches de musl y
al --filter=blob:none: a todo menos al binario. Lo resolvió `strings` sobre los
dos binarios en un minuto — hub 2 cadenas de .gitmodules/gitlink, worker 0.
Recompilado el worker (23 s) y borrado el árbol de fuentes viejo, waterfox pasa
el punto donde moría y el submódulo se materializa.

Se comprueba por mtime y no se recompila a ciegas: cargo cacheado son ~24 s,
pero correrlo cada IDLE_SLEEP sin motivo es ruido en el log y CPU que le estamos
quitando a un build. Si nada cambió cuesta un `find`. Probado en los dos
sentidos (fuente más nuevo -> detecta; binario más nuevo -> no hace nada).
2026-09-06 02:46:28 +00:00
SergioandClaude Opus 5 ee1b6eb0df SDD 26: el conteo de símbolos w2c_ estaba inflado — 634, no 1547
`llvm-nm --defined-only | grep -c w2c_` cuenta también los nombres C++
mangleados que llevan `w2c_` en el MEDIO —`_ZN5rlbox...PK16w2c_mem_capacity...`
son 913 de los 1547—, no sólo los símbolos de la jaula wasm. Anclado
(`grep -c " w2c_"`, o `awk '{print $3}' | grep -c "^w2c_"`) da 634, que es lo
que mide el guardián de recipes/firefox.toml y lo que el documento quería decir.

La conclusión no se mueve: la base sin RLBox da 0 con cualquiera de los dos
patrones, y el derivado hereda los mismos 634 que la base. Lo que se corrige es
la afirmación, que decía «símbolos w2c_*» y contaba otra cosa: un número sin su
patrón no es una medición.

Lo cazó hammer-03 al reproducir el número desde su lado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 02:46:07 +00:00
SergioandClaude Opus 5 69425b0600 atuq v0.5 rehecha sobre el firefox con RLBox, y la dep medida en vez de creída
`firefox` selló con RLBox (b3:8116bdec) y `atuq` se reconstruyó encima en 2
segundos, que es exactamente lo que el §2 prometía del camino derivado: el
overlay no cambió una línea por un rebuild del motor.

Comprobado que la base nueva es la que quedó DENTRO del derivado, no sólo
declarada: el `libxul.so` de atuq trae 1547 símbolos `w2c_*` donde la base
anterior (352d7880) traía 0. Y la capa de v0.5 sigue entera sobre ella — los
cuatro contenedores, las dos extensiones y el mapa del proxy en un arranque
real.

De paso, una promesa del documento que convenía no creer sin medir: que si sube
firefox, atuq se reconstruye solo. Se midió en un catálogo de sonda (una copia
de atuq.toml + su árbol + firefox.toml, resuelto por el fallback al catálogo
padre): con el firefox idéntico al del corpus, atuq da el mismo hash; cambiando
una bandera de ese firefox copiado, atuq pasa a b3:60ade76a. La dep entra en
hash_inputs, así que un atuq viejo no puede quedarse tapando un motor nuevo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 02:41:11 +00:00
Sergio 1f4d2dfc05 estado: cosecha granja 2026-09-06T02:36:18Z — avance del árbol KDE 2026-09-06 02:36:18 +00:00
Sergio 9a2a55a4c8 SDD 26: la cadena wasm reproduce byte a byte entre el hub y el worker
Las cinco recetas se construyeron por separado en las dos máquinas —gioser (4c)
y el LXC dev.gioser.net (6c)— y los cinco artefactos salen idénticos comparando
el árbol completo, no sólo el ArtifactHash.

No es un trámite. El lab NO entra en hash_inputs, así que dos labs distintos
pueden sellar bytes distintos en la MISMA dirección del store y nada lo detecta:
la igualdad de hashes no probaba nada por sí sola. Esto dice que para esta
cadena los dos labs coinciden de verdad.

Sale gratis de haber construido en las dos máquinas por otro motivo (el hub no
tenía wasi-libcxx y sin él no podía correr scripts/test-guardianes-wasm.sh).
2026-09-06 02:16:54 +00:00
SergioandClaude Opus 5 98884c785a SDD 26 §6.8: el intento de probar el paquete, y los tres muros que encontró
La v0.5 dejó dicho que faltaba probar que una petición hecha en «Banco» sale
por el proxy de «Banco». Se intentó y no se llegó; queda escrito el intento
porque el muro es reusable y el que venga después no tiene por qué volver a
descubrirlo.

El diseño de la prueba sirve y no necesita red ni servidor: la MISMA url a un
puerto vacío desde dos contenedores, y comparar los errores. `connectionFailure`
contra `proxyConnectFailure`. Que sean DISTINTOS es la prueba, porque el segundo
sólo aparece si Gecko habló con un proxy y el único que se lo pudo indicar es
nuestro proxy.onRequest mirando el cookieStoreId.

Lo que falta es abrir una pestaña EN un contenedor, y las tres puertas están
cerradas:

1. Desde la línea de comandos no existe el flag.
2. Marionette SÍ está en el artefacto y responde —se le habló con un cliente
   propio de 60 líneas, el protocolo es `<longitud>:<json>`, abrió sesión y
   aceptó comandos—, pero el contexto chrome exige `-remote-allow-system-access`
   y en la jaula no lo tomó ni por bandera, ni con --remote-debugging-port, ni
   por MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1, que es lo que RemoteAgent.sys.mjs lee
   en su constructor. Comprobado que la variable llega al proceso.
3. Desde la extensión, `tabs.create({cookieStoreId})` exige el permiso
   `cookies` (ext-tabs-base.js:getUserContextIdForCookieStoreId). Darle a la
   extensión del proxy acceso a TODAS las cookies del usuario para poder
   probarse a sí misma sería pagar con la superficie de ataque del producto una
   comodidad del test. No se hace, y queda dicho por qué.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 02:06:52 +00:00
Sergio 4fae3ee1ff prueba que los guardianes de la cadena wasm sirven — con CONTROL
Un guardián que nunca falló no se sabe si sirve, y uno que falla SIEMPRE se ve
idéntico a uno que funciona. Por eso el control con el sysroot intacto —que
tiene que PASAR— no es un extra: es la mitad de la prueba. El método lo aportó
la sesión hammer-f8 (scripts/test-atuq-politica.py hace lo mismo con el cruce
política<->XPI).

Le pone delante a la cadena las cuatro formas conocidas de romperla —sin la
sonda include/c++/v1, sin cstring, sin libc++.a, sin libc.a— y exige que las
cuatro maten, usando la invocación EXACTA del configure de firefox dentro del
lab. No construye nada, no toma work/.farm-build.lock y no escribe en el store:
trabaja sobre copias temporales de artefactos ya sellados, así que corre con la
granja a pleno.

Y el control se ganó el sueldo en su primera corrida: salió en ROJO. La causa no
era un guardián flojo sino este script, que resolvía el artefacto con
 y agarraba el VIEJO —el de antes de que la
receta creara el directorio-sonda—. Misma trampa que el cache-hit que congela
regresiones: un artefacto anterior se hace pasar por el actual y la prueba mide
otra cosa. Sin el control se habría leído como «4 en verde, guardianes ok».

Ahora el artefacto se resuelve por  de la receta, nunca por glob.
5 en verde, 0 en rojo.
2026-09-06 02:02:34 +00:00
Sergio 292df59095 estado: cosecha granja 2026-09-06T02:02:22Z — avance del árbol KDE 2026-09-06 02:02:22 +00:00
Sergio 602233b8a5 CLAUDE.md regla 2: acotar el COMMIT por pathspec, no sólo el add
Medido hoy, y contra mi propia metida de pata: el commit ff0b556 se llevó dentro
un rename y un borrado de otro agente HABIENDO usado `git add <ruta explícita>`
y `git commit` sin -a. O sea, cumpliendo la regla al pie de la letra.

La causa es que el índice es estado COMPARTIDO entre los agentes que trabajan el
árbol: `git commit` commitea el índice entero, no lo que uno acaba de añadir.
Comprobado en un repo de juguete en los dos sentidos:

  git add mio.txt && git commit -m …   -> arrastra el ajeno.txt que el otro tenía staged
  git commit -m … -- mio.txt           -> sólo mio.txt; lo del otro queda staged e intacto

La regla decía «sólo rutas explícitas» y esa frase apunta al `add`, que no es
donde está el peligro. Queda apuntando al `commit`, que es donde sí.

Lo levantó la sesión hammer-f8 al ver sus ficheros dentro de mi commit.
2026-09-06 02:00:05 +00:00
SergioandClaude Opus 5 c5ebbda933 atuq v0.5: proxy por contenedor — el primer diferenciador del §6 que se paga entero
Es el único de la lista del SDD 26 §6 que no pasa por el host de native
messaging del §7: es API de Firefox y nada más. Cada contenedor —Personal,
Trabajo, Banco, Compras— puede salir por su propio proxy.

Tres piezas y ninguna alcanza sola: `atuq.cfg` prende los contenedores (vienen
apagados), `policies.json` los CREA con `Containers.Default` —el único
mecanismo que los pone en un perfil NUEVO— y deja la config de fábrica en
`3rdparty.Extensions`, y `extensions/proxy/` los enruta con `proxy.onRequest`,
que es lo único que ve el `cookieStoreId` de la petición.

Las cuatro se comprobaron DENTRO del artefacto de firefox antes de escribir una
línea, no en la documentación de Mozilla: `Containers` y `3rdparty` en el
`policies-schema.json` de browser/omni.ja, `cookieStoreId` en el
`schemas/proxy.json` de omni.ja, y `storage.managed` leyendo
`getExtensionPolicy(id)` en ext-storage.js. Es la regla del §2.sexies: la
pregunta no es si Mozilla lo tiene, es si NUESTRO build lo tiene.

Tres decisiones que valen más que el código:

1. La config se indexa por NOMBRE de contenedor y no por `cookieStoreId`: el id
   depende del orden en que se crearon, así que la misma configuración aplicada
   a otro perfil apuntaría a otro contenedor.
2. FAIL CLOSED. Un contenedor con proxy configurado que no se pudo honrar no
   sale directo: va a un destino cerrado y el navegador muestra el error. Salir
   directo sería una fuga silenciosa — la misma familia que el artefacto vacío
   de la regla 3, el fallo que llega hasta el final diciendo que todo fue bien.
3. `proxyDNS` PRENDIDO por defecto: sin él la consulta DNS sale por la línea que
   se quería evitar. Es la fuga clásica de esta configuración.

Y lo que no promete está arriba de todo en la página de opciones, no en un pie:
separación de tráfico, NO anonimato; para anonimato, Tor Browser. El §4 cumplido
donde el usuario lo lee.

De paso, una verdad que estaba escrita en dos sitios pasa a tener un dueño: el
id de cada extensión sale ahora del `manifest.json` y no de una constante de
rebrand.py, el nombre del XPI se deriva de él, y el `install_url` de la política
se cruza contra la ruta donde el fichero quedó escrito de verdad. El icono se
inyecta desde branding/icons/ en vez de estar copiado byte a byte dentro de cada
extensión. Agregar una tercera extensión es ahora un directorio.

PROBADO, corriendo el árbol en la misma jaula que atuq-nested.sh:
  · captura de about:preferences#containers con los cuatro contenedores y sus
    iconos, más el containers.json del perfil;
  · extensions.json del perfil nombra las dos extensiones;
  · `console.info: "atuq/proxy: 4 contenedor(es) enrutado(s)"` — leyó la config
    de fábrica por storage.managed Y la casó con los contenedores de la política;
  · scripts/test-atuq-politica.py: cinco formas de desincronizar política y XPI,
    las cinco matan el build, y el control con la política intacta pasa.

NO probado y dicho por su nombre: que una petición hecha en «Banco» salga por el
proxy de «Banco». Pide automatizar la UI y queda pendiente.

Dos obstáculos del método, que valen para la próxima. La consola de una
extensión es CONTENIDO: `devtools.console.stdout.chrome` (que viene en true) no
la incluye, hace falta `...stdout.content`. Y un `moz-extension://` NO se abre
desde la línea de comandos —muere con `NS_NOINTERFACE [nsIFileURL.file]` y abre
la home en su lugar—, además de que `--screenshot` dispara al `load`, que puede
ocurrir antes de que arranquen las extensiones. Eso último destapó un fallo real
y arreglado: la página de opciones confundía «no hay contenedores» con «la API
no está» y mostraba un mensaje FALSO.

Verificado contra firefox b3:352d7880; se reconstruye contra el firefox con
RLBox cuando selle. La otra mitad de esta unidad —el rename de
recipes/atuq/extension/ a extensions/inicio/— entró sin querer en ff0b556, que
es de otro frente.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 01:57:44 +00:00
Sergio df4111c57d SDD 26: §3.quater — la cadena wasm y las tres cosas que costó
Deja escrito en el documento que se lee para reanudar: el orden obligatorio de
las cinco recetas, por qué wasi-libc-headers pinea un commit viejo a propósito, y
las tres trampas ya pagadas (sesgo de clang en check-symbols, el wasi-libcxx que
faltaba, y el directorio vacío que es una sonda del compilador).

Y el criterio de guardián que sale de todo esto, que es lo más reusable:
«existe» no es «se encuentra».
2026-09-06 01:51:20 +00:00
Sergio cddae46205 wasi-libcxx: el directorio VACÍO que hace visible a libc++ (y el guardián que lo prueba)
Segunda muerte del firefox con RLBox, otra vez a los 6 segundos y con el mismo
mensaje engañoso, ya con wasi-libcxx sellado:

    checking the wasm C compiler can find wasi headers... yes
    checking the wasm C++ compiler can find wasi headers...
    fatal error: 'cstring' file not found

El C pasaba todo y sólo fallaba el C++. Y `cstring` ESTABA en el artefacto, en
include/wasm32-wasip1/c++/v1/cstring. No era un fichero que faltara: era un
fichero que clang no buscaba.

CAUSA. El APKBUILD de Alpine hace un `mkdir -p .../include/c++/v1` que parece
ruido de empaquetado y yo descarté al transcribir. Es la SONDA por la que el
driver de clang decide que el sysroot tiene layout de libc++; sin ese directorio
no añade `include/<triple>/c++/v1` a la lista de búsqueda.

MEDIDO, no deducido. Sysroot fusionado (wasi-libc + wasi-libcxx) y la invocación
literal del configure de Mozilla, dentro del lab:

    clang++ -std=gnu++20 --target=wasm32-wasip1 conftest.cpp --sysroot=$S -c

    sin include/c++/v1  ->  fatal error: 'cstring' file not found
    con include/c++/v1  ->  exit 0

Es el reverso exacto de la regla 3 del CLAUDE.md: allá un directorio vacío es un
artefacto mentiroso, acá un directorio vacío es una declaración dirigida al
compilador. Queda documentado en la receta para que nadie lo borre por limpieza.

Y la lección que deja el guardián nuevo: los tests de presencia que tenía la
receta habrían dado esto por bueno, porque "existe" no es "se encuentra". Así
que ahora la receta corre LA MISMA PRUEBA que el configure de firefox, contra un
sysroot fusionado con el de su dep. Si pasa acá no puede fallar allá; y si falla,
falla en el segundo 20 de esta receta —imprimiendo la lista de búsqueda de
clang— y no en el segundo 6 de un build de cuatro horas que además culpa a otro.

Re-hashea: wasi-libcxx b3:1361deae -> b3:838a9557, y con él firefox.
2026-09-06 01:46:38 +00:00
Sergio 553fc1fb7d wasi-libcxx: el eslabón que faltaba en la cadena wasm (lo encontró el build)
El firefox con RLBox murió en el configure a los SEIS SEGUNDOS:

    /tmp/conftest.cpp:1:10: fatal error: 'cstring' file not found
    ERROR: Cannot find wasi headers or problem with the wasm compiler.

El mensaje culpa a los headers de WASI y los headers de WASI estaban perfectos.
`<cstring>` es un header de C++ y `wasi-libc` sólo trae la C: las librerías que
RLBox enjaula no son todas C —graphite2 es C++— así que el sandbox necesita una
biblioteca estándar de C++ para wasm32. Mismo patrón que la lección de los
headers UAPI: el error nombra lo que buscó, no lo que falta.

El hueco estaba en el GRAFO antes de que el build lo encontrara, y se podía ver
sin construir nada: el `wasi-sdk` de Alpine declara
`depends="wasi-libc wasi-libcxx wasi-compiler-rt"` —TRES— y nosotros teníamos
dos. Queda anotado como método: cuando se porta una cadena ajena, la lista de
depends del paquete equivalente es una comprobación de completitud gratis.

La receta es el mismo tarball de LLVM 22.1.8 que `wasi-compiler-rt` (mismo
sha256) y repite su transcripción del toolchain file de wasi-sdk, por el mismo
motivo: 38 líneas de declaraciones puras no justifican una fuente de red y una
receta. Si se toca una, se toca la otra.

Dos detalles que son trampas y no adorno:

- Se construyen los DOS triples (wasip1 y wasip1-threads). `wasi-libc` ya publica
  los dos y firefox pide el sysroot por nombre de triple, así que dejar uno sin
  libc++ sería presente-a-medias, que es peor que ausente.
- cmake instala en el triple canónico (`wasm32-unknown-wasip1`) y clang busca en
  el corto (`wasm32-wasip1`). Sin el renombre del install, libc++ queda al lado
  del sitio donde se la busca: en el artefacto e invisible para el compilador.

El guardián exige exactamente lo que el configure no encontró —`cstring`,
`libc++.a` y `libc++abi.a`— para los dos triples.

Re-hashea: wasi-sdk b3:c8a587fa -> b3:f13634c6, firefox b3:98d6899a ->
b3:2880e28a.
2026-09-06 01:42:34 +00:00
Sergio 65ee20e437 firefox: guardián que exige RLBox EN EL BINARIO, no en el configure
Tercera repetición de la misma lección en esta receta, y por eso nace junto con
la bandera y no después: `--with-wasi-sysroot` es exactamente la clase de opción
que el configure acepta sin chistar y que puede no llegar al artefacto. Ya pasó
dos veces acá (MOZ_REQUIRE_SIGNING y MOZ_BUILD_DATE) y las dos veces el síntoma
apareció lejísimos, sin nombrar la bandera.

Acá el fallo sería el más caro de los tres: un firefox que dice tener enjaulados
los parsers de fuentes y medios —graphite, ogg, expat, woff2, o sea el código
que come entrada NO confiable de la red— y no los tiene. Una promesa falsa de
seguridad es peor que no ofrecerla (§4 del SDD 26, mismo criterio que el modelo
de adversario de qullqa).

El discriminante está MEDIDO, no supuesto: sobre el artefacto b3:352d7880 —el
último con RLBox apagado, 224 MB de libxul.so— los tres patrones (w2c_, rlbox,
wasm2c) dan CERO. Así que cualquiera apareciendo prueba que la jaula entró.

Se aceptan los tres y no sólo `w2c_` a propósito: el prefijo exacto es un
detalle de la versión de wasm2c, y un guardián que falla por un cambio de
convención de nombres tira 4 h de build por algo que no está roto. Lo que no
puede pasar desapercibido es que los tres sigan en cero.

El método de comprobar el artefacto y no la doc lo aportó la sesión hammer-f8.

Re-hashea: b3:dba8fbf9 -> b3:98d6899a. Entra en el build que arranca ahora, que
es justamente para lo que se agrega antes y no después.
2026-09-06 01:39:20 +00:00
Sergio 1e11fd330b estado: cosecha granja 2026-09-06T01:32:12Z — avance del árbol KDE 2026-09-06 01:32:12 +00:00
Sergio ff0b5567d6 firefox: RLBox ENCENDIDO — --without-wasm-sandboxed-libraries -> --with-wasi-sysroot
Cierra la unidad 3.b del SDD 26. El flag que se va apagaba la jaula wasm que
envuelve a los parsers de fuentes y medios (graphite, ogg, expat, woff2), o sea
el código que come entrada NO confiable de la red. Estaba apagado por una razón
honesta —«el SDK de WASI no está en ninguna cola»— que dejó de ser cierta hace
un commit: la cadena wasi-libc-headers -> wasi-compiler-rt -> wasi-libc ->
wasi-sdk sella entera.

El flag nuevo es LITERALMENTE el de Alpine, verificado hoy trayendo su mozconfig
de community/firefox: `--with-wasi-sysroot=/usr/share/wasi-sysroot`, que es la
misma ruta donde instala nuestro wasi-libc.

Las deps van las TRES y no es redundancia: en hammer las deps NO son
transitivas —el bwrap apila como overlay sólo lo que está escrito en la lista—
así que declarar sólo `wasi-sdk` no arrastraría al `wasi-libc` que necesita, y
ése es el peor fallo posible de esta cadena: el sysroot no existiría y el error
saldría horas después, hablando de un header de wasm sin nombrar a ninguna
receta. Alpine declara wasi-compiler-rt + wasi-sdk porque su wasi-sdk YA trae el
sysroot dentro; el nuestro está partido en cuatro, así que la lista es distinta
a propósito.

Esto re-hashea firefox: b3:352d7880 -> b3:dba8fbf9. Es un rebuild largo y va a
la máquina que corresponde, no a gioser (4c/7,6 G sin swap).
2026-09-06 01:32:05 +00:00
Sergio 85a393aff4 cadena wasm completa: wasi-libc y wasi-sdk sellan (SDD 26, unidad 3.b)
Los cuatro eslabones que enciende RLBox en firefox, sellados de punta a punta:

  wasi-libc-headers  b3:54c6e96d  (andamio, rompe el ciclo del bootstrap)
  wasi-compiler-rt   b3:a3fe63d1  (libclang_rt.builtins-wasm32.a)
  wasi-libc          b3:8e50b145  (libc.a 970.734 bytes, 32 archivos .a)
  wasi-sdk           b3:c8a587fa  (el .cfg que hace el sysroot invisible)

Los dos primeros ya estaban construidos; los dos últimos costaron dos fallos, y
ninguno de los dos era de la receta:

1. `check-symbols` moría por SESGO DE CLANG, no por un artefacto malo. El lab
   trae clang 22.1.8 y este commit de wasi-libc (2025-06-26) congeló su snapshot
   de macros predefinidos contra un clang anterior, así que el diff completo era
   UNA línea: `+#define __wasip1__ 1`. La mitad que importa —los símbolos
   definidos e indefinidos de la libc recién construida— coincidía byte a byte.
   Por eso se RECONCILIA `expected/*/predefined-macros.txt` en vez de saltar el
   chequeo con `make no-check-symbols`, que era la salida fácil: ese target
   existe y se lleva por delante también la comparación de símbolos, que es la
   que vale. Así cualquier OTRA deriva sigue matando el build.

2. `make TARGET_TRIPLE=NOBUILD ... install` NO es un no-op. La regla `install`
   depende de `finish`, que depende de `libc`, así que make no lee el triple
   inexistente como «no construyas» sino como «construí para el triple NOBUILD»
   y muere en `build/NOBUILD/.../crt1-command.o` con `invalid thread model
   'single'` — un error que nombra un fichero que nadie pidió. Se copia
   `sysroot/{lib,share,include}` a mano, que es literalmente lo que hace la
   regla de upstream (`SYSROOT ?= $(CURDIR)/sysroot`).

Cada receta lleva su guardián de contenido, porque acá el modo de fallo caro no
es el ausente sino el vacío: un sysroot con headers y sin libc.a pasa por bueno
y revienta dentro del build de firefox, horas después y sin nombrar a nadie de
esta cadena. Verificado en verde: los builtins son objetos wasm de verdad
(magic 0061736d, no x86_64 nativos) y el .cfg de wasi-sdk apunta a un sysroot
que existe.

El segundo fallo lo diagnosticamos entre dos sesiones: hammer-f8 aportó el
`invalid thread model 'single'` que explicaba la línea que yo veía.

Queda para la próxima unidad el flip de recipes/firefox.toml:
--without-wasm-sandboxed-libraries -> --with-wasi-sysroot, que es un rebuild
largo y su propia unidad de trabajo.
2026-09-06 01:29:41 +00:00
Sergio 78a7f87836 estado: cosecha granja 2026-09-06T01:02:16Z — avance del árbol KDE 2026-09-06 01:02:16 +00:00
Sergio 3f3653b171 estado: cosecha granja 2026-09-06T00:31:52Z — avance del árbol KDE 2026-09-06 00:31:52 +00:00
Sergio c0f94c93ac estado: cosecha granja 2026-09-06T00:02:19Z — avance del árbol KDE 2026-09-06 00:02:19 +00:00
Sergio 25cc429c9e estado: cosecha granja 2026-09-05T23:32:18Z — avance del árbol KDE 2026-09-05 23:32:18 +00:00
Sergio 0c552057e1 estado: cosecha granja 2026-09-05T23:02:27Z — avance del árbol KDE 2026-09-05 23:02:27 +00:00
SergioandClaude Opus 5 bf9f1e4ca3 atuq: firefox REPRODUCE — why-differs 56/56 sobre dos builds reales
La deuda de BuildID queda cerrada con la misma prueba que se le exigió al
derivado: apartar el artefacto como .ref, construir de nuevo y comparar.
56 entradas idénticas, 0 divergen. Antes de MOZ_BUILD_DATE esto no podía
salir bien, y build-state.json no lo veía porque el ArtifactHash es
input-addressed y no se mueve por la hora del build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4H7RabF2NCxCvFPJi37r
2026-09-05 22:48:02 +00:00
Sergio 98ded1dd92 estado: cosecha granja 2026-09-05T22:31:57Z — avance del árbol KDE 2026-09-05 22:31:57 +00:00
Sergio d4e2b8f1a7 estado: cosecha granja 2026-09-05T22:02:17Z — avance del árbol KDE 2026-09-05 22:02:17 +00:00
Sergio 8a2c6fad39 repro: «no pude comparar» tampoco es no-determinismo — segundo sitio, misma lección
Una tanda volvió a llenar el libro de veredictos falsos: 47 recetas sanas anotadas como
NO-DETERMINISMO. Esta vez la cadena empezó antes de donde yo había mirado.

El `mv` que APARTA el artefacto falló (el directorio de apartado no estaba) y yo nunca comprobaba que
hubiera funcionado. A partir de ahí todo lo que sigue es basura: el artefacto se queda en su sitio,
`hammer why-differs` compara contra una ruta que no existe y devuelve `Error: store: no existe…`, y
mi código leía cualquier salida no-cero como «difieren». Un error de comparación disfrazado de
veredicto, 47 veces.

Es la MISMA lección que arreglé hace un rato para el build, en un segundo sitio que no miré:
**no poder medir no es un resultado negativo**. Dos guardas nuevas:

  · el apartado se comprueba (`mv || SIN VEREDICTO`) y la receta se salta ruidosamente;
  · `why-differs` se lee, no sólo su exit code: si dice `Error:`/`no existe`, es SIN VEREDICTO, se
    restaura el artefacto y NO se anota nada.

Probadas las dos: con el apartadero sin permiso de escritura sale «no pude apartar el artefacto ⇒
SIN VEREDICTO», el artefacto queda intacto y el libro no crece.

Comprobado además que la tanda mala no perdió NADA: como el `mv` fallaba, los 47 artefactos nunca
salieron del store (`faltan: 0`). El daño fue sólo el registro, y está purgado — el libro queda con
51 verificaciones buenas.

La regla, ya por triplicado hoy: cuando un guardián no puede establecer algo, el estado es *sin
veredicto*, y eso no se escribe. Un libro que anota lo que no midió se cree igual que uno que sí.
2026-09-05 21:44:50 +00:00
Sergio 6c1f80204a repro: cobertura 92 de 1156 — la tanda sólo-C es la sostenible
18 recetas C verificadas de una, en unos nueve minutos, 15 REPRODUCEN y 3 eran DERIVA (ya al día).
Cero no-determinismos. Y `work/sources` se quedó en 4 KB toda la tanda gracias a la poda por
veredicto: antes una tanda así llevaba el disco del 95% al 98%.

Lo que hace sostenible a esta y no a la anterior es la SELECCIÓN. El filtro `MAX_SECONDS` sobre los
tiempos del worker no transfiere a Rust —`zola` se comió 40 minutos y no dio un solo veredicto— pero
en C sí: sin caché de cargo de por medio, un build de 40 s allá son 40 s acá. Lista explícita de
clase `c`, no muestreo ciego.
2026-09-05 21:41:41 +00:00
Sergio 2277d17b13 estado: cosecha granja 2026-09-05T21:32:15Z — avance del árbol KDE 2026-09-05 21:32:15 +00:00
Sergio 1505bfad78 licencias: ffmpeg y LLVM salen del árbol — «lo decide un humano» era «lo decide la receta»
Tres que había clasificado como decisión legal y no lo eran. Las dos causas son distintas y las dos
me las estaba perdiendo por mirar sólo el árbol:

**ffmpeg → LGPL-2.1-or-later.** Su raíz trae cuatro COPYING.* y por eso el detector se planta, con
razón: el árbol solo no puede decidir. Pero el `LICENSE.md` del propio ffmpeg explica la regla —«In
combination the LGPL v2.1+ applies to FFmpeg» y «None of these parts are used by default, you have to
explicitly pass `--enable-gpl`… In this case, FFmpeg's license changes to GPL v2+»— y la respuesta la
da NUESTRA receta: no pasa `--enable-gpl` ni `--enable-version3`, y encima va con
`--disable-autodetect`. O sea que la licencia sale de cruzar el árbol con los flags, no de elegir uno
de los cuatro ficheros.

**llvm18 y clang18 → Apache-2.0 WITH LLVM-exception.** Acá el detector veía «tres licencias
distintas» en un solo `LICENSE.TXT` y se plantaba, pero el fichero ABRE diciendo «The LLVM Project is
under the Apache License v2.0 with LLVM Exceptions»: lo demás es la sección legada. La regla de
«varias licencias ⇒ decide un humano» es correcta como default y aquí el humano sólo tuvo que leer la
primera línea.

`licenses/` pasa de 48 a 49 textos: `LLVM-exception` hacía falta para poder declararla, que es el
lazo de siempre —no se declara lo que no se puede entregar—. El único sin texto canónico sigue siendo
`LicenseRef-qorpa-ajena-no-enumerable`, que es identificador nuestro y SPDX no publica.

97% (1135/1165); la cola baja de 27 a 24. Los 3 ArtifactHash, idénticos.
2026-09-05 21:26:53 +00:00
Sergio a48885e143 repro: podar el árbol de fuentes tras cada veredicto sano — el barrido ya no llena el disco
Un barrido dejaba un árbol extraído por receta en `work/sources` y no limpiaba hasta el final. En el
hub eso llevó el disco del 95% al 98% DOS VECES hoy, y un disco lleno no se lee como disco lleno: se
lee como recetas rotas. Con la poda por veredicto, `work/sources` se queda en 3 MB durante toda la
tanda en vez de crecer hasta 7,7 G.

No cuesta nada porque `fetch.rs` borra y re-extrae el árbol en CADA build por diseño: guardarlo entre
recetas de un barrido no ahorra un segundo.

Dos límites deliberados:
  · Sólo se poda cuando el veredicto es SANO. Si algo falló o divergió, el árbol es justamente lo que
    hace falta para mirarlo — un verificador que limpia la escena del problema que acaba de encontrar
    sirve para poco. Es la misma razón por la que los ejemplares divergentes se conservan.
  · Sólo el árbol de ESA receta. Las deps extraídas se quedan: son compartidas, y borrarlas mientras
    otra cosa las usa es exactamente el ADR 0012.

Con esto la cobertura se puede levantar en el HUB, que es donde están los 1157 artefactos. El worker
sólo tiene los que construyó —el último barrido allá reportó 47 de 60 «sin artefacto»— así que como
máquina de cobertura no sirve por más disco que tenga.
2026-09-05 21:05:12 +00:00
Sergio 8b43b44c11 estado: cosecha granja 2026-09-05T21:02:03Z — avance del árbol KDE 2026-09-05 21:02:03 +00:00
Sergio e348c97964 aichat: era NO REPRODUCIBLE, y la causa estaba en el build.rs de un crate transitivo
`scripts/verificar-repro.sh` lo cazó en el worker: dos reconstrucciones seguidas, misma máquina y
mismo lab, dan `usr/bin/aichat` distintos. Es el primer no-determinismo REAL que el instrumento
encuentra —hasta hoy todo lo que había marcado era deriva— y salió porque el arreglo de esta tarde
conserva los dos ejemplares en `store/.divergen/` en vez de borrarlos.

`hammer why-differs` dijo `.rodata`. El primer byte que cambia (offset 1749687) cae dentro de un JSON
de *stopwords* por idioma embebido en el binario, y lo que difiere no es el contenido sino el ORDEN:
una build empieza por `"sl"` y la otra por `"fi"`.

De ahí a la causa: el crate es `stop-words` 0.8.1, transitivo vía bm25, y su `build.rs` arma un
`HashMap<String, Vec<String>>` y lo serializa con `serde_json::to_string`. El `HashMap` de Rust usa
`RandomState` —semilla ALEATORIA POR PROCESO— así que el orden de serialización cambia en cada
compilación y ese JSON se hornea en el binario. `BTreeMap` serializa en orden de clave. Es un arreglo
de una palabra que upstream no hizo.

Se parchea el crate vendorizado en una fase `configure` (el vendoreo ocurre ANTES de las fases, así
que el árbol ya está ahí) y se vacía la lista de ficheros de su `.cargo-checksum.json`, por la misma
razón y con la misma técnica que `recipes/firefox.toml`: cargo verifica el sha de cada fichero
vendorizado y no ofrece forma de recalcularlo.

Los `[ -f ]` fallan ruidoso a propósito. Si mañana la cadena de deps deja de traer stop-words, lo
peor sería que el parche se saltara en silencio y el binario volviera a ser aleatorio sin que nadie
se entere — que es exactamente cómo esto llegó hasta acá.

Re-hashea aichat (b3:c40a15bd → b3:7a515b0a), y eso es gratis: `yupana radio` da 0 dependientes y
ninguna imagen lo declara.
2026-09-05 20:49:23 +00:00
Sergio a2e5ebd6d7 estado: cosecha granja 2026-09-05T20:32:01Z — avance del árbol KDE 2026-09-05 20:32:01 +00:00
Sergio 058162c6d3 licencias: la cola de pendientes también se escribe — «lo que falta y por qué» es dato
Las 33 que quedan sólo existían en la salida del script, o sea que se perdían al cerrar la terminal.
Para quien tiene que decidirlas —y son decisiones legales, no mecánicas— «qué falta y POR QUÉ no se
puede afirmar» es tan dato como los veredictos. `docs/licencias-pendientes.tsv` las deja listadas con
su motivo: cuáles traen varios ficheros de licencia que dicen cosas distintas (gmp, ffmpeg, clang18,
llvm18, los `gi-*`), cuáles tienen un LICENSE que no reconozco y con qué frase empieza (fuse3, lsof),
y cuáles no traen nada en el árbol (boost, pigz, sqlite-shared).

Al revés que `licencias-evidencia.tsv`, ésta se REGENERA entera en cada corrida. No es un registro de
lo que se probó sino una foto de lo que queda, y una pendiente resuelta tiene que DESAPARECER de acá
—si se acumulara, la lista de trabajo mentiría hacia arriba para siempre.
2026-09-05 20:18:21 +00:00
Sergio d7d9cf8fd2 repro: «la 2ª reconstrucción no construyó» NO es no-determinismo — mi propio bug, y lo pagó el libro
Corriendo una tanda con `MAX_SECONDS=45` se llenó el disco a mitad, los rebuilds empezaron a morir
por ENOSPC, y la rama que yo mismo había escrito para distinguir DERIVA de NO-DETERMINISMO metió
«falló» y «difiere» en la misma condición ⇒ **21 recetas sanas quedaron anotadas como
NO-DETERMINISMO** en un libro que pretende ser autoritativo. Un registro falso y persistente es peor
que no tener registro: el propósito del libro es que nadie tenga que volver a mirar, así que una
entrada equivocada se cree para siempre.

Es la trampa de siempre —un disco lleno se lee como receta rota, y la pista es que mueren varias
seguidas— y el script ORIGINAL ya la evitaba: «no es lo mismo «no reproduce» que «no construye
acá». Distinguirlos importa: mezclarlos inventaría un problema de determinismo». Lo rompí al añadir
la segunda reconstrucción. Restituido: si la 2ª no construye, se restaura el artefacto, se cuenta
como fallo y **no se anota nada** — sin veredicto es un estado legítimo.

Reparado además el daño: las 21 entradas falsas purgadas del libro (quedan 27 verificaciones
buenas), y `angle-grinder` repuesto desde `store/.divergen/`, donde lo había dejado la clasificación
errónea. Eso sí funcionó como debía: el ejemplar estaba guardado y entero, no perdido.

Y queda anotado en el propio script el límite del filtro por tiempo que provocó todo: el sidecar de
`store/.times/` lo escribe el WORKER, así que acota el build de esa receta EN LA MÁQUINA QUE LO
MIDIÓ. Una receta Rust de 40 s allá con la caché de cargo caliente son muchos minutos acá en frío
—`angle-grinder` y `amp` entraron por el filtro y se comieron el disco— y encima el número no cuenta
la CASCADA de deps que falten en el store local, que es la misma sorpresa que dio `gjs`. Con disco
justo conviene pasar las recetas a mano.
2026-09-05 20:05:33 +00:00
Sergio 118ba2e2c3 estado: cosecha granja 2026-09-05T20:03:46Z — avance del árbol KDE 2026-09-05 20:03:46 +00:00
Sergio 4067bf817e estado: cosecha granja 2026-09-05T19:32:01Z — avance del árbol KDE 2026-09-05 19:32:01 +00:00
Sergio efe6e001d2 repro: un libro de lo verificado, y por fin un número de cobertura — era 2 de 1157
`verificar-repro.sh` sorteaba una muestra y se olvidaba. Sin registro no había forma de contestar la
pregunta que importa —¿qué fracción del corpus se comprobó alguna vez que reproduce?— y encima las
mismas recetas salían sorteadas una y otra vez mientras otras no se miraban jamás. La primera medida
con el libro puesto: **2 de 1157**. El invariante central del proyecto no se había verificado de
forma acumulativa prácticamente en nada, y eso no se veía porque cada corrida daba « PUERTA
SUPERADA» sobre su propia muestra.

`docs/state/repro-verificado.tsv` va con la misma clave auto-invalidante que el libro de fuzz:
(receta, **ArtifactHash**). El hash resume la receta MÁS su cierre de deps, así que una entrada deja
de casar en cuanto algo aguas arriba cambia. Un «ya lo verifiqué» que sobreviviera a un re-hash sería
una mentira; éste no puede serlo.

Con eso el muestreo pasa a sortear entre lo NO verificado, así que corridas sucesivas ACUMULAN
cobertura en vez de repetir (con `TODO=1` se sortea entre todas, para re-verificar a propósito), y
`--coverage` da el número sin construir nada.

Verificadas en esta tanda: itstool, markupsafe, musl-obstack, hicolor-icon-theme, musl-fts, npth,
poppler-render-check, doas, libffi y packaging REPRODUCEN; `when` derivaba y quedó al día. Cero
no-determinismos. Van 11 de 1157.

⚠ Y una trampa que casi me come, anotada acá porque el próximo la va a pisar: elegí candidatos «por
artefacto chico» y la lista empezó con **nodejs**, cuyo artefacto pesa 0,2 MB y cuyo build son horas
de V8. El tamaño del artefacto NO dice nada del costo de construirlo. Para elegir muestra barata hay
que mirar tiempos de build, no bytes de salida.
2026-09-05 19:31:33 +00:00
Sergio d5863ae554 verificar-repro: el apartadero iba a tmpfs, no tomaba el lock, y dejaba el veredicto a medias
Tres defectos, encontrados corriéndolo y pagando uno de ellos: perdí el artefacto de `aichat` al
interrumpir una corrida. Está reconstruyéndose en el worker.

**1. El apartadero estaba en `/tmp`.** Para verificar hay que sacar el artefacto de su sitio y dejar
que hammer lo reconstruya; eso iba a un `mktemp -d`. La cabecera prometía «un verificador que
destruye lo que verifica es peor que no tenerlo», y esa ubicación rompía la promesa en tres sitios a
la vez: `/tmp` es **tmpfs**, así que apartar copiaba el artefacto a RAM en una caja de 7,6 GiB sin
swap; al ser otro sistema de ficheros el `mv` no era un rename atómico sino copiar-y-borrar, o sea
que una interrupción a mitad lo dejaba en ninguna parte; y lo que sobreviviera moría al reiniciar.
Ahora va a `store/.verificar-repro`: el `mv` es instantáneo, no cuesta RAM ni disco, y lo apartado
SOBREVIVE a un kill — la corrida siguiente lo repone sola antes de empezar.

**2. Llamaba a `hammer build` sin tomar el `flock`.** Es la regla 1 del repo, y saltársela arriesga
el árbol compartido de `work/sources` de forma irreversible (ADR 0012). Lo toma el script y no quien
lo llama, por lo mismo que `poda-fuentes.sh`: así no hay forma de correrlo mal — y correrlo mal fue
exactamente lo que hice. Con `-E 3`, porque sin eso «no conseguí el lock» sale como exit 1, que es lo
que este script usa para «hay divergencias»: informar «el corpus no reproduce» cuando lo que pasaba
era que había otro build corriendo es peor que no correr.

**3. Confundía DERIVA con NO-DETERMINISMO y lo delegaba en la memoria de quien leía.** Que el
guardado difiera de una reconstrucción de hoy puede ser que el lab se movió (deriva, sano) o que las
mismas entradas dan salidas distintas (no-determinismo, bug). La cabecera decía «corré el script dos
veces y mirá la segunda» — con lo que la primera corrida informaba «DIVERGE» sobre cosas sanas y
entrenaba a no creerle. Ahora, ante una diferencia, reconstruye una segunda vez y compara las dos
reconstrucciones ENTRE SÍ: si coinciden es DERIVA, si no, NO-DETERMINISMO. El rebuild extra sólo se
paga cuando ya hubo una diferencia. Y cuando es no-determinismo de verdad, los dos ejemplares se
conservan en `store/.divergen/` en vez de borrarse: antes el guardián tiraba la única evidencia justo
en el caso que existe para encontrar.

Medido de paso, y es la parte buena: `libassuan`, `libksba`, `libffi` y `packaging` —artefactos del
21 de agosto— reproducen BIT A BIT con el lab de hoy. `scdoc` derivaba y ya está al día; `gron`,
`age` y `anew` reproducen. La rama de DERIVA se probó ensuciando a propósito una copia guardada:
clasifica bien y el store queda con la reconstrucción limpia.
2026-09-05 19:03:48 +00:00
Sergio 5420d2db0d estado: cosecha granja 2026-09-05T19:02:04Z — avance del árbol KDE 2026-09-05 19:02:04 +00:00
Sergio 3ddac0269c SDD 26 §2.sexies: tres afirmaciones que el documento hacía sin medir, y cómo se cerraron
El re-empaque determinista, la capa de configuración y el branding visible eran afirmaciones, no
mediciones. Quedan con su prueba al lado: why-differs entre dos builds de atuq (85 idénticas, 0
divergen), el testigo escrito como pref de usuario, y la captura de la pantalla real.

De la primera salió el contraste que importa: el DERIVADO reproducía y la BASE no. El BuildID de
firefox era la hora del build, y build-state.json no lo podía ver porque el hash es input-addressed.
Verde y mintiendo. Cerrado, con guardián.

La regla que sale del día entero: cuando la duda es «¿llegó al artefacto?», la respuesta no está en
el log ni en la receta — está dentro del artefacto.
2026-09-05 18:39:35 +00:00
Sergio e0dce7c4b7 estado: cosecha granja 2026-09-05T18:33:07Z — avance del árbol KDE 2026-09-05 18:33:07 +00:00
Sergio d13af44ddb kernel: el contrato admite capacidades de INTERFAZ — el test llevaba rojo desde el 03-09
`cargo test` del workspace fallaba en `el_contrato_del_repo_cierra_sobre_si_mismo`:
«proceso-por-descriptor no declara símbolos». No es un descuido del alta: `abda7d3` dio de alta pidfd
con `symbols = []` y dedicó ocho líneas a explicar por qué va vacío — pidfd no depende de ningún
`CONFIG_*`, es interfaz del core desde Linux 5.3. Lo que no se actualizó fue el invariante, así que
el dato quedó bien y el test quedó rojo. Dos días sin que nadie lo viera, que es lo que pasa cuando
una suite falla por algo que «ya se sabe»: deja de mirarse entera.

El invariante correcto no es «toda capacidad tiene símbolos» sino **«toda capacidad afirma algo
comprobable»**: sin símbolos vale, pero entonces hay que nombrar la INTERFAZ. Sin una ni la otra, la
capacidad no dice nada que se pueda verificar y lo más probable es que sea un campo a medias — que
es el caso que el assert original quería atrapar y sigue atrapando.

Hoy hay exactamente una capacidad así y declara `interface = ["pidfd_open(2)", "pidfd_send_signal(2)"]`.

Workspace: 541 tests en verde, 0 fallos.
2026-09-05 18:17:26 +00:00
Sergio 59a0e56a16 licencias 97% (1132/1165): «no hay fichero» y «no reconozco el texto» no son lo mismo
El script decía «sin COPYING ni declaración en el árbol» para `lsof`, `when`, `wpa_supplicant`,
`perl-xml-parser` y `fuse3` — y las cinco SÍ tienen un COPYING o un LICENSE en la raíz. Lo que
pasaba es que ninguna de mis huellas reconocía su texto, que es un problema distinto y se acciona
distinto: uno pide buscar la licencia en otro sitio, el otro pide una huella nueva o leerla. Decir lo
mismo de los dos casos mandaba a mirar donde no era. Ahora el mensaje nombra el fichero y cita su
primera línea, y con eso tres se resolvieron leyéndolas:

  · `wpa_supplicant` — su COPYING no es una licencia sino un aviso: «the project has chosen to use
    only the BSD license option for future distribution», y remite al README. El README trae las tres
    cláusulas clásicas ⇒ BSD-3-Clause. Sin seguir esa remisión no había veredicto posible.
  · `when` — «under the same terms as Perl, or, at your option, under version 2 of the GPL». «Los
    mismos términos que Perl» ES la disyunción Artistic-1.0-Perl OR GPL-1.0-or-later, y la opción de
    GPL-2 ya queda cubierta por el `-or-later`.
  · `perl-xml-parser` — su LICENSE es el texto íntegro de la Artistic License 2.0.

Ese último obligó a cerrar un lazo que yo mismo había dejado abierto: el script se niega a declarar
un SPDX cuyo TEXTO no tengamos en `licenses/`, porque la obligación legal es entregar el texto y no
el nombre. Pero `licencias-textos.sh` deriva qué textos bajar DE las licencias ya declaradas ⇒ una
licencia genuinamente nueva no podía entrar por ningún lado. El orden que lo resuelve es: declarar
con evidencia, bajar el texto, comprobar que llegó. `licenses/` pasa de 44 a 48 textos y el único que
queda sin texto canónico es `LicenseRef-qorpa-ajena-no-enumerable`, que es un identificador nuestro y
SPDX no publica.

Quedan 33. `lsof` (licencia propia de Purdue) y `fuse3` (aviso compuesto: LGPL para la librería, GPL
para las utilidades) siguen pendientes a propósito, y el resto son expresiones que decide una
persona: gmp, ffmpeg, clang18, llvm18 y los `gi-*` traen VARIOS ficheros de licencia que dicen cosas
distintas, y en ffmpeg la respuesta ni siquiera está en el árbol —la deciden los flags de la receta.

Los 3 ArtifactHash, idénticos antes y después.
2026-09-05 18:10:52 +00:00
Sergio b87835f719 licencias 96% (1129/1165): la fuente git también es evidencia, y el script deja de llamarse «tarball»
Faltaban tres recetas cuya fuente es un repo, no un tarball: `fcft`, `foot` y `glab`. El commit
pineado cumple exactamente el mismo papel que el sha256 —es el árbol EXACTO que construimos— así que
la evidencia estaba igual de disponible, sólo que por otra puerta.

`miembros_de_git()` la abre sin clonar el árbol entero: `--depth 1 --filter=blob:limit=64k` sobre el
commit. Un COPYING nunca pasa de 64k y los fuentes donde vive la concesión tampoco; los que sí pasan
—binarios, assets— son justo los que no interesan. Un repo de cientos de megas se resuelve con unos
pocos. Las tres salieron del `license:` que el propio autor escribe en su `meson.build` (fcft, foot)
y del texto del LICENSE (glab).

Los repos por SSH se saltan diciéndolo: este script hace fetch anónimo y no tiene claves. Son
`hammerd` y `portal-probe`, y ya están resueltas por otra vía (su repo es este mismo).

`analizar()` pasa a consumir un iterable de `(ruta, bytes)` en vez de abrir el tar ella misma, así
que las dos fuentes comparten TODA la lógica de veredicto: la jerarquía de evidencia, la regla de
sólo-la-raíz, la unión de identificaciones y la exigencia de que la concesión hable de la misma
versión que el COPYING. Un criterio, dos puertas.

Y el script se renombra `licencias-tarball.py` → `licencias-fuente.py`, porque el nombre viejo pasó a
ser mentira en el momento en que aprendió a leer git. Un nombre que describe la implementación de
ayer manda a la gente a buscar en el sitio equivocado.

Los 3 ArtifactHash, idénticos antes y después.
2026-09-05 18:07:17 +00:00