Gecko SE COME EL PRIMER ELEMENTO de `LD_LIBRARY_PATH` al lanzar el proceso RDD
—el que decodifica vídeo—. El lanzador ponía `/usr/lib/atuq` una sola vez, o sea
primero, así que el RDD arrancaba sin él, no encontraba `libmozavcodec.so` /
`libmozavutil.so` (el ffvpx bundleado, donde vive dav1d) y anotaba:
PlatformDecoderModule FFVPX: Link result: NoProvidedLib
El navegador NO falla ahí: se cae al ffmpeg del sistema, que cubre
H.264/AAC/VP8/VP9/MP3/FLAC/Opus. Pero AV1 por software NO lo cubre —el
decodificador `av1` de ffmpeg es sólo hwaccel y nuestra receta va sin dav1d—, así
que un vídeo AV1 se quedaba en `readyState=1` para siempre, sin un error, sin un
NEEDED faltante y sin una cadena ausente. Media función apagada en silencio, que
es la forma de fallo de esta casa.
Tres corridas que sólo cambian esa variable, con el mismo artefacto:
LD=/usr/lib/atuq:/usr/lib:/lib → RDD NoProvidedLib AV1 ✗
LD=/usr/lib/atuq:/usr/lib/atuq:/usr/lib → RDD Success AV1 ✓
LD=/relleno:/usr/lib/atuq:/usr/lib → RDD Success AV1 ✓
El arreglo es repetir el appdir. Feo y correcto mientras no haya `patchelf` en el
corpus para grabar `RUNPATH=$ORIGIN`, que borraría la variable entera.
Y el guardián que sale de este punto ciego, `scripts/test-atuq-codecs.sh`: abre
cinco muestras versionadas en `scripts/fixtures/codecs/` y mira si `currentTime`
AVANZA — «se creó el decodificador» no es «decodifica». El veredicto sale por
`dump()` al stdout, así que no necesita ni red ni servidor, y corre headless para
que sirva en el worker. Con `--negative-control` se saltea el lanzador y EXIGE
que AV1 falle: probado en los dos sentidos, 5/5 y control ✓.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
atuq — el envoltorio Gecko de la distro
atuq (quechua: zorro) es el navegador de la distro: un artefacto DERIVADO de firefox, no un
fork de su fuente. El diseño completo está en docs/26-atuq-envoltorio-gecko.md.
Este directorio ES la fuente de atuq — recipes/atuq.toml lo declara con source.dir y hammer lo
hashea por CONTENIDO. Editar un fichero de acá mueve el ArtifactHash; no hay commit que pinear.
Qué hay acá y dónde cae
| Acá | En el artefacto | Qué hace |
|---|---|---|
prefs/autoconfig.js |
defaults/pref/autoconfig.js |
Le dice a Gecko que lea atuq.cfg. Es el único gancho posible antes de que arranque el perfil. |
atuq.cfg |
<appdir>/atuq.cfg |
Prefs de fábrica + carga la hoja de estilo del chrome. |
chrome/atuq.css |
<appdir>/chrome/atuq.css |
El aspecto. Se inyecta como AGENT_SHEET desde atuq.cfg. |
distribution/policies.json |
<appdir>/distribution/policies.json |
Política de fábrica: telemetría, updates, la instalación de las extensiones, los CONTENEDORES y la config de fábrica del proxy. |
extensions/inicio/ |
<appdir>/distribution/extensions/inicio@atuq.tawasuyu.xpi |
Página de inicio y pestaña nueva. |
extensions/proxy/ |
<appdir>/distribution/extensions/proxy@atuq.tawasuyu.xpi |
Proxy por contenedor (SDD 26 §6.8). |
Por qué la extensión se instala por POLÍTICA y no por carpeta
Dejar el .xpi en distribution/extensions/ era el mecanismo clásico de las distros y ya no
funciona: Firefox retiró el sideloading desde esa carpeta. Se comprobó midiendo, no leyendo — con
el XPI puesto ahí, el extensions.json del perfil no lo mencionaba siquiera, y el log no decía nada:
un fallo perfectamente silencioso.
El mecanismo vigente es ExtensionSettings en policies.json, con install_url apuntando al XPI
por ruta absoluta del FHS. Eso es estable acá porque el árbol se hidrata siempre en /usr/lib/atuq.
Una extensión nueva no cuesta nada, y su id tiene un solo dueño
extensions/<lo-que-sea>/ con un manifest.json adentro y ya: rebrand.py recorre el directorio,
saca el id de browser_specific_settings.gecko.id —no de una constante suya— y escribe
<id>.xpi. El icono lo inyecta desde branding/icons/atuq128.png, así que tampoco hay que copiarlo
en cada extensión: la v0.4 tenía ese PNG duplicado byte a byte y con dos extensiones habrían sido
tres copias que se desalinean en cuanto alguien actualice una sola.
Y después cruza los ids contra policies.json, en los dos sentidos: una extensión empaquetada
que la política no declara, o una política que apunta a un XPI que no existe, matan el build. Lo
mismo con la ruta: el install_url tiene que ser exactamente donde el XPI quedó escrito. Antes eso
era una constante contra otra constante; ahora una de las dos se DERIVA del fichero real.
scripts/test-atuq-politica.py le pone delante las cinco formas conocidas de romper ese cruce y
exige que las cinco maten el build (más un control con la política intacta, que tiene que pasar).
El proxy por contenedor (§6.8)
Tres piezas, y ninguna alcanza sola:
atuq.cfgprende los contenedores (privacy.userContext.enabled), que Firefox trae apagados;policies.jsonlos CREA conContainers.Default—el único mecanismo que los pone en un perfil NUEVO— y deja la config de fábrica del proxy en3rdparty.Extensions, que es de dónde la extensión lee sustorage.managed;extensions/proxy/los enruta conproxy.onRequest, que es lo único que ve elcookieStoreIdde cada petición.
La config se escribe 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. Dos capas: storage.managed es la de fábrica (la distro) y storage.local la del
usuario, que pisa por contenedor.
⚠ Es separación de tráfico, NO anonimato, y eso está escrito arriba de todo en la propia página de opciones, no en un pie de página. No toca la huella del navegador.
Fail closed. Un contenedor que TIENE proxy configurado y no se pudo honrar no sale directo: va a
un destino cerrado (127.0.0.1:9) y el navegador muestra el error. Salir directo sería una fuga
silenciosa, que es la peor forma de este fallo. Y proxyDNS viene prendido: sin él la consulta DNS
sale por la línea que se quería evitar.
Por qué el CSS no es userChrome.css
userChrome.css vive en el PERFIL del usuario y exige que el usuario prenda
toolkit.legacyUserProfileCustomizations.stylesheets. Una distro no puede depender de eso: el
navegador tiene que verse como atuq la primera vez que arranca, con un perfil recién creado. La vía
que sí funciona a nivel de aplicación es el nsIStyleSheetService desde autoconfig, que es lo que
hace atuq.cfg.
La marca
El arte viene de un tablero de identidad del operador (2026-09-05): un zorro en medallón con cenefa andina, sobre disco teal y anillo noche.
branding/icons/atuq{16,32,48,64,128,256}.png— el medallón recortado del tablero, con máscara circular y alfa, reducido con LANCZOS desde un master de 1024 px. Se generan una vez y se copian; no se dibujan en cada build.- La paleta del chrome se muestreó del propio medallón, tomando el color más frecuente de cada
zona, en vez de estimarla a ojo:
#c15728el zorro,#1b6470el disco,#04161del anillo,#f5ecdelos reflejos. Un token escrito «parecido» es cómo una identidad se desalinea de su logo en tres ediciones. - Del tablero se toman la forma y el color; los eslóganes NO — el operador los descartó
explícitamente.
brand.ftllleva el nombre y nada más.
⚠ A 16 px el medallón pierde el detalle y queda como una mancha cálida redonda. Es intrínseco a un logo con cenefa, no un defecto del escalado: la salida sería un dibujo simplificado para ese tamaño, y eso lo decide quien dibujó el zorro.
Lo que TODAVÍA no está
- El chrome de verdad dentro de
omni.ja(split view, dientes propios). El re-empaque determinista ya está resuelto y probado; lo que falta es el contenido. Sigue en pie la condición de preservar el orden deljarlogcuando el PGO exista, o se tira a la basura la optimización de arranque. - La extensión de
scty el host de native messaging (SDD 26 §6.1 y §7). (El §6.8 ya no tiene pendientes: que una petición hecha en «Banco» sale por el proxy de «Banco» está probado porscripts/test-atuq-ruteo.py, que abre las dos pestañas por sessionstore y mira a qué puerto llama el navegador. Trae control negativo.)