Files
takana/recipes/atuq
SergioandClaude Opus 5 a44fc4f06a atuq: AV1 no reproducía, y la causa era UN elemento de LD_LIBRARY_PATH
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
2026-09-07 15:38:02 +00:00
..

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.idno 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:

  1. atuq.cfg prende los contenedores (privacy.userContext.enabled), que Firefox trae apagados;
  2. policies.json los CREA con Containers.Default —el único mecanismo que los pone en un perfil NUEVO— y deja la config de fábrica del proxy en 3rdparty.Extensions, que es de dónde la extensión lee su storage.managed;
  3. extensions/proxy/ los enruta con proxy.onRequest, que es lo único que ve el cookieStoreId de 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: #c15728 el zorro, #1b6470 el disco, #04161d el anillo, #f5ecde los 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.ftl lleva 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 del jarlog cuando el PGO exista, o se tira a la basura la optimización de arranque.
  • La extensión de sct y 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 por scripts/test-atuq-ruteo.py, que abre las dos pestañas por sessionstore y mira a qué puerto llama el navegador. Trae control negativo.)