1# SDD 26 — `atuq`: el envoltorio Gecko de la distro
2
3Escrito 2026-09-05, a partir de la pregunta del usuario: *«ya habiendo compilado firefox y waterfox,
4qué tan complicado vs provechoso suena crear nuestro propio envoltorio, en vez de zen?»*, y de su
5aclaración de por qué la pregunta existe: **`puriy` quedó muy verde y es difícil sacarlo de ahí**.
6
7`atuq` es *zorro* en quechua. El nombre estaba libre —verificado con `grep -ri atuq` sobre
8`/mnt/vvv/hammer` y `/mnt/vvv/tawasuyu`, cero coincidencias— y dice exactamente lo que la cosa es:
9otro fox, con marca propia y sin usar la de Mozilla (ver el §branding de `recipes/firefox.toml`).
10
11**Este documento no reemplaza a `puriy` ni lo cancela.** `puriy` sigue siendo el motor soberano; el
12§9 explica por qué `atuq` es su andamio y no su desvío.
13
14---
15
16## 1. La corrección que ordena todo: no hay envoltorio fuera del chrome
17
18La intuición natural —una carcasa Llimphi con nuestros widgets, y Gecko adentro pintando la página—
19**no es posible**. Gecko no tiene API de embebido en escritorio desde que murió XULRunner; GeckoView
20existe y es de Android. No hay forma soportada de meter el motor en una ventana de `mirada`.
21
22⇒ un envoltorio de Gecko es, necesariamente, **chrome-level**: JS/CSS/XHTML *dentro* del propio
23Firefox, más extensiones, más procesos externos que hablen con él.
24
25Y eso es exactamente lo que Zen es. Zen no toca el motor: su diferencia entera —workspaces, split
26view, glance, compact mode— vive en el chrome, más una herramienta (`surfer`) que reaplica sus
27parches sobre el árbol de Firefox y recompila. **Su costo no es el motor: es la cinta de correr del
28rebase cada cuatro semanas.** Copiar su método sería heredar su costo sin heredar su equipo.
29
30## 2. La decisión de arquitectura: artefacto DERIVADO, no fork de fuente
31
32Si `atuq` parchea el árbol, **cada iteración de UI cuesta un build de Gecko de cuatro horas**. Eso
33mata el proyecto en la primera semana de diseño, porque el diseño de chrome es iterativo por
34naturaleza.
35
36Pero nosotros no distribuimos un binario: sellamos artefactos. Entonces `atuq` es una receta cuyo
37`[deps]` incluye `firefox`, y cuyas fases copian el árbol instalado e inyectan encima:
38
39| Qué se inyecta | Mecanismo de Firefox | Sin recompilar |
40|---|---|---|
41| Marca, iconos, nombre | `distribution/`, `application.ini` | ✅ |
42| Prefs de fábrica | `defaults/pref/*.js` + autoconfig (`.cfg`) | ✅ |
43| Política de empresa | `distribution/policies.json` | ✅ |
44| Chrome propio (dientes, layout) | `userChrome.css` + re-empaque de `omni.ja` | ✅ |
45| Extensiones nuestras | `distribution/extensions/` | ✅ |
46
47**Ventajas que esto tiene sobre el método de Zen, y que son estructurales:**
48
491. **Iteración en segundos, no en horas.** El chrome se toca sin volver a compilar C++.
502. **No mueve el `ArtifactHash` de `firefox`.** El corpus no se invalida; `waterfox` y cualquier otro
51 fork siguen compartiendo el mismo artefacto base.
523. **Las CVE se heredan gratis.** Un fork de fuente te vuelve dueño del reloj de seguridad del
53 binario más atacado de la máquina — es lo que hundió a casi todos los forks de Firefox, y el
54 reproche histórico a Waterfox. Un derivado se reconstruye solo cuando sube `firefox`.
55
56**Los dos gotchas reales del camino, que son trabajo pero chico:**
57
58- **`omni.ja` es un zip ⇒ hay que re-empacarlo determinista** (mtimes fijos, orden estable) o el
59 artefacto deja de reproducir bit a bit. Es la misma disciplina que ya exige la imagen del lab.
60- **El `jarlog` del PGO ordena el `omni.ja` para el arranque** (§3.bis). Re-empacarlo a lo bruto
61 tira esa optimización a la basura sin que nadie lo note. El re-empaque preserva el orden.
62- **Hay que invalidar el startup cache** tras tocarlo (fichero `.purgecaches` junto al binario, o
63 bump del BuildID). Si no, los cambios no se ven y **parece que el overlay no agarró**: un falso
64 negativo de los caros, de la misma familia que el cache-hit que congela regresiones.
65
66### 2.bis Cuándo SÍ hay que parchear el árbol
67
68Cuando algo necesite tocar C++ del motor. Hoy conocemos un caso: `sct` en su forma fuerte (§6.1).
69La regla es: **nada baja al árbol antes de que exista la toolchain del §3**, porque hasta entonces
70cada intento cuesta cuatro horas. Y cuando baje, el vigía de parches (`scripts/vigia-parches.py`,
71commit `aa200a4`) es lo que convierte esas cuatro horas en un minuto para saber si el parche agarra.
72
73## 3. La puerta de las optimizaciones: no es un flag, es una toolchain
74
75El usuario decidió habilitar las optimizaciones. Lo que hay que saber antes de intentarlo:
76
77`recipes/firefox.toml` va con `compiler = "gcc"` por el **muro 3** (zig enlaza `libc++` estática para
78musl y el `configure` de Mozilla exige encontrar un `NEEDED …libc++`; es negativa de upstream, no una
79perilla). El problema es que **PGO, LTO y BOLT son cadena de clang en Gecko**:
80
81- `--enable-lto=cross` quiere clang + lld. El LTO de GCC sobre Gecko no está soportado upstream.
82- `--enable-profile-use` espera `-fprofile-instr-use` (clang), no `-fprofile-use` (gcc).
83- BOLT necesita `llvm-bolt`.
84
85Y **el corpus no tiene clang usable como compilador**. Verificado en las recetas, no supuesto:
86
87- `recipes/clang18.toml` compila `ninja -C build libclang.so clang-resource-headers` y su fase
88 `install` copia **sólo** `build/lib/libclang.so*` y las cabeceras `clang-c`. Está ahí porque
89 `bindgen` carga `libclang.so` en runtime. **No hay driver `clang`, ni `lld`, ni `libc++`.**
90- `llvm18` se selló con `-DLLVM_ENABLE_PROJECTS=""` — LLVM a secas.
91
92⇒ la primera versión de este documento concluyó que hacía falta **una receta `llvm-toolchain`
93(clang+lld+libc++ desde fuente)**. **Era caro de más, y se corrigió el mismo día mirando el lab en
94vez de suponerlo:**
95
96- `.dev-fs/alpine` **ya trae clang22 + llvm22 22.1.8** — la misma major que usa el APKBUILD de
97 Alpine para este mismo Firefox (`_llvmver=22`).
98- **`Compiler::Clang` ya existe en hammer**, cableado de punta a punta (`parse_compiler` lo acepta;
99 `hammer-build` pone `CC=clang`, `CXX=clang++`, `AR=llvm-ar`). Ninguna receta lo usaba.
100- Faltaba **sólo `ld.lld`**: `apk add lld` ⇒ dos paquetes, cero upgrades.
101
102Y los tres muros caen sin perder lo que gcc daba: el sondeo de linker se satisface con
103`--enable-linker=lld`, el `ar` lo pone hammer solo, y el `NEEDED` de la stdlib de C++ existe porque
104clang++ de Alpine usa la `libstdc++` **compartida** — la prueba no es teórica: Alpine construye este
105Firefox con clang22 y **sin** `libcxx` en sus makedepends.
106
107**La huella del lab no se movió, y se midió antes de tocar nada.** `lld` no casa ningún prefijo de
108`TOOLCHAIN_PREFIXES` (`hammer-core/src/lab.rs`), así que los 43 paquetes que entran en `hash_inputs`
109salieron idénticos ⇒ **los 837 artefactos sellados quedan intactos**. Eso abarata el cambio hoy y a
110la vez deja un agujero escrito: la versión de `lld` no es parte de la identidad del artefacto, y sólo
111expone a las recetas `compiler="clang"` — hoy, una. Cerrarlo cuesta re-hashear el corpus entero.
112
113**Hecho el 2026-09-05** (commit `cb3ecd5`): `firefox` va con `compiler = "clang"`,
114`--enable-linker=lld`, `--enable-lto=cross`, `--enable-packed-relative-relocs` y
115`--with-unsigned-addon-scopes=app,system`. Falta construirlo.
116
117**Riesgos de PGO, escritos antes de empezar:**
118
119- El PGO de Mozilla **corre el navegador** para juntar el perfil (`profileserver.py`, headless +
120 marionette). Dentro de un sandbox hermético, sin red y sin X11, eso hay que hacerlo andar.
121 Headless no necesita display, pero sí necesita que el árbol de perfil se genere adentro.
122- **El perfil resultante es un input del artefacto.** O entra en `hash_inputs`, o `atuq` deja de ser
123 determinista y no lo vamos a notar: sería otra forma del lab que no está en `hash_inputs`.
124- Ganancia esperable, para calibrar expectativas: PGO+LTO en Gecko son del orden de 10-25% en carga
125 de página y JS. **Hoy estamos ATRÁS de Zen en este eje, no adelante** — Zen hereda la
126 configuración de Mozilla tal cual. Esto no es una ventaja nuestra: es una deuda que se salda.
127
128### 3.bis El diff contra Alpine — y no es sólo PGO
129
130Medido el 2026-09-05 trayendo el `APKBUILD` y el `mozconfig` de `community/firefox` de aports y el
131`PKGBUILD` de Arch, y comparándolos línea a línea con el mozconfig de `recipes/firefox.toml`.
132**Alpine es nuestro propio upstream** —de ahí salen los once parches de musl— así que la comparación
133no es contra una distro lejana: es contra la que ya construye este mismo código.
134
135| | `recipes/firefox.toml` | Alpine `community/firefox` |
136|---|---|---|
137| LTO | **no** | `--enable-lto=cross` |
138| PGO | **no** | `--enable-profile-use=cross` + `merged.profdata` + `jarlog` |
139| Linker | GNU ld (via gcc) | `--enable-linker=lld` (lld 22) |
140| RELR | **no** | `--enable-packed-relative-relocs` |
141| Sandbox RLBox | **APAGADO** (`--without-wasm-sandboxed-libraries`) | `--with-wasi-sysroot` |
142| Extensiones sin firmar | **no declarado** | `--with-unsigned-addon-scopes=app,system` |
143| Allocator | `--disable-jemalloc` | `--disable-jemalloc` — **igual, no es desviación nuestra** |
144| Librerías | todo bundleado | `--with-system-{icu,nss,nspr,av1,vpx,webp,png,jpeg,zlib,ffi,libevent,pixman,pipewire}` |
145
146Arch hace lo mismo en el eje de velocidad («Do 3-tier PGO», `--enable-lto=cross`,
147`--enable-profile-use=cross`). **Ni Alpine ni Arch usan BOLT** (cero menciones en los dos ficheros)
148⇒ BOLT no es la desventaja, es el extra. Lo que nos separa del resto del mundo es **PGO + LTO**, del
149orden de 10-20% en carga de página y JS.
150
151**Tres lecturas que hay que sacar de esa tabla:**
152
1531. **RLBox apagado es una desventaja de SEGURIDAD, no de velocidad, y probablemente pesa más que el
154 PGO.** Es la jaula wasm alrededor de los parsers de fuentes y medios —graphite, ogg, expat,
155 woff2—, o sea exactamente el código que come entrada no confiable. Encenderlo pide `wasi-sdk` +
156 `wasi-compiler-rt` en el corpus: receta propia, no un flag.
1572. **`--with-unsigned-addon-scopes=app,system` nos falta y `atuq` lo NECESITA** para shipear sus
158 propias extensiones desde `distribution/extensions/` (§6.1, §7). Es un flag de `configure` ⇒
159 **una capacidad de `atuq` que no se resuelve en el overlay: exige tocar la base.**
1603. **Las librerías del sistema son diferencia a propósito, no deuda.** Alpine usa las suyas; nuestro
161 corpus *es* la fuente y bundlear es lo que mantiene la clausura cerrada.
162
163**Corolario que ordena el plan: todo esto entra en UN SOLO rebuild de `firefox`.** Cada rebuild son
164cuatro horas y re-sella la cola Gecko entera (waterfox incluido), así que LTO, PGO, RELR, lld, RLBox
165y el scope de addons se juntan en una sola pasada, no en seis.
166
167### 3.ter Los dos muros del PGO que las distros no tienen
168
169**El perfil se junta corriendo el navegador, y las dos distros lo hacen bajo `xvfb-run`** (Alpine:
170`xvfb-run -a -s "-screen 0 1920x1080x24" ./mach python build/pgo/profileserver.py`; Arch, idéntico).
171**Nosotros no tenemos X11 en el corpus** —es la decisión Wayland-only de toda la distro— así que ese
172camino no existe acá. La salida hammer-nativa es correrlo bajo un **sway headless**
173(`WLR_BACKENDS=headless`), que ya tenemos del frente wlr/sway y arranca en segundos.
174
175**Y el perfil no es determinista.** Los contadores dependen del timing de la corrida, así que dos
176generaciones del profdata no dan los mismos bytes — a las distros no les importa porque no persiguen
177bit-repro; a nosotros nos rompe el invariante. La salida: **generar el perfil UNA vez y sellarlo como
178artefacto propio, consumido por hash** desde `firefox`. El build optimizado vuelve a ser determinista
179aunque su insumo no lo sea, y el perfil se regenera a propósito, no por accidente.
180
181## 4. Lo que `atuq` NO promete
182
183Esta sección existe antes que la lista de features a propósito, con el mismo criterio que el modelo
184de adversario de `qullqa`: **lo que no se promete se escribe primero, porque una promesa falsa de
185privacidad es peor que no ofrecer nada.**
186
187**`atuq` no es Tor Browser y no va a tener «modo Tor».** Tor Browser no es «Firefox + SOCKS»: su
188valor son las contramedidas de huella (RFP, letterboxing, normalización de fuentes/canvas/timers) y
189sobre todo **un conjunto de anonimato donde todos se ven idénticos**. `atuq` es, por construcción, un
190binario único en el mundo: musl, Wayland-only, branding propio, extensiones propias. Alguien saliendo
191por Tor desde `atuq` es **más** identificable, no menos, y su conjunto de anonimato son diez
192personas. Para anonimato: Tor Browser, y lo decimos nosotros primero.
193
194Lo que sí se ofrece, y es honesto porque es otra cosa: **proxy por contenedor** (§6.8) — separación
195de tráfico, no anonimato.
196
197**`atuq` tampoco promete** anti-fingerprinting propio (es un programa de investigación con equipo
198full-time) ni bloqueo de anuncios propio (se shipea uBO y listo).
199
200## 5. Contexto verificado al escribir (2026-09-05)
201
202De `docs/state/build-state.json`, no de memoria:
203
204| Dato | Valor |
205|---|---|
206| Recetas / nodos | 838 / 840 |
207| Selladas | 837 |
208| `firefox` 154.0 | **sealed**, cola `corpus` |
209| `waterfox` 6.7.1.1 | **never** — primer build en curso |
210| Plataforma compartida | `gtk3`, `atk`, `nodejs`, `clang18`, `cbindgen`: todas sealed |
211
212**`atuq` no arranca hasta que `waterfox` selle.** Waterfox es la prueba de la tesis del reúso —«las
213diferencias reales con `firefox.toml` son TRES: la fuente, el branding y la versión»—. Si esa tesis
214falla, el precio de todo este documento cambia y hay que releerlo.
215
216## 6. Los diferenciadores, priorizados
217
218La columna «quién más lo tiene» es lo que evita que nos contemos un cuento.
219
220| # | Qué | Quién más lo tiene | Pieza que ya existe | Costo |
221|---|---|---|---|---|
222| 6.1 | **`sct` — transparencia de scripts** | **nadie** | `puriy-sct`, `puriy-sct-testigo` | medio |
223| 6.2 | **Descargas direccionadas por contenido** | nadie | store CAS de hammer, `tejido` | bajo |
224| 6.3 | **Archivo personal + RAG local** | Rewind/Recall (nube, Windows); SingleFile (guarda, no busca) | `khipu`, `rag-motor`, `willay-rag` | medio |
225| 6.4 | **Historial y perfil sobre `qullqa`** | nadie | `qullqa-core`, `qullqa-pozo` | medio |
226| 6.5 | **Foco por `cortafuegos`, no por extensión** | nadie (nadie es dueño del navegador *y* del sistema) | `cortafuegos`, `pacha` | bajo |
227| 6.6 | **Medios por fuera del navegador** | extensiones sueltas; de fábrica no | `foreign-ytdlp`, `-platform`, `-dlna` | bajo |
228| 6.7 | **IA local en la barra lateral** | Chrome/Edge son nube; Zen no tiene | `rimay`, `iniy` | bajo |
229| 6.8 | **Proxy por contenedor** | nadie de fábrica | — (API de Firefox) | bajo |
230| 6.9 | **Torrent adentro** | **Vivaldi lo trae; Brave tuvo WebTorrent** | `shared/foreign-torrent` (SDD propio, sobre librqbit) | bajo |
231
232### 6.1 `sct` es la joya
233
234BLAKE3 a todo lo que se ejecuta, y **negarse a correr código que ningún testigo vio nunca**. Es lo
235más cerca que hay de «Certificate Transparency, pero para el JavaScript que te corre en la cara», y
236no lo tiene nadie: ni Zen, ni Brave, ni Tor Browser.
237
238En tawasuyu ya está escrito y —dice su README— la lógica es **agnóstica del transporte** y está
239certificada sin red; `puriy-sct-testigo` es sólo el cable HTTP.
240
241- **v1, sin tocar C++:** extensión con `webRequest` bloqueante que hashea cada respuesta de script y
242 consulta al testigo antes de dejarla pasar.
243- **v2, forma fuerte:** gancho en el script loader de Gecko. Eso sí es parche de árbol ⇒ §2.bis ⇒
244 después de la toolchain del §3.
245
246### 6.2 Una descarga con identidad
247
248Lo bajado no es un archivo en `~/Downloads`: es un objeto BLAKE3 en el store — deduplicado,
249verificable, y re-compartible por `tejido`. El torrent (6.9) alimenta lo mismo. **Ningún navegador
250trata una descarga como algo con identidad**; para todos es un blob con nombre, y por eso lo bajás
251dos veces y no lo sabés.
252
253Ésa, y no «tener torrent», es la razón por la que 6.9 vale: Vivaldi ya tiene torrent, pero termina en
254una carpeta.
255
256### 6.3 El archivo personal es la razón por la que alguien usaría `atuq`
257
258Cada página visitada se congela en el CAS y entra al DAG de `khipu`; después le preguntás a tu
259propio historial en lenguaje natural, **todo local**. Es la feature que más justifica el proyecto
260entero, y sale de piezas que ya están escritas.
261
262### 6.4 `qullqa` con su advertencia puesta
263
264Almacenamiento con negación plausible y modelo de adversario **escrito** — ningún navegador tiene
265eso; el modo incógnito es teatro. ⚠ Con la advertencia que el propio SDD de `qullqa` ya anotó y que
266**hay que repetir acá, no esconder**: en una máquina con swap sin cifrar, el documento no aplica.
267Se promete lo que se puede probar.
268
269### 2.ter Dónde vive la fuente de `atuq` — hizo falta un modo de `[source]` nuevo
270
271Escribir la receta destapó un muro que este documento no había visto: `[source]` era
272**obligatoriamente** git o tarball, así que una receta cuyo contenido no viene de upstream sino de
273nosotros no se podía ni expresar. Los rodeos eran todos peores — fetchear una fuente que después se
274ignora **miente** sobre la identidad del artefacto, y colgar el overlay de un repo aparte obliga al
275worker a tener acceso a un repo privado.
276
277Se agregó **`source.dir`** (commit `54fa4a9`): un árbol dentro del propio repo, hasheado por
278**contenido** con `ArtifactHash::of_tree` —que ya existía para verificar bit-reproducibilidad en
279Stage 2— y no por ruta. Es la misma disciplina que ya tenían los `patches`, que entran al hash por
280bytes. Editar un CSS del overlay mueve el `ArtifactHash`; renombrar el directorio, no.
281
282Un `.swm` **no** puede llevar una receta derivada, y los cuatro sitios que lo tocan fallan
283diciéndolo: el manifiesto es compartible y necesita un puntero resoluble desde fuera.
284
285**Hecho el 2026-09-05** (commits `aeebb19` y `6f6c32b`): `recipes/atuq.toml` + `recipes/atuq/`.
286La **v0.2** (`b3:7eff4ba4`) ya trae el branding: `application.ini` (Vendor/Name/RemotingName/
287CodeName, con el `ID` intacto para no salirse del ecosistema de complementos), `brand.ftl` dentro de
288`browser/omni.ja`, los binarios renombrados, iconos propios y `.desktop`. El re-empaque preserva
289orden, `STORED` y fechas del original, y se verificó contra el artefacto: **5306 entradas, mismo
290orden, y una sola con contenido distinto**. El `BuildID` se deriva del contenido del overlay —no de
291la fecha— porque es la clave con la que Gecko invalida su startup cache: sin eso, un cambio de
292chrome arranca con la interfaz vieja y *parece* que el overlay no agarró.
293
294La capa de configuración de la v0.1 son cuatro ficheros —autoconfig, `atuq.cfg`, `chrome/atuq.css`,
295`policies.json`— y el CSS se registra como `USER_SHEET` desde autoconfig porque `userChrome.css`
296vive en el perfil y exigiría que el usuario prenda una pref: atuq tiene que verse como atuq en el
297primer arranque. Falta construirlo: su dep `firefox` está en vuelo.
298
299### 2.quater Abrirlo en una pantalla: tres fallos que la clausura no podía ver
300
301El 2026-09-05, con `atuq` sellado y verificado, se hidrató y se abrió en el compositor de la sesión
302(waypipe). **No arrancó**, y los tres motivos son la diferencia entre «sellado» y «usable»:
303
3041. **EXDEV al hidratar.** `hydrate` proyecta con hardlinks y `linkat()` rechaza cruzar un punto de
305 montaje aunque los dos lados sean el mismo filesystem. Acá el store es `/dev/sdb` bind-monteado
306 y `work/` vive en `/dev/sdc`. El rootfs va bajo `/mnt/cosecha`, donde el volumen está montado
307 entero. Se comprueba con `findmnt -T`, **nunca** con `stat -c %d`.
3082. **El lanzador no puede ser un symlink.** Ni el binario ni sus `.so` traen `RPATH`/`RUNPATH`
309 (`readelf -d`), así que el motor no encontraba su propio `libnspr4.so` y moría en
310 `XPCOMGlueLoad`. Es un script con `LD_LIBRARY_PATH` + `exec`, como Debian y Fedora. La
311 alternativa limpia —`RPATH=$ORIGIN` con `patchelf`, que es lo que hace Alpine— espera a que
312 `patchelf` exista como receta del corpus.
3133. **Un bug del corpus, no de atuq: `atk` se había tragado GObject.** Declaraba la variante
314 ESTÁTICA de glib y produce un objeto compartido, así que `libatk-1.0.so` llevaba una copia
315 entera del sistema de tipos. Dos GObject en un proceso ⇒ 45 `GLib-GObject-CRITICAL` y
316 `Segmentation fault`. **El síntoma no nombra a atk**, y no se ve mirando el rootfs: había una
317 sola `libgobject`. Se cazó preguntando quién DEFINE el símbolo:
318 `nm -D --defined-only <cada .so> | grep " T g_type_register_static"` — deben salir uno, salieron
319 dos.
320
321Radio medido antes de tocar (`yupana radio atk`): 4 transitivos, 3 sellados a deuda; GNOME fuera
322porque usa gtk4. La cadena `atk → gtk3 → firefox → atuq` volvió a sellar 4/4, y **firefox tardó
323~50 minutos, no las cuatro horas** que repetía este documento — el número venía del folclore de la
324receta, no de una medición.
325
326**La lección para el plan: ninguna unidad de este frente está cerrada hasta que algo se ABRE en una
327pantalla.** La clausura decía 100% con un navegador que no llegaba a pintar un píxel.
328
329### 2.quinquies La v0.3 y los tres mecanismos: dos muertos, uno vivo
330
331La página de inicio y la pestaña nueva parecían un `pref` y resultaron ser un frente. Lo que se
332midió, en orden:
333
334| Mecanismo | Resultado |
335|---|---|
336| `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 |
337| `policies.json` → `ExtensionSettings` con `install_url` | **Se lee** (`browser.policies.applied=true` en el perfil) y falla nombrándose: `ERROR_SIGNEDSTATE_REQUIRED` |
338| `xpinstall.signatures.required = false` | **No alcanza** — la exigencia viene COMPILADA |
339
340**El instrumento que destrabó el diagnóstico fue un testigo.** `defaultPref` no deja rastro en
341`prefs.js` —sólo se guarda lo que difiere del default— así que un autoconfig que **no se ejecuta**
342es indistinguible de uno que sí y no hace nada. `atuq.cfg` escribe ahora `atuq.autoconfig.ok` como
343pref de **usuario**, legible desde fuera sin abrir el navegador. Salió `true` ⇒ el `.cfg` corría, y
344por lo tanto la pref de firma se estaba ignorando.
345
346De ahí `MOZ_REQUIRE_SIGNING` vacío en `recipes/firefox.toml`. Dos detalles que costaron cada uno su
347vuelta:
348
3491. El default sale del **milestone** (`milestone.is_release_or_beta`), no del canal de actualización.
350 El nuestro es `default` y la exigencia estaba activa igual.
3512. **Se desactiva con el valor VACÍO, no con cero.** `=0` muere con «takes 0 values»: es booleana y
352 el cero es un valor que no acepta. Leído del parser de mozbuild, no deducido.
353
354**El precio, escrito:** este Firefox deja de exigir la firma de Mozilla para cualquier complemento.
355La alternativa es firmar en AMO —cuenta y revisión de Mozilla por versión—, que es la dependencia
356externa que esta distro existe para no tener. Y no es un desvío: **sin esto, el `sct` del §6.1
357tampoco se podría shipear nunca**, así que la unidad 6 dependía de ésta sin que el plan lo dijera.
358
359**Método que conviene repetir:** la prueba del testigo se hizo **sin reconstruir**, montando el
360`.cfg` modificado con `--ro-bind` por encima del artefacto. Editar el fichero en el rootfs habría
361escrito sobre un **hardlink del store** y corrompido el artefacto sellado.
362
363## 7. La costura: un host de native messaging en Rust
364
365Todo lo del §6 que no es CSS pasa por **un solo mecanismo**: un proceso Rust que habla native
366messaging con la extensión de `atuq` y, del otro lado, con la suite (`agora` para identidad Ed25519,
367`khipu`, el store, `foreign-*`, `cortafuegos`).
368
369Que sea **uno** y no siete es la decisión: cada feature del §6 pasa a ser un verbo de ese host, no un
370proyecto nuevo. El binario vive en tawasuyu (es suite, no build system); `atuq` sólo lo declara como
371dep y le deja el manifiesto de native messaging en su sitio. **El nombre del crate se decide en
372tawasuyu y con su regla 10 puesta** — acá no se bautiza nada de allá.
373
374## 8. Plan, por unidades de trabajo
375
376Cada una cierra sola, se commitea y se pushea. El orden no es preferencia: cada una destraba a la
377siguiente.
378
379| # | Unidad | Puerta que abre | Bloqueada por |
380|---|---|---|---|
381| 1 | `waterfox` sella | valida la tesis del reúso | build en curso |
382| 2 | ~~`llvm-toolchain`~~ → **`apk add lld` + `compiler="clang"`** ✅ 2026-09-05 | LTO **y** el muro 3 | — |
383| 3 | Construir el `firefox` con LTO (hash `b3:6f2a3b2f`) | el eje de velocidad **y** las extensiones de `atuq` | 2 ✅ |
384| 3.a | PGO: perfil bajo sway headless, **sellado como artefacto propio** y consumido por hash | la otra mitad de la ganancia | 3 |
385| 3.b | `wasi-sdk` + `wasi-compiler-rt` ⇒ RLBox encendido | cierra el hueco de seguridad del §3.bis | 2 |
386| 4 | **`atuq`: receta derivada + overlay de chrome** ✅ v0.1 escrita | el andamio de todo lo demás | 3 |
387| 4.b | Branding + re-empaque de `omni.ja` ✅ v0.2 | el chrome de verdad | — |
388| 4.d | **Abrirlo en pantalla** — destapó EXDEV, el lanzador, el `atk` envenenado y la cadena GTK3 estática | «usable», no sólo «sellado» | — |
389| 4.e | **v0.3: página de inicio y pestaña nueva** por extensión de sistema; exigió `MOZ_REQUIRE_SIGNING` vacío | y con eso queda destrabada la unidad 6 (`sct`) | rebuild de firefox |
390| 4.c | **`MOZ_BUILD_DATE` determinista en `firefox`** — hoy el `BuildID` es la fecha del build ⇒ el artefacto **no reproduce bit a bit** | el invariante de reproducibilidad | otro build de 4 h + re-sellar atuq |
391| 5 | Host de native messaging (tawasuyu) | los verbos del §6 | 4 |
392| 6 | `sct` v1 (extensión + testigo) | el diferenciador que nadie tiene | 5 |
393| 7 | Descargas al CAS | 6.2, y alimenta 6.9 | 5 |
394| 8 | Archivo + RAG | 6.3 | 5, 7 |
395| 9 | Proxy por contenedor, torrent, medios, foco | 6.5–6.9 | 5 |
396
397Las unidades 2 y 4 son **paralelizables**: la toolchain no toca el chrome y el chrome no toca la
398toolchain. Si hay dos frentes, van juntas.
399
400## 9. Por qué esto no abandona a `puriy`
401
402`puriy` es el motor DOM/CSS propio con JS real (QuickJS-NG sobre `wasmi`) cuyo límite declarado no es
403el lenguaje sino **el wiring nativo**: DOM bindings completos, red, render. Eso no se acelera
404escribiendo otro navegador; se acelera con tiempo.
405
406`atuq` no le compite porque no comparte una sola línea con él, y **le sirve** porque todo lo del §6
407vive del lado Rust: el host del §7, el testigo de `sct`, las descargas al CAS, el archivo con RAG.
408Son **agnósticos del motor**. Cuando `puriy` madure, se enchufan del mismo lado sin reescribirse.
409
410Dicho al revés, que es como conviene recordarlo: **`atuq` es el navegador que se puede usar mientras
411`puriy` crece, y el andamio de las features que `puriy` va a heredar.** Si dentro de dos años `atuq`
412se apaga, lo que se tira es una receta derivada y una hoja de CSS; todo lo demás sobrevive.
26-atuq-envoltorio-gecko.md
SDD 26 — atuq: el envoltorio Gecko de la distro
Escrito 2026-09-05, a partir de la pregunta del usuario: «ya habiendo compilado firefox y waterfox,
qué tan complicado vs provechoso suena crear nuestro propio envoltorio, en vez de zen?», y de su
aclaración de por qué la pregunta existe: puriy quedó muy verde y es difícil sacarlo de ahí.
atuq es zorro en quechua. El nombre estaba libre —verificado con grep -ri atuq sobre
/mnt/vvv/hammer y /mnt/vvv/tawasuyu, cero coincidencias— y dice exactamente lo que la cosa es:
otro fox, con marca propia y sin usar la de Mozilla (ver el §branding de recipes/firefox.toml).
Este documento no reemplaza a puriy ni lo cancela. puriy sigue siendo el motor soberano; el
§9 explica por qué atuq es su andamio y no su desvío.
1. La corrección que ordena todo: no hay envoltorio fuera del chrome
La intuición natural —una carcasa Llimphi con nuestros widgets, y Gecko adentro pintando la página—
no es posible. Gecko no tiene API de embebido en escritorio desde que murió XULRunner; GeckoView
existe y es de Android. No hay forma soportada de meter el motor en una ventana de mirada.
⇒ un envoltorio de Gecko es, necesariamente, chrome-level: JS/CSS/XHTML dentro del propio
Firefox, más extensiones, más procesos externos que hablen con él.
Y eso es exactamente lo que Zen es. Zen no toca el motor: su diferencia entera —workspaces, split
view, glance, compact mode— vive en el chrome, más una herramienta (surfer) que reaplica sus
parches sobre el árbol de Firefox y recompila. Su costo no es el motor: es la cinta de correr del
rebase cada cuatro semanas. Copiar su método sería heredar su costo sin heredar su equipo.
2. La decisión de arquitectura: artefacto DERIVADO, no fork de fuente
Si atuq parchea el árbol, cada iteración de UI cuesta un build de Gecko de cuatro horas. Eso
mata el proyecto en la primera semana de diseño, porque el diseño de chrome es iterativo por
naturaleza.
Pero nosotros no distribuimos un binario: sellamos artefactos. Entonces atuq es una receta cuyo
[deps] incluye firefox, y cuyas fases copian el árbol instalado e inyectan encima:
| Qué se inyecta | Mecanismo de Firefox | Sin recompilar |
| Marca, iconos, nombre | distribution/, application.ini | ✅ |
| Prefs de fábrica | defaults/pref/*.js + autoconfig (.cfg) | ✅ |
| Política de empresa | distribution/policies.json | ✅ |
| Chrome propio (dientes, layout) | userChrome.css + re-empaque de omni.ja | ✅ |
| Extensiones nuestras | distribution/extensions/ | ✅ |
Ventajas que esto tiene sobre el método de Zen, y que son estructurales:
- Iteración en segundos, no en horas. El chrome se toca sin volver a compilar C++.
- No mueve el
ArtifactHash de firefox. El corpus no se invalida; waterfox y cualquier otro
fork siguen compartiendo el mismo artefacto base.
- Las CVE se heredan gratis. Un fork de fuente te vuelve dueño del reloj de seguridad del
binario más atacado de la máquina — es lo que hundió a casi todos los forks de Firefox, y el
reproche histórico a Waterfox. Un derivado se reconstruye solo cuando sube
firefox.
Los dos gotchas reales del camino, que son trabajo pero chico:
omni.ja es un zip ⇒ hay que re-empacarlo determinista (mtimes fijos, orden estable) o el
artefacto deja de reproducir bit a bit. Es la misma disciplina que ya exige la imagen del lab.
- El
jarlog del PGO ordena el omni.ja para el arranque (§3.bis). Re-empacarlo a lo bruto
tira esa optimización a la basura sin que nadie lo note. El re-empaque preserva el orden.
- Hay que invalidar el startup cache tras tocarlo (fichero
.purgecaches junto al binario, o
bump del BuildID). Si no, los cambios no se ven y parece que el overlay no agarró: un falso
negativo de los caros, de la misma familia que el cache-hit que congela regresiones.
2.bis Cuándo SÍ hay que parchear el árbol
Cuando algo necesite tocar C++ del motor. Hoy conocemos un caso: sct en su forma fuerte (§6.1).
La regla es: nada baja al árbol antes de que exista la toolchain del §3, porque hasta entonces
cada intento cuesta cuatro horas. Y cuando baje, el vigía de parches (scripts/vigia-parches.py,
commit aa200a4) es lo que convierte esas cuatro horas en un minuto para saber si el parche agarra.
3. La puerta de las optimizaciones: no es un flag, es una toolchain
El usuario decidió habilitar las optimizaciones. Lo que hay que saber antes de intentarlo:
recipes/firefox.toml va con compiler = "gcc" por el muro 3 (zig enlaza libc++ estática para
musl y el configure de Mozilla exige encontrar un NEEDED …libc++; es negativa de upstream, no una
perilla). El problema es que PGO, LTO y BOLT son cadena de clang en Gecko:
--enable-lto=cross quiere clang + lld. El LTO de GCC sobre Gecko no está soportado upstream.
--enable-profile-use espera -fprofile-instr-use (clang), no -fprofile-use (gcc).
- BOLT necesita
llvm-bolt.
Y el corpus no tiene clang usable como compilador. Verificado en las recetas, no supuesto:
recipes/clang18.toml compila ninja -C build libclang.so clang-resource-headers y su fase
install copia sólo build/lib/libclang.so* y las cabeceras clang-c. Está ahí porque
bindgen carga libclang.so en runtime. No hay driver clang, ni lld, ni libc++.
llvm18 se selló con -DLLVM_ENABLE_PROJECTS="" — LLVM a secas.
⇒ la primera versión de este documento concluyó que hacía falta una receta llvm-toolchain
(clang+lld+libc++ desde fuente). Era caro de más, y se corrigió el mismo día mirando el lab en
vez de suponerlo:
.dev-fs/alpine ya trae clang22 + llvm22 22.1.8 — la misma major que usa el APKBUILD de
Alpine para este mismo Firefox (_llvmver=22).
Compiler::Clang ya existe en hammer, cableado de punta a punta (parse_compiler lo acepta;
hammer-build pone CC=clang, CXX=clang++, AR=llvm-ar). Ninguna receta lo usaba.
- Faltaba sólo
ld.lld: apk add lld ⇒ dos paquetes, cero upgrades.
Y los tres muros caen sin perder lo que gcc daba: el sondeo de linker se satisface con
--enable-linker=lld, el ar lo pone hammer solo, y el NEEDED de la stdlib de C++ existe porque
clang++ de Alpine usa la libstdc++ compartida — la prueba no es teórica: Alpine construye este
Firefox con clang22 y sin libcxx en sus makedepends.
La huella del lab no se movió, y se midió antes de tocar nada. lld no casa ningún prefijo de
TOOLCHAIN_PREFIXES (hammer-core/src/lab.rs), así que los 43 paquetes que entran en hash_inputs
salieron idénticos ⇒ los 837 artefactos sellados quedan intactos. Eso abarata el cambio hoy y a
la vez deja un agujero escrito: la versión de lld no es parte de la identidad del artefacto, y sólo
expone a las recetas compiler="clang" — hoy, una. Cerrarlo cuesta re-hashear el corpus entero.
Hecho el 2026-09-05 (commit cb3ecd5): firefox va con compiler = "clang",
--enable-linker=lld, --enable-lto=cross, --enable-packed-relative-relocs y
--with-unsigned-addon-scopes=app,system. Falta construirlo.
Riesgos de PGO, escritos antes de empezar:
- El PGO de Mozilla corre el navegador para juntar el perfil (
profileserver.py, headless +
marionette). Dentro de un sandbox hermético, sin red y sin X11, eso hay que hacerlo andar.
Headless no necesita display, pero sí necesita que el árbol de perfil se genere adentro.
- El perfil resultante es un input del artefacto. O entra en
hash_inputs, o atuq deja de ser
determinista y no lo vamos a notar: sería otra forma del lab que no está en hash_inputs.
- Ganancia esperable, para calibrar expectativas: PGO+LTO en Gecko son del orden de 10-25% en carga
de página y JS. Hoy estamos ATRÁS de Zen en este eje, no adelante — Zen hereda la
configuración de Mozilla tal cual. Esto no es una ventaja nuestra: es una deuda que se salda.
3.bis El diff contra Alpine — y no es sólo PGO
Medido el 2026-09-05 trayendo el APKBUILD y el mozconfig de community/firefox de aports y el
PKGBUILD de Arch, y comparándolos línea a línea con el mozconfig de recipes/firefox.toml.
Alpine es nuestro propio upstream —de ahí salen los once parches de musl— así que la comparación
no es contra una distro lejana: es contra la que ya construye este mismo código.
| recipes/firefox.toml | Alpine community/firefox |
| LTO | no | --enable-lto=cross |
| PGO | no | --enable-profile-use=cross + merged.profdata + jarlog |
| Linker | GNU ld (via gcc) | --enable-linker=lld (lld 22) |
| RELR | no | --enable-packed-relative-relocs |
| Sandbox RLBox | APAGADO (--without-wasm-sandboxed-libraries) | --with-wasi-sysroot |
| Extensiones sin firmar | no declarado | --with-unsigned-addon-scopes=app,system |
| Allocator | --disable-jemalloc | --disable-jemalloc — igual, no es desviación nuestra |
| Librerías | todo bundleado | --with-system-{icu,nss,nspr,av1,vpx,webp,png,jpeg,zlib,ffi,libevent,pixman,pipewire} |
Arch hace lo mismo en el eje de velocidad («Do 3-tier PGO», --enable-lto=cross,
--enable-profile-use=cross). Ni Alpine ni Arch usan BOLT (cero menciones en los dos ficheros)
⇒ BOLT no es la desventaja, es el extra. Lo que nos separa del resto del mundo es PGO + LTO, del
orden de 10-20% en carga de página y JS.
Tres lecturas que hay que sacar de esa tabla:
- RLBox apagado es una desventaja de SEGURIDAD, no de velocidad, y probablemente pesa más que el
PGO. Es la jaula wasm alrededor de los parsers de fuentes y medios —graphite, ogg, expat,
woff2—, o sea exactamente el código que come entrada no confiable. Encenderlo pide
wasi-sdk +
wasi-compiler-rt en el corpus: receta propia, no un flag.
--with-unsigned-addon-scopes=app,system nos falta y atuq lo NECESITA para shipear sus
propias extensiones desde distribution/extensions/ (§6.1, §7). Es un flag de configure ⇒
una capacidad de atuq que no se resuelve en el overlay: exige tocar la base.
- Las librerías del sistema son diferencia a propósito, no deuda. Alpine usa las suyas; nuestro
corpus es la fuente y bundlear es lo que mantiene la clausura cerrada.
Corolario que ordena el plan: todo esto entra en UN SOLO rebuild de firefox. Cada rebuild son
cuatro horas y re-sella la cola Gecko entera (waterfox incluido), así que LTO, PGO, RELR, lld, RLBox
y el scope de addons se juntan en una sola pasada, no en seis.
3.ter Los dos muros del PGO que las distros no tienen
El perfil se junta corriendo el navegador, y las dos distros lo hacen bajo xvfb-run (Alpine:
xvfb-run -a -s "-screen 0 1920x1080x24" ./mach python build/pgo/profileserver.py; Arch, idéntico).
Nosotros no tenemos X11 en el corpus —es la decisión Wayland-only de toda la distro— así que ese
camino no existe acá. La salida hammer-nativa es correrlo bajo un sway headless
(WLR_BACKENDS=headless), que ya tenemos del frente wlr/sway y arranca en segundos.
Y el perfil no es determinista. Los contadores dependen del timing de la corrida, así que dos
generaciones del profdata no dan los mismos bytes — a las distros no les importa porque no persiguen
bit-repro; a nosotros nos rompe el invariante. La salida: generar el perfil UNA vez y sellarlo como
artefacto propio, consumido por hash desde firefox. El build optimizado vuelve a ser determinista
aunque su insumo no lo sea, y el perfil se regenera a propósito, no por accidente.
4. Lo que atuq NO promete
Esta sección existe antes que la lista de features a propósito, con el mismo criterio que el modelo
de adversario de qullqa: lo que no se promete se escribe primero, porque una promesa falsa de
privacidad es peor que no ofrecer nada.
atuq no es Tor Browser y no va a tener «modo Tor». Tor Browser no es «Firefox + SOCKS»: su
valor son las contramedidas de huella (RFP, letterboxing, normalización de fuentes/canvas/timers) y
sobre todo un conjunto de anonimato donde todos se ven idénticos. atuq es, por construcción, un
binario único en el mundo: musl, Wayland-only, branding propio, extensiones propias. Alguien saliendo
por Tor desde atuq es más identificable, no menos, y su conjunto de anonimato son diez
personas. Para anonimato: Tor Browser, y lo decimos nosotros primero.
Lo que sí se ofrece, y es honesto porque es otra cosa: proxy por contenedor (§6.8) — separación
de tráfico, no anonimato.
atuq tampoco promete anti-fingerprinting propio (es un programa de investigación con equipo
full-time) ni bloqueo de anuncios propio (se shipea uBO y listo).
5. Contexto verificado al escribir (2026-09-05)
De docs/state/build-state.json, no de memoria:
| Dato | Valor |
| Recetas / nodos | 838 / 840 |
| Selladas | 837 |
firefox 154.0 | sealed, cola corpus |
waterfox 6.7.1.1 | never — primer build en curso |
| Plataforma compartida | gtk3, atk, nodejs, clang18, cbindgen: todas sealed |
atuq no arranca hasta que waterfox selle. Waterfox es la prueba de la tesis del reúso —«las
diferencias reales con firefox.toml son TRES: la fuente, el branding y la versión»—. Si esa tesis
falla, el precio de todo este documento cambia y hay que releerlo.
6. Los diferenciadores, priorizados
La columna «quién más lo tiene» es lo que evita que nos contemos un cuento.
| # | Qué | Quién más lo tiene | Pieza que ya existe | Costo |
| 6.1 | sct — transparencia de scripts | nadie | puriy-sct, puriy-sct-testigo | medio |
| 6.2 | Descargas direccionadas por contenido | nadie | store CAS de hammer, tejido | bajo |
| 6.3 | Archivo personal + RAG local | Rewind/Recall (nube, Windows); SingleFile (guarda, no busca) | khipu, rag-motor, willay-rag | medio |
| 6.4 | Historial y perfil sobre qullqa | nadie | qullqa-core, qullqa-pozo | medio |
| 6.5 | Foco por cortafuegos, no por extensión | nadie (nadie es dueño del navegador y del sistema) | cortafuegos, pacha | bajo |
| 6.6 | Medios por fuera del navegador | extensiones sueltas; de fábrica no | foreign-ytdlp, -platform, -dlna | bajo |
| 6.7 | IA local en la barra lateral | Chrome/Edge son nube; Zen no tiene | rimay, iniy | bajo |
| 6.8 | Proxy por contenedor | nadie de fábrica | — (API de Firefox) | bajo |
| 6.9 | Torrent adentro | Vivaldi lo trae; Brave tuvo WebTorrent | shared/foreign-torrent (SDD propio, sobre librqbit) | bajo |
6.1 sct es la joya
BLAKE3 a todo lo que se ejecuta, y negarse a correr código que ningún testigo vio nunca. Es lo
más cerca que hay de «Certificate Transparency, pero para el JavaScript que te corre en la cara», y
no lo tiene nadie: ni Zen, ni Brave, ni Tor Browser.
En tawasuyu ya está escrito y —dice su README— la lógica es agnóstica del transporte y está
certificada sin red; puriy-sct-testigo es sólo el cable HTTP.
- v1, sin tocar C++: extensión con
webRequest bloqueante que hashea cada respuesta de script y
consulta al testigo antes de dejarla pasar.
- v2, forma fuerte: gancho en el script loader de Gecko. Eso sí es parche de árbol ⇒ §2.bis ⇒
después de la toolchain del §3.
6.2 Una descarga con identidad
Lo bajado no es un archivo en ~/Downloads: es un objeto BLAKE3 en el store — deduplicado,
verificable, y re-compartible por tejido. El torrent (6.9) alimenta lo mismo. Ningún navegador
trata una descarga como algo con identidad; para todos es un blob con nombre, y por eso lo bajás
dos veces y no lo sabés.
Ésa, y no «tener torrent», es la razón por la que 6.9 vale: Vivaldi ya tiene torrent, pero termina en
una carpeta.
6.3 El archivo personal es la razón por la que alguien usaría atuq
Cada página visitada se congela en el CAS y entra al DAG de khipu; después le preguntás a tu
propio historial en lenguaje natural, todo local. Es la feature que más justifica el proyecto
entero, y sale de piezas que ya están escritas.
6.4 qullqa con su advertencia puesta
Almacenamiento con negación plausible y modelo de adversario escrito — ningún navegador tiene
eso; el modo incógnito es teatro. ⚠ Con la advertencia que el propio SDD de qullqa ya anotó y que
hay que repetir acá, no esconder: en una máquina con swap sin cifrar, el documento no aplica.
Se promete lo que se puede probar.
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»:
- 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.
- 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.
- 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:
- 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.
- 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.
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
messaging con la extensión de atuq y, del otro lado, con la suite (agora para identidad Ed25519,
khipu, el store, foreign-*, cortafuegos).
Que sea uno y no siete es la decisión: cada feature del §6 pasa a ser un verbo de ese host, no un
proyecto nuevo. El binario vive en tawasuyu (es suite, no build system); atuq sólo lo declara como
dep y le deja el manifiesto de native messaging en su sitio. El nombre del crate se decide en
tawasuyu y con su regla 10 puesta — acá no se bautiza nada de allá.
8. Plan, por unidades de trabajo
Cada una cierra sola, se commitea y se pushea. El orden no es preferencia: cada una destraba a la
siguiente.
| # | Unidad | Puerta que abre | Bloqueada por |
| 1 | waterfox sella | valida la tesis del reúso | build en curso |
| 2 | llvm-toolchain → apk add lld + compiler="clang" ✅ 2026-09-05 | LTO y el muro 3 | — |
| 3 | Construir el firefox con LTO (hash b3:6f2a3b2f) | el eje de velocidad y las extensiones de atuq | 2 ✅ |
| 3.a | PGO: perfil bajo sway headless, sellado como artefacto propio y consumido por hash | la otra mitad de la ganancia | 3 |
| 3.b | wasi-sdk + wasi-compiler-rt ⇒ RLBox encendido | cierra el hueco de seguridad del §3.bis | 2 |
| 4 | atuq: receta derivada + overlay de chrome ✅ v0.1 escrita | el andamio de todo lo demás | 3 |
| 4.b | Branding + re-empaque de omni.ja ✅ v0.2 | el chrome de verdad | — |
| 4.d | Abrirlo en pantalla — destapó EXDEV, el lanzador, el atk envenenado y la cadena GTK3 estática | «usable», no sólo «sellado» | — |
| 4.e | v0.3: página de inicio y pestaña nueva por extensión de sistema; exigió MOZ_REQUIRE_SIGNING vacío | y con eso queda destrabada la unidad 6 (sct) | rebuild de firefox |
| 4.c | MOZ_BUILD_DATE determinista en firefox — hoy el BuildID es la fecha del build ⇒ el artefacto no reproduce bit a bit | el invariante de reproducibilidad | otro build de 4 h + re-sellar atuq |
| 5 | Host de native messaging (tawasuyu) | los verbos del §6 | 4 |
| 6 | sct v1 (extensión + testigo) | el diferenciador que nadie tiene | 5 |
| 7 | Descargas al CAS | 6.2, y alimenta 6.9 | 5 |
| 8 | Archivo + RAG | 6.3 | 5, 7 |
| 9 | Proxy por contenedor, torrent, medios, foco | 6.5–6.9 | 5 |
Las unidades 2 y 4 son paralelizables: la toolchain no toca el chrome y el chrome no toca la
toolchain. Si hay dos frentes, van juntas.
9. Por qué esto no abandona a puriy
puriy es el motor DOM/CSS propio con JS real (QuickJS-NG sobre wasmi) cuyo límite declarado no es
el lenguaje sino el wiring nativo: DOM bindings completos, red, render. Eso no se acelera
escribiendo otro navegador; se acelera con tiempo.
atuq no le compite porque no comparte una sola línea con él, y le sirve porque todo lo del §6
vive del lado Rust: el host del §7, el testigo de sct, las descargas al CAS, el archivo con RAG.
Son agnósticos del motor. Cuando puriy madure, se enchufan del mismo lado sin reescribirse.
Dicho al revés, que es como conviene recordarlo: atuq es el navegador que se puede usar mientras
puriy crece, y el andamio de las features que puriy va a heredar. Si dentro de dos años atuq
se apaga, lo que se tira es una receta derivada y una hoja de CSS; todo lo demás sobrevive.