diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md index 84b2f2fb..3c5af0bd 100644 --- a/docs/26-atuq-envoltorio-gecko.md +++ b/docs/26-atuq-envoltorio-gecko.md @@ -449,6 +449,126 @@ directas, nadie llamando al proxy—. La única diferencia entre las dos corrida 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. +### 2.ter Dónde vive la fuente de `atuq` — hizo falta un modo de `[source]` nuevo + +Escribir la receta destapó un muro que este documento no había visto: `[source]` era +**obligatoriamente** git o tarball, así que una receta cuyo contenido no viene de upstream sino de +nosotros no se podía ni expresar. Los rodeos eran todos peores — fetchear una fuente que después se +ignora **miente** sobre la identidad del artefacto, y colgar el overlay de un repo aparte obliga al +worker a tener acceso a un repo privado. + +Se agregó **`source.dir`** (commit `54fa4a9`): un árbol dentro del propio repo, hasheado por +**contenido** con `ArtifactHash::of_tree` —que ya existía para verificar bit-reproducibilidad en +Stage 2— y no por ruta. Es la misma disciplina que ya tenían los `patches`, que entran al hash por +bytes. Editar un CSS del overlay mueve el `ArtifactHash`; renombrar el directorio, no. + +Un `.swm` **no** puede llevar una receta derivada, y los cuatro sitios que lo tocan fallan +diciéndolo: el manifiesto es compartible y necesita un puntero resoluble desde fuera. + +**Hecho el 2026-09-05** (commits `aeebb19` y `6f6c32b`): `recipes/atuq.toml` + `recipes/atuq/`. +La **v0.2** (`b3:7eff4ba4`) ya trae el branding: `application.ini` (Vendor/Name/RemotingName/ +CodeName, con el `ID` intacto para no salirse del ecosistema de complementos), `brand.ftl` dentro de +`browser/omni.ja`, los binarios renombrados, iconos propios y `.desktop`. El re-empaque preserva +orden, `STORED` y fechas del original, y se verificó contra el artefacto: **5306 entradas, mismo +orden, y una sola con contenido distinto**. El `BuildID` se deriva del contenido del overlay —no de +la fecha— porque es la clave con la que Gecko invalida su startup cache: sin eso, un cambio de +chrome arranca con la interfaz vieja y *parece* que el overlay no agarró. + +La capa de configuración de la v0.1 son cuatro ficheros —autoconfig, `atuq.cfg`, `chrome/atuq.css`, +`policies.json`— y el CSS se registra como `USER_SHEET` desde autoconfig porque `userChrome.css` +vive en el perfil y exigiría que el usuario prenda una pref: atuq tiene que verse como atuq en el +primer arranque. Falta construirlo: su dep `firefox` está en vuelo. + +### 2.quater Abrirlo en una pantalla: tres fallos que la clausura no podía ver + +El 2026-09-05, con `atuq` sellado y verificado, se hidrató y se abrió en el compositor de la sesión +(waypipe). **No arrancó**, y los tres motivos son la diferencia entre «sellado» y «usable»: + +1. **EXDEV al hidratar.** `hydrate` proyecta con hardlinks y `linkat()` rechaza cruzar un punto de + montaje aunque los dos lados sean el mismo filesystem. Acá el store es `/dev/sdb` bind-monteado + y `work/` vive en `/dev/sdc`. El rootfs va bajo `/mnt/cosecha`, donde el volumen está montado + entero. Se comprueba con `findmnt -T`, **nunca** con `stat -c %d`. +2. **El lanzador no puede ser un symlink.** Ni el binario ni sus `.so` traen `RPATH`/`RUNPATH` + (`readelf -d`), así que el motor no encontraba su propio `libnspr4.so` y moría en + `XPCOMGlueLoad`. Es un script con `LD_LIBRARY_PATH` + `exec`, como Debian y Fedora. La + alternativa limpia —`RPATH=$ORIGIN` con `patchelf`, que es lo que hace Alpine— espera a que + `patchelf` exista como receta del corpus. +3. **Un bug del corpus, no de atuq: `atk` se había tragado GObject.** Declaraba la variante + ESTÁTICA de glib y produce un objeto compartido, así que `libatk-1.0.so` llevaba una copia + entera del sistema de tipos. Dos GObject en un proceso ⇒ 45 `GLib-GObject-CRITICAL` y + `Segmentation fault`. **El síntoma no nombra a atk**, y no se ve mirando el rootfs: había una + sola `libgobject`. Se cazó preguntando quién DEFINE el símbolo: + `nm -D --defined-only | grep " T g_type_register_static"` — deben salir uno, salieron + dos. + +Radio medido antes de tocar (`yupana radio atk`): 4 transitivos, 3 sellados a deuda; GNOME fuera +porque usa gtk4. La cadena `atk → gtk3 → firefox → atuq` volvió a sellar 4/4, y **firefox tardó +~50 minutos, no las cuatro horas** que repetía este documento — el número venía del folclore de la +receta, no de una medición. + +**La lección para el plan: ninguna unidad de este frente está cerrada hasta que algo se ABRE en una +pantalla.** La clausura decía 100% con un navegador que no llegaba a pintar un píxel. + +### 2.quinquies La v0.3 y los tres mecanismos: dos muertos, uno vivo + +La página de inicio y la pestaña nueva parecían un `pref` y resultaron ser un frente. Lo que se +midió, en orden: + +| Mecanismo | Resultado | +|---|---| +| `distribution/extensions/` — el clásico de las distros | **No instala nada.** Firefox retiró el *sideloading*. El `extensions.json` del perfil ni lo mencionaba y el log no dijo una palabra: fallo perfectamente silencioso | +| `policies.json` → `ExtensionSettings` con `install_url` | **Se lee** (`browser.policies.applied=true` en el perfil) y falla nombrándose: `ERROR_SIGNEDSTATE_REQUIRED` | +| `xpinstall.signatures.required = false` | **No alcanza** — la exigencia viene COMPILADA | + +**El instrumento que destrabó el diagnóstico fue un testigo.** `defaultPref` no deja rastro en +`prefs.js` —sólo se guarda lo que difiere del default— así que un autoconfig que **no se ejecuta** +es indistinguible de uno que sí y no hace nada. `atuq.cfg` escribe ahora `atuq.autoconfig.ok` como +pref de **usuario**, legible desde fuera sin abrir el navegador. Salió `true` ⇒ el `.cfg` corría, y +por lo tanto la pref de firma se estaba ignorando. + +De ahí `MOZ_REQUIRE_SIGNING` vacío en `recipes/firefox.toml`. Dos detalles que costaron cada uno su +vuelta: + +1. El default sale del **milestone** (`milestone.is_release_or_beta`), no del canal de actualización. + El nuestro es `default` y la exigencia estaba activa igual. +2. **Se desactiva con el valor VACÍO, no con cero.** `=0` muere con «takes 0 values»: es booleana y + el cero es un valor que no acepta. Leído del parser de mozbuild, no deducido. + +**El precio, escrito:** este Firefox deja de exigir la firma de Mozilla para cualquier complemento. +La alternativa es firmar en AMO —cuenta y revisión de Mozilla por versión—, que es la dependencia +externa que esta distro existe para no tener. Y no es un desvío: **sin esto, el `sct` del §6.1 +tampoco se podría shipear nunca**, así que la unidad 6 dependía de ésta sin que el plan lo dijera. + +**Método que conviene repetir:** la prueba del testigo se hizo **sin reconstruir**, montando el +`.cfg` modificado con `--ro-bind` por encima del artefacto. Editar el fichero en el rootfs habría +escrito sobre un **hardlink del store** y corrompido el artefacto sellado. + +### 2.sexies Lo que quedó PROBADO, y con qué + +Tres afirmaciones que este documento venía haciendo sin medir, y cómo se cerraron: + +| Afirmación | Prueba | +|---|---| +| El re-empaque del `omni.ja` es determinista | `why-differs` entre dos builds de `atuq`: **85 entradas idénticas, 0 divergen** | +| La capa de configuración se aplica | testigo `atuq.autoconfig.ok` escrito como pref de **usuario** en el perfil | +| El branding se ve | captura de la pantalla real con `grim` desde dentro de la jaula, y la página de inicio renderizada por el propio navegador con `--headless --screenshot` | +| La base también reproduce | `why-differs` entre dos builds de `firefox` en el worker: **56 entradas idénticas, 0 divergen** | + +Y el contraste que salió de la primera: **el derivado reproducía y la base no.** El `BuildID` de +`firefox` era la hora del build, así que dos construcciones con el mismo `ArtifactHash` daban bytes +distintos — y `build-state.json` no lo podía ver, porque el hash es input-addressed y no se mueve +por esto. Verde y mintiendo. Cerrado con `MOZ_BUILD_DATE` desde `SOURCE_DATE_EPOCH`, que es el +mecanismo de hermeticidad que el sandbox ya tenía — y **comprobado como se comprueba esto**: +apartando el artefacto como `.ref`, construyendo de nuevo y pasándole `why-differs`. Reproduce. + +**Dos guardianes nuevos en la fase `install` de `firefox`**, hermanos entre sí, porque los dos fallos +son de la misma familia —una bandera que el `configure` acepta y el artefacto ignora, muda y a 50 +minutos de distancia—: uno comprueba `MOZ_REQUIRE_SIGNING: false` dentro del `omni.ja`, el otro que +el `BuildID` sea el que `SOURCE_DATE_EPOCH` obliga. + +**La regla que sale de todo el día:** cuando la duda es «¿llegó al artefacto?», la respuesta no está +en el log del build ni en la receta — está **dentro del artefacto**, y hay que ir a buscarla ahí. + ## 7. La costura: un host de native messaging en Rust Todo lo del §6 que no es CSS pasa por **un solo mecanismo**: un proceso Rust que habla native