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
This commit is contained in:
@@ -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 <cada .so> | 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
|
||||
|
||||
Reference in New Issue
Block a user