Los dos sitios que quedaban con trafico real sirven desde la caja `takana`, con TLS publico y
verificados desde fuera. Los servicios viejos SIGUEN corriendo en gioser a proposito (decision del
usuario: mover el DNS y dejar el origen vivo, para poder volver en un minuto).
· `tawasuyu.net` + `www`: la raiz en gioser era EL MONOREPO ENTERO (145 G) servido por HTTP. El log
de accesos dice que las unicas rutas con trafico son `/`, `/descargas` y `/web`, asi que se
replicaron solo los dos subarboles que el sitio usa (1,2 M + 3,8 G) con la MISMA estructura, para
que cada `rewrite` siga siendo el mismo.
· `gioser.net` + `www`: 428 M de estaticos + `/reencuentro`, que necesita PHP. `/hooks/*` NO se
muda: lo atiende `webhook-deploy.py`, que redespliega aura/sigma/summa/brahman — los cuatro
FOSILES del §6.19 — y tiene cero peticiones. Muere con la caja vieja.
· `sergio` dejo de ser `CNAME -> www` y tiene su A propio (apuntando a gioser, sin cambio visible).
Sin eso, mover `www` se lo llevaba puesto: en esa zona casi todo cuelga de `www`.
PHP no entra al corpus y no hace falta: `php-fpm 8.5.10` corre en la instancia `gioser-php` (ADR
0015), supervisado por arje. Control contra el original en los dos sentidos: POST con la trampa
anti-robots da `{"ok":true}` 200 y GET da 405, igual que en gioser.
EL MURO, que es general y no de PHP: para montar en `/work/www/gioser-web`, bwrap crea `/work` y
`/work/www` —que la imagen no trae— y los crea **0700 root**. Adentro somos root y a mano todo
funciona; pero un servicio que BAJA de privilegio (php-fpm a `http`, o cualquier instancia con
`run_as`) no puede ni atravesarlos, y el sintoma es un «File not found» sobre un fichero que ESTA.
Medido con el control que lo separa de un problema de permisos del anfitrion: como `http`, leer un
fichero de la IMAGEN funciona y leer el directorio CONCEDIDO da Permission denied.
`grants_to_args` crea ahora los ancestros que faltan con `--perms 0755 --dir`, y SOLO los que la
imagen no trae: hacerlo sobre `/etc` o `/home` le cambiaria los modos a la imagen. Con test, y
probado rompiendolo a proposito (sale `["/etc","/etc/php"]` en vez de `["/etc/php"]`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cuatro secciones discutiendo por qué la ventana de atuq salía de 117x70, y no era la ventana de atuq.
Lo dice el mismo volcado de protocolo que ya se había leído, dos líneas más abajo de donde paró la
lectura:
558 -> xdg_toplevel#58.set_title("Open atuq in Troubleshoot Mode?")
Es safeMode.xhtml, el diálogo de Modo de resolución de problemas. La cadena, cada eslabón con su
fuente y ninguno deducido:
1. el perfil del usuario traía `toolkit.startup.recent_crashes = 16` (prefs.js del 15-Sep 00:36,
ANTES de las corridas del día; leído con debugfs sobre la partición, sin montar y sin root);
2. el umbral de NUESTRO build es 3 (browser/omni.ja -> defaults/preferences/firefox.js:831);
3. BrowserGlue.sys.mjs:389 abre el diálogo MODAL en `_beforeUIStartup()`, que por su propio
comentario corre «before the first window is opened»: no hay navegador detrás esperando;
4. el `set_max_size(348, 16332)` sale del Fluent del diálogo (`max-width: 400px`);
5. y el proceso se va solo con `ATUQ-EXIT=0` porque el único camino de ese diálogo que termina el
programa es `onCancel()` -> `quit(eForceQuit)`. Quién lo cancela no está medido.
El control, en los dos sentidos, mismo artefacto y mismo perfil: con el contador limpio pinta a
+123 s (704.458 px); fijado en 16, nada en 300 s y vuelve el diálogo. Y una honestidad que cambia la
fuerza del argumento: en la corrida limpia el `--crashes 0` fue un NO-OP —Gecko ya lo había
borrado—, así que la que prueba la causa es la que lo pone de vuelta y rompe de vuelta.
Cae también la sospecha del §6.10.duodecies: con `WidgetScreen:5`, Gecko ve la pantalla completa
(1280x800 de workarea) CINCO SEGUNDOS antes de crear la ventana. No había carrera con wl_output.
Y lo más caro: la serie de primera-pintura.json está contaminada por el propio andamiaje. El arnés
mata la VM con el navegador vivo, cada arranque sin terminar es una caída para Gecko y pasados 3 el
sujeto medido deja de ser el navegador. «3 de 13» y «2 de 10» midieron un perfil que se degradaba
corrida a corrida. Lo que cada corrida sume exactamente NO está medido y así queda escrito.
Queda una decisión de producto que no se mete de paso porque re-sella el artefacto: si atuq debe
traer `toolkit.startup.max_resumed_crashes = -1`.
`atuq-en-imagen.py` ahora lee `toolkit.startup.recent_crashes` del perfil ANTES y DESPUÉS de cada
corrida y lo anota junto al tiempo de primera pintura (`recent_crashes_antes` / `_despues` /
`_fijado`). Los dos volcados, no uno: Gecko BORRA el contador al mostrar el diálogo de Modo de
resolución de problemas, así que un perfil envenenado se ve limpio si se lo mira después — y la
corrida siguiente parece arreglarse sola mientras la anterior parece intermitente.
Con eso, `vigia-imagen.py` saca de la tasa las corridas con el contador > 3 (ahí el sujeto no es el
navegador sino un diálogo modal) y para las 21 anteriores al campo dice que NO SE PUEDEN CLASIFICAR
en vez de contarlas como buenas. Un denominador se marca, no se borra.
Mandos y guardas nuevas:
· `--crashes N` fija el contador en el `user.js` del perfil: 0 limpia, >3 reproduce. Es el control en
los dos sentidos, que es lo único que distingue causa de correlación con suerte;
· `WidgetScreen:5` en el MOZ_LOG y una sonda `ATUQ-DIAG` en la propia página (screen/outer/inner por
`dump()`), que es lo que refutó la carrera con `wl_output`;
· la lista ordenada del protocolo ya no se ahoga en los ~60 modos que anuncia QEMU —el `head`
cortaba antes del `set_window_geometry`— y trae `set_title`/`set_app_id`, que es lo que identificó
la ventana;
· si QEMU no arranca, se dice en el primer segundo leyendo `qemu.log`. El fallo real es «Failed to
get write lock» con otra VM sobre la misma imagen, y sin esta guarda se veía como diez minutos
esperando una marca del serial: el socket queda creado y el `connect()` funciona.
Las fases (`--xulstore`), el `ATUQ-EXIT=$?`, la copia del log del arranque anterior y el `find` del
perfil sobre la raíz entera los escribió la otra sesión que trabaja este frente; conviven acá porque
el fichero es compartido.
El primer servicio AJENO que corre sobre takana. 200 con TLS publico desde 2.29.29.217 y
`/api/chat/` contestando con Gemini de verdad, con el backend en una instancia `qorpa` sobre el
rootfs de Arch pineado por sha256, supervisado por arje-zero como el ente `sergioh-api`.
Es el que §6.21 llamo «lo que decide la fecha de borrado de gioser»: su venv trae extensiones
`cpython-314-...-linux-gnu.so`, o sea glibc, y no tiene camino a musl. La coincidencia que lo hizo
barato: el Arch pineado trae python 3.14.7 y el venv de gioser es 3.14.6 — misma serie, asi que el
venv se REHACE adentro con pip en vez de copiarse.
La seccion trae ademas el muro (la jaula no viajaba en ninguna imagen — commit anterior), los tres
tropiezos del camino (el uid 1001 del rsync que cae fuera del userns; una instancia admite UN solo
`run`; `ps` que no ve procesos y `dig` que no existe, dos ausencias que se leen como diagnostico) y
lo que quedo declarado: `instance.toml` con un unico dir concedido, las 84 deps pineadas en
`requirements.lock`, y la tarjeta en cards.d Y en el genesis.
Y una correccion de rumbo en §9: la lista «lo que falta para el cutover» era del 11-09 y sus pasos
1-5 ya estan hechos. Lo que sigue vivo en gioser son TRES vhosts, no quince.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mudando `api.sergio.gioser.net` a la caja de produccion, `takana qorpa provision` aborto:
Error: no encuentro harkaq-exec en /root/.cache/harkaq/harkaq-exec
construilo: gcc -O1 -Wall -static -o ... scripts/harkaq/harkaq-exec.c
El mensaje es bueno y la receta que propone es IMPOSIBLE de seguir: en una caja instalada no hay
gcc, ni `scripts/`, ni arbol de desarrollo. El binario solo existia como un `gcc` a mano en la
cache de `$HOME` del hub, o sea que la jaula del ADR 0015 funcionaba unicamente en la maquina
donde alguien la habia compilado.
Y su companero estaba igual, medido en `build-state.json`: `bwrap` sellado desde hace meses con
`"perfiles": []` — CERO perfiles. Lo invocan por PATH tanto `qorpa` como el sandbox de
`takana build` (`takana-build/src/sandbox.rs`), asi que **ninguna imagen de takana podia enjaular
nada, ni construir**. Es [[subcomando-sin-driver]] un piso mas abajo: el CLI que los llama viaja
en todas las imagenes y sus herramientas en ninguna.
Tres piezas:
· `recipes/harkaq-exec.toml` — nueva. Estatico musl, `b3:cd34954f...`, 269 K. El pin va al commit
que toco la FUENTE (`1d9ddcee`, 2026-09-03) y no a HEAD: el `.c` no se mueve desde entonces y el
repo commitea cada media hora por el cron de la cosecha — pinear HEAD re-hashearia la receta cada
media hora sin que su fuente cambiara. La fuente sigue en `scripts/harkaq/` y no en un arbol
propio porque tres scripts la compilan desde ahi y dos copias divergen en silencio.
· `qorpa.rs` — busca el binario tambien en `/usr/bin`, que es de donde sale en cualquier maquina
que no sea el hub. El orden es HARKAQ_BIN (lo que el operador declara) → arbol de desarrollo →
paquete, para que un cambio en la jaula se pruebe sin instalar nada. Y el error ya nombra las
dos salidas, no solo la del hub.
· `targets.toml` — los dos en `perfil.base`.
Medido en la caja de produccion tras aplicarlos: `bwrap 0.11.0` + harkaq-exec responden, y
`takana qorpa provision sergioh-api` instala python 3.14.7 con pacman DENTRO de la jaula
(`[harkaq] jaula puesta: ABI 9`). El 3.14 no es casualidad: el venv de gioser trae extensiones
`cpython-314-...-gnu.so`, asi que la imagen de Arch pineada da la misma serie.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La decima extension de atuq. Ofrece la credencial que corresponde al sitio
abierto y la pone en el formulario, pero NO puede sacar una contrasena de la
boveda por su cuenta: pregunta cuales corresponden —y eso contesta titulo y
usuario, nada mas— y la contrasena sale por otro verbo que pregunta en el
escritorio antes de contestar. Si la extension queda comprometida, o si una
pagina consigue hablarle, lo que obtiene es la lista de titulos de los sitios
que coinciden con su propia direccion.
La direccion siempre la pone el chrome y nunca la pagina. Lo unico que el
trozo que corre dentro del sitio aporta es que HAY un formulario y lo que el
usuario tipeo; si pudiera decir de quien es la pagina, podria decir que es el
banco.
Y la extension no decide nada: quien sabe que credencial va en que sitio es
la funcion del otro lado del cable. Ponerlo en JS seria reimplementar el
original y, peor, poner en la pagina la decision de a quien se le entrega una
contrasena.
EL GESTOR DE GECKO SE APAGA, pero como valor de arranque y no como politica.
Dos gestores peleando por el mismo campo es la peor experiencia que puede
tener alguien que solo quiere entrar a su correo: el sitio se rellena dos
veces, o ninguna, y no hay forma de saber cual tiene la buena. De fabrica
manda la boveda; el que prefiera el de Gecko lo prende y listo. Prohibirselo
seria decidir por el, que es lo que este fichero dice en su cabecera que atuq
no quiere ser.
Y un guardian nuevo, que corre en un segundo y sin construir nada. Una
extension esta enchufada en CUATRO lugares distintos y ninguno da error si
falta: el identificador que ella declara, la politica que la instala, el
permiso para hablarle al host, y las preferencias que la acompanan. Si falta
la politica no se instala. Si falta el permiso se instala y se conecta a
nada, en silencio, y la insignia no aparece nunca. Si faltan las preferencias
los dos gestores se pelean. Ninguno de los tres se ve como un error: se ven
como "no anda". El guardian los mira todos y trae su control negativo, que le
saca el permiso del host y exige que lo detecte.
No reemplaza al guardian de metal —servidor, navegador real, login real, el
dialogo a la vista—: lo precede, y ahorra descubrir construyendo que faltaba
un renglon en un JSON.
Los cinco verbos del otro lado ya estan en tawasuyu (bc6903f9e), con trece
tests que leen el cable.
rustc desde fuente está compilando el stage1 en el worker. El documento recoge el frente entero: el
hueco medido (12 crates y 42 555 líneas de Rust, 44 recetas `cargo-*`, CERO de rustc, y el `rustc`
que construye todo eso saliendo del LAB — que ENTRA en el ArtifactHash), los tres escalones, y lo
que costó cada uno.
Los cinco muros del escalón 2, todos con un mensaje que no nombra la causa:
1. `llvm18` no sirve: `bad LLVM version, need >=21`. Mínimos medidos en el propio bootstrap
(1.87→18 · 1.90→19 · 1.93/1.95→20 · 1.97→21). Bajar de rustc no es salida porque el stage0 de N
es N-1 o N. De ahí `llvm21` — y `llvm18` se queda, que lo usa mesa.
2. **El toolchain musl oficial no puede producir proc-macros** (crt-static=true ⇒ sin dylibs):
muere compilando el propio bootstrap de rustc, que usa `clap` con derive. Sirve para compilar
programas, no para arrancar el build de rustc. El stage0 es el del lab.
3. x.py DESCARGA el stage0 si no le nombras el local: en un sandbox sin red, `RuntimeError: failed
verification` en `download_toolchain()`, que no menciona ni la red ni el stage0.
4. El triple de Alpine no es el canónico: sin fijarlo, la sección `[target.…-unknown-…]` no aplica a
nada y x.py se pone a construir LLVM solo; forzando el canónico, el stage0 no tiene su std.
5. cmake + zig = «compiler broken» en la prueba de ABI. CC=gcc, igual que llvm21 y cmake.
Y del escalón 1, lo que no estaba en el grafo: el prebuilt no ejecuta sin `musl-shared` (cargador) y
sin `gcc-libs` (`libgcc_s.so.1`) — dos paquetes sellados y en el perfil equivocado. Más una
propiedad que vale: no hace falta un `cc`, el toolchain trae su propio lld.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
Ordenando el protocolo numerado en los dos sentidos: cosmic-comp manda
configure_bounds(0,0)+configure(0,0) («elegi vos»), el cliente contesta
set_min_size(117,37), set_max_size(348,16332) y set_window_geometry(117,70),
y RECIEN ahi el compositor devuelve configure(117,70) teniendo 1280x692
para dar. No hay nada que arreglar en COSMIC.
El MOZ_LOG dice como: «Initial resize to 1 x 1» — Gecko crea la ventana de
1x1 y nunca la agranda; 117x70 es lo que queda cuando el chrome se mide a
si mismo. El frente se mueve a Gecko.
El configure abortó con «stage0 NO es 1.97.0: rustc 1.97.0» — o sea, la guarda rechazando
exactamente la versión que exige. La causa: en un string multilínea con comillas SIMPLES, TOML no
procesa escapes, así que `grep -q '1\\.97\\.0'` busca una barra invertida literal y nunca casa.
Va `'1[.]97[.]0'`, que no depende de cómo el formato trate las barras.
Un guardián que falla sobre lo que debería aceptar es tan inútil como uno que nunca falla — pero al
menos éste falló RUIDOSAMENTE y en el primer minuto, no tres horas después.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
El segundo escalón de los tres: rustc construido acá, con el `llvm21` del corpus y con
`rust-toolchain-bin` (el tarball oficial sellado ayer) como **stage0**. Un prebuilt ajeno sirve
exactamente para esto: ser el escalón que permite dejar de necesitarlo.
Tres cosas que no son detalles y que van escritas en la receta:
· **`local-rebuild = true`**: el bootstrap espera que el stage0 sea el compilador ANTERIOR (o el
beta de esta versión), y acá es 1.97.0 construyendo 1.97.0. Sin esa opción x.py aborta por
«stage0 compiler version mismatch». Es el modo con el que las distros reconstruyen la misma
versión con la misma versión.
· **LLVM externo ≥21**, medido en el propio bootstrap (`bad LLVM version … need >=21`). Por eso
existe `llvm21`: `llvm18` está sellado pero lo usa mesa, que no soporta 21.
· **Sin red**: el tarball `-src` trae `vendor/` con todas las deps de cargo. Es la única forma de
construir dentro del sandbox, que no tiene red después del fetch.
Y una guarda en el `configure`: la versión del stage0 se COMPRUEBA (`rustc --version | grep 1.97.0`)
en vez de asumirse. `local-rebuild` sólo es correcto si el compilador de arranque es exactamente
esta versión; si alguien mueve el pin de `rust-toolchain-bin`, esto tiene que fallar ahí y no tres
horas después.
`llvm21` ya selló: 1,6 G, `llvm-config --version` → 21.1.2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
Las otras tres recetas cgo (sq, usql, gitea) dan DERIVA, no no-determinismo: sus dos
reconstrucciones coinciden entre sí y el artefacto guardado era el de antes del arreglo.
Ya están al día.
Y de paso se les fue una deuda que NO era de reproducibilidad sino de PORTABILIDAD: el
`x_cgo_sigaction` de sq y usql —código C de cgo, sin guarda de CPU en runtime— venía con
AVX2 horneado, de cuando el driver pisaba el `-mcpu=baseline`. Un binario así no falla al
construir ni al sellar: falla al EJECUTAR en una CPU sin AVX2, con SIGILL. Ahora los tres
salen baseline (cero ymm, cero zmm en esa función).
── El límite del libro, anotado en su encabezado ──────────────────────────────────────
`gocryptfs` acabó con CUATRO filas al MISMO hash: dos NO-DETERMINISMO y dos reproduce, y
las cuatro son ciertas. La clave (receta, hash) no puede distinguir antes y después de un
arreglo del FRAMEWORK, porque la fase generada no entra en `hash_inputs` — a propósito,
para no re-sellar 362 recetas cada vez que se toca un driver.
Corolario, que es lo caro: tras tocar un driver, lo sellado NO se rehace solo. Hay que
forzarlo receta por receta, y si nadie lo hace el store se queda con artefactos que ya
nadie construiría así.