Al abrir `application.ini` para el branding de atuq apareció esto:
BuildID=20260905035306
Es la fecha y hora del build. Dos construcciones de la MISMA receta, con el mismo ArtifactHash,
producen bytes distintos. Es justo el agujero que el invariante de reproducibilidad existe para
cerrar, y no se ve en `build-state.json` porque el hash es input-addressed y no se mueve por esto.
El arreglo es conocido y lo usa Alpine: exportar `MOZ_BUILD_DATE` con un valor determinista.
NO se aplica de paso, a propósito: vive en una FASE, así que re-hashea la receta y cuesta otro build
de cuatro horas más el re-sellado de atuq, que cuelga de ella. Es su propia unidad de trabajo y
queda como 4.c en el plan del SDD 26.
El comentario se comprobó que NO mueve el hash antes de commitearlo (b3:aa90192a antes y después):
un comentario fuera de las fases no entra en `hash_inputs`, así que el firefox sellado sigue válido.
Y el §2.ter del SDD queda con el estado real de atuq v0.2 y su evidencia.
El documento daba por hecho que la receta derivada se podía escribir con lo que hammer ya tenía.
No: `[source]` era obligatoriamente git o tarball. Queda escrito el muro, por qué los rodeos eran
peores (fetchear una fuente que se ignora miente sobre la identidad; un repo aparte obliga al worker
a leer algo privado) y la salida — `source.dir`, hasheado por contenido con el `of_tree` que ya
existía para Stage 2.
Y el estado real del plan: la unidad 4 tiene su v0.1 escrita, con el branding y el re-empaque de
omni.ja separados como 4.b porque los dos necesitan mirar el artefacto que `firefox` todavía no
selló.
El documento estimó una receta `llvm-toolchain` (clang+lld+libc++ desde fuente) como la unidad de
mayor palanca del frente. Mirar el lab en vez de suponerlo la redujo a un `apk add`: clang22 y
llvm22 ya estaban, `Compiler::Clang` ya estaba cableado en hammer y ninguna receta lo usaba.
Se corrige el §3 con la evidencia y la medición de la huella (no se movió ⇒ 837 artefactos
intactos), y el plan del §8: la unidad 2 queda cerrada, la 3 pasa a ser construir, y el PGO se
separa como 3.a porque tiene sus propios dos muros (sin X11 para el profileserver, profdata no
determinista).
El SDD 26 estimó que habilitar PGO/LTO exigía una receta `llvm-toolchain` (clang+lld+libc++ desde
fuente). ERA CARO DE MÁS. Al mirar el lab en vez de suponerlo:
- `.dev-fs/alpine` YA TRAE clang22 + llvm22 22.1.8 — la MISMA major que usa el APKBUILD de Alpine
para este mismo Firefox (`_llvmver=22`).
- `Compiler::Clang` YA EXISTE en hammer, cableado de punta a punta: `parse_compiler` lo acepta y
`hammer-build/src/lib.rs` pone CC=clang, CXX=clang++ y AR=llvm-ar. NINGUNA receta lo usaba.
- Lo único que faltaba era `ld.lld`. `apk add lld` ⇒ lld22 22.1.8, dos paquetes, cero upgrades.
CON ESO CAEN LOS TRES MUROS QUE OBLIGABAN A gcc, sin perder lo que gcc daba: el sondeo de linker se
satisface con `--enable-linker=lld`, el `ar` lo pone hammer solo, y el `NEEDED` de la stdlib de C++
existe porque clang++ de Alpine usa la libstdc++ COMPARTIDA — la prueba no es teórica, Alpine
construye este Firefox con clang22 y sin libcxx en sus makedepends.
LA HUELLA DEL LAB NO SE MOVIÓ, Y SE MIDIÓ ANTES DE TOCAR NADA. `lld` no casa ningún prefijo de
TOOLCHAIN_PREFIXES (hammer-core/src/lab.rs), así que los 43 paquetes que entran en `hash_inputs`
salieron idénticos ⇒ los 837 artefactos sellados quedan intactos. Eso es lo que hace barato el
cambio HOY, y a la vez es un agujero escrito en los dos sitios: la versión de lld no es parte de la
identidad del artefacto, y sólo expone a las recetas `compiler="clang"`, que hoy es una. Meter
"lld" en la lista de prefijos es lo correcto y cuesta re-hashear el corpus entero: próxima campaña.
En el mozconfig entran, además de lld: `--enable-lto=cross`, `--enable-packed-relative-relocs` y
`--with-unsigned-addon-scopes=app,system`. El último no es cosmético: sin él un Firefox de release
rechaza las extensiones que la distro deja en distribution/extensions/, así que la capacidad de atuq
de shipear su propio `sct` se decide ACÁ, en la base, y no en el overlay del derivado.
PGO no entra en esta pasada y el porqué queda escrito en la receta: el perfil se junta corriendo el
navegador (Alpine y Arch usan xvfb-run; nosotros no tenemos X11 ⇒ sway headless) y el profdata NO es
determinista, así que tiene que sellarse como artefacto propio y consumirse por hash.
ThinLTO se capa con la misma cuenta que -j y por la misma razón que ella no es un literal: un número
fijo ataría el ArtifactHash a la RAM de quien escribió la receta.
Nuevo ArtifactHash: b3:6f2a3b2f6db4452ed0d2d3f4e2ff7cd6562a86878d4360653859720be1c3d94d
(el firefox 154.0 sellado con gcc queda SUPERADO, no perdido).
Pregunta del usuario: «¿firefox está sin pgo-lto-bolt? ¿eso es una desventaja frente a las otras
distros?». Sí, y para medirlo se trajeron el APKBUILD + mozconfig de community/firefox de aports y
el PKGBUILD de Arch, y se compararon línea a línea con el mozconfig de recipes/firefox.toml.
La comparación importa porque ALPINE ES NUESTRO PROPIO UPSTREAM: de ahí salen los once parches de
musl. No es una distro lejana sacándonos ventaja, es la que ya construye este mismo código con
`--enable-lto=cross` + `--enable-profile-use=cross` + jarlog. Arch hace lo mismo («Do 3-tier PGO»).
BOLT NO ES LA DESVENTAJA: cero menciones en los dos ficheros. Lo que nos separa es PGO+LTO.
Y el diff completo da más que velocidad:
- RLBOX APAGADO (`--without-wasm-sandboxed-libraries`) es una desventaja de SEGURIDAD y pesa más que
el PGO: es la jaula wasm de graphite/ogg/expat/woff2, o sea el código que come entrada no
confiable. Encenderlo pide wasi-sdk + wasi-compiler-rt en el corpus: receta, no flag.
- `--with-unsigned-addon-scopes=app,system` nos falta y ATUQ LO NECESITA para shipear sus propias
extensiones ⇒ una capacidad de atuq que NO se resuelve en el overlay: exige tocar la base.
- Falta RELR; el linker es GNU ld y no lld.
- `--disable-jemalloc` NO es desviación nuestra: Alpine también lo pasa. Un fantasma menos que
perseguir.
- Bundlear en vez de `--with-system-*` es diferencia a propósito: el corpus ES la fuente.
Corolario que ordena el plan: TODO ESO ENTRA EN UN SOLO REBUILD. Cada uno son cuatro horas y
re-sella la cola Gecko entera, así que la unidad 3 pasa a ser una pasada única y RLBox se separa
como 3.b.
Y dos muros del PGO que las distros no tienen, porque no persiguen lo que nosotros perseguimos:
- las dos corren el profileserver bajo `xvfb-run`, y NO TENEMOS X11 en el corpus (Wayland-only) ⇒ va
bajo sway headless, que ya existe del frente wlr.
- el profdata NO ES DETERMINISTA (contadores dependientes del timing) ⇒ se genera una vez, se sella
como artefacto propio y se consume por hash, o firefox deja de reproducir.
Bonus para el §2: el jarlog del PGO ordena el omni.ja para el arranque. Re-empacarlo a lo bruto tira
esa optimización a la basura sin que nadie lo note.
La pregunta era «nuestro propio envoltorio en vez de zen». La respuesta tiene tres hallazgos que
cambian el precio, y el documento los pone antes que la lista de features.
1. NO HAY ENVOLTORIO FUERA DEL CHROME. Gecko no tiene API de embebido en escritorio desde que murió
XULRunner (GeckoView es Android) ⇒ una carcasa Llimphi con el motor adentro no es posible. El
envoltorio es chrome-level o no es. Que es exactamente lo que Zen es.
2. ARTEFACTO DERIVADO, NO FORK DE FUENTE. Si atuq parchea el árbol, cada iteración de UI cuesta
cuatro horas de Gecko y el diseño de chrome es iterativo por naturaleza. Como no distribuimos un
binario sino que sellamos artefactos, atuq puede depender de `firefox` e inyectar marca, prefs,
policies.json y omni.ja encima: itera en segundos, no mueve el ArtifactHash del corpus, y hereda
las CVE gratis — que es lo que hundió a los forks de Firefox. Con sus dos gotchas escritos: el
omni.ja es un zip y hay que re-empacarlo determinista, y sin invalidar el startup cache el
overlay PARECE no agarrar.
3. LAS OPTIMIZACIONES NO SON UN FLAG, SON UNA TOOLCHAIN. PGO/LTO/BOLT son cadena de clang en Gecko,
y el corpus no tiene clang usable como compilador: clang18.toml compila SÓLO libclang.so (para
bindgen) y llvm18 se selló con PROJECTS="". La unidad real es una receta llvm-toolchain con
clang+lld+libc++ musl — la misma pieza que resuelve el muro 3 de firefox.toml. Es la mayor
palanca del frente, y por eso encabeza el plan.
Lo que NO se promete va escrito ANTES de la lista de features, con el criterio del modelo de
adversario de qullqa: atuq no es Tor Browser y no va a tener «modo Tor» — es un binario único en el
mundo (musl, wayland-only, branding propio), así que salir por Tor desde acá identifica MÁS, no
menos. Se ofrece proxy por contenedor, que es separación de tráfico y no anonimato.
Los diferenciadores van con una columna «quién más lo tiene» para no contarnos un cuento: sct
(transparencia de scripts, BLAKE3 + testigo) no lo tiene nadie y ya está escrito en tawasuyu;
torrent SÍ lo tiene Vivaldi, y lo nuestro sólo vale por dónde cae lo bajado (el CAS). Todo lo que
no es CSS pasa por UNA costura: un host de native messaging en Rust.
Y atuq no abandona a puriy: el host, el testigo y el archivo con RAG son agnósticos del motor, así
que cuando puriy madure se enchufan del mismo lado. atuq es su andamio, no su desvío.
Genera las cabeceras C++ de los componentes Rust de Gecko (Stylo, WebRender) y el
build de Firefox la exige. Mudanza gratis: [deps] vacío ⇒ no hay cierre que
cambiar, mismo ArtifactHash (b3:bd2e6ca6) en las dos rutas, radio 0.
El build de Firefox corre bindgen sobre los headers de C++ y bindgen carga
libclang.so en RUNTIME. La receta ya existía —nació en incoming-cosmic por un
crate *-sys del portal, y su commit de origen dice «sólo libclang.so»— pero desde
el corpus no se alcanza una cola hermana. Sin esto no hay Firefox, ni Waterfox,
ni Zen.
Mudanza medida ANTES, no después: sus cinco deps (cmake, samurai, python3,
pkgconf, llvm18) viven sólo en el corpus, así que incoming-cosmic ya las resolvía
por caída al padre y el cierre no cambia. hammer hash dio el MISMO ArtifactHash
(b3:25d95279) en las dos rutas.
Verificado que xdg-desktop-portal-cosmic —su único dependiente— sigue SELLADO y
que la cola cosmic queda en 36/36 sin deuda: cero rebuilds.
Se MUEVE y no se copia: dos ficheros para un artefacto es el cuadro de las dos
glib esperando a que uno de los dos derive.
Una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la
métrica de clausura no lo puede ver porque mide lo declarado — la lección que este
fichero ya aprendió con foot en sway y con las 22 apps KDE. OBS estaba sellado y
en ninguna imagen.
Entra SÓLO en escritorio-kde, y no por preferencia: su frontend es Qt6 y las 13
recetas Qt viven sólo en esa cola. Verificado con `yupana perfiles obs-studio`.
El perfil pasa de 263/263 a 274/274 — arrastra 11 nodos a la clausura, todos ya
sellados, y el grafo sigue cerrando.
De paso: el aviso de los «tres huecos de mpv» estaba repetido en los CUATRO
perfiles de escritorio y hoy quedó vencido en los cuatro (ALSA/PipeWire, Lua/OSC
y vaapi están cerrados). Corregidos los cuatro; el assert de count==1 fue lo que
destapó que no era uno solo.
Entran libglvnd, uthash, nlohmann-json, jansson, x264, simde, pciutils-shared y
mbedtls al corpus, y obs-studio a incoming-kde. Cero en deuda en los 7 perfiles.
libva sube al corpus y la copia de incoming-kde se JUBILA (git mv, no copia: dos
ficheros para un artefacto es el cuadro de las dos glib esperando a derivar). La
mudanza fue gratis y medida antes: las cinco deps de libva viven sólo en el
corpus, así que incoming-kde ya las resolvía por caída al padre y hammer hash dio
el MISMO ArtifactHash en las dos rutas (b3:405c6211).
libva pasa a -Dwith_wayland=yes, que eso SÍ cambia el hash. La razón está en
mpv/meson.build:1464: el feature `vaapi` se requiere contra
`vaapi-drm or vaapi-wayland or vaapi-x11 or vaapi-win32` ⇒ -Dvaapi=enabled a
secas no habilita NADA. De los cuatro backends, x11/win32 están fuera por
Wayland-only y vaapi-drm exige features[drm] de mpv, apagado porque vo=drm pide
libdisplay-info. Queda vaapi-wayland, que pide libva-wayland.pc.
Evidencia, no sólo sello: meson lista «vaapi vaapi-wayland» entre los features
habilitados, y el binario sellado trae NEEDED libva.so.2 + libva-wayland.so.2.
Radio medido antes de tocar: 2 dependientes (kpipewire, spectacle), los dos
reconstruidos. Las 7 imágenes siguen en 0 en deuda.
⚠ El driver es RUNTIME (intel-media-driver / gallium VA por dlopen): que esto
selle y que --hwdec=vaapi funcione en una máquina son cosas distintas.
La receta llevaba --disable-x86asm heredado de cuando ffmpeg entró SÓLO para
kpipewire (codificar un stream de escritorio, donde da igual). Para el
reproductor era decodificar sin SIMD.
Dos recetas —ffmpeg.toml y mpv.toml— prohibían tocarlo con la misma razón: un
hash distinto dejaría DOS ffmpeg peleando por las mismas rutas en una imagen KDE,
y el arreglo correcto sería «promover una sola y jubilar la de la cola». Eso ya
había pasado: incoming-kde/ffmpeg.toml no existe y hay UNA sola receta ffmpeg en
todo el disco. La prohibición sobrevivió a la condición que la justificaba.
--x86asmexe=nasm explícito y nasm (2.16.03) a [deps].build: sin ensamblador
declarado configure apagaría x86asm en SILENCIO, sellando un artefacto con otro
hash y sin la SIMD que dice traer.
Evidencia, no sólo sello: configure imprime «x86 assembler nasm», compila los
objetos X86ASM, y libavcodec.so pasa de 12.685.744 a 14.454.288 bytes (+1,77 MB).
Radio medido antes de tocar (yupana radio ffmpeg): 4 dependientes, ninguno
transitivo de más — mpv, wf-recorder, kpipewire, spectacle. Los 4 reconstruidos y
sellados; las 7 imágenes siguen en 0 en deuda.