Files
takana/recipes/atuq
SergioandClaude Opus 5 c5ebbda933 atuq v0.5: proxy por contenedor — el primer diferenciador del §6 que se paga entero
Es el único de la lista del SDD 26 §6 que no pasa por el host de native
messaging del §7: es API de Firefox y nada más. Cada contenedor —Personal,
Trabajo, Banco, Compras— puede salir por su propio proxy.

Tres piezas y ninguna alcanza sola: `atuq.cfg` prende los contenedores (vienen
apagados), `policies.json` los CREA con `Containers.Default` —el único
mecanismo que los pone en un perfil NUEVO— y deja la config de fábrica en
`3rdparty.Extensions`, y `extensions/proxy/` los enruta con `proxy.onRequest`,
que es lo único que ve el `cookieStoreId` de la petición.

Las cuatro se comprobaron DENTRO del artefacto de firefox antes de escribir una
línea, no en la documentación de Mozilla: `Containers` y `3rdparty` en el
`policies-schema.json` de browser/omni.ja, `cookieStoreId` en el
`schemas/proxy.json` de omni.ja, y `storage.managed` leyendo
`getExtensionPolicy(id)` en ext-storage.js. Es la regla del §2.sexies: la
pregunta no es si Mozilla lo tiene, es si NUESTRO build lo tiene.

Tres decisiones que valen más que el código:

1. La config se indexa 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.
2. FAIL CLOSED. Un contenedor con proxy configurado que no se pudo honrar no
   sale directo: va a un destino cerrado y el navegador muestra el error. Salir
   directo sería una fuga silenciosa — la misma familia que el artefacto vacío
   de la regla 3, el fallo que llega hasta el final diciendo que todo fue bien.
3. `proxyDNS` PRENDIDO por defecto: sin él la consulta DNS sale por la línea que
   se quería evitar. Es la fuga clásica de esta configuración.

Y lo que no promete está arriba de todo en la página de opciones, no en un pie:
separación de tráfico, NO anonimato; para anonimato, Tor Browser. El §4 cumplido
donde el usuario lo lee.

De paso, una verdad que estaba escrita en dos sitios pasa a tener un dueño: el
id de cada extensión sale ahora del `manifest.json` y no de una constante de
rebrand.py, el nombre del XPI se deriva de él, y el `install_url` de la política
se cruza contra la ruta donde el fichero quedó escrito de verdad. El icono se
inyecta desde branding/icons/ en vez de estar copiado byte a byte dentro de cada
extensión. Agregar una tercera extensión es ahora un directorio.

PROBADO, corriendo el árbol en la misma jaula que atuq-nested.sh:
  · captura de about:preferences#containers con los cuatro contenedores y sus
    iconos, más el containers.json del perfil;
  · extensions.json del perfil nombra las dos extensiones;
  · `console.info: "atuq/proxy: 4 contenedor(es) enrutado(s)"` — leyó la config
    de fábrica por storage.managed Y la casó con los contenedores de la política;
  · scripts/test-atuq-politica.py: cinco formas de desincronizar política y XPI,
    las cinco matan el build, y el control con la política intacta pasa.

NO probado y dicho por su nombre: que una petición hecha en «Banco» salga por el
proxy de «Banco». Pide automatizar la UI y queda pendiente.

Dos obstáculos del método, que valen para la próxima. La consola de una
extensión es CONTENIDO: `devtools.console.stdout.chrome` (que viene en true) no
la incluye, hace falta `...stdout.content`. Y un `moz-extension://` NO se abre
desde la línea de comandos —muere con `NS_NOINTERFACE [nsIFileURL.file]` y abre
la home en su lugar—, además de que `--screenshot` dispara al `load`, que puede
ocurrir antes de que arranquen las extensiones. Eso último destapó un fallo real
y arreglado: la página de opciones confundía «no hay contenedores» con «la API
no está» y mostraba un mensaje FALSO.

Verificado contra firefox b3:352d7880; se reconstruye contra el firefox con
RLBox cuando selle. La otra mitad de esta unidad —el rename de
recipes/atuq/extension/ a extensions/inicio/— entró sin querer en ff0b556, que
es de otro frente.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-06 01:57:44 +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).
  • Del §6.8: la prueba del paquete. Está probado que los contenedores se crean, que la extensión se instala y que su mapa se construye contra ellos; NO está probado con una captura que una petición hecha en el contenedor «Banco» salga por el proxy de «Banco». Eso pide automatizar la UI (abrir una pestaña de contenedor no se puede desde la línea de comandos), y se anota como pendiente en vez de darlo por hecho.