2070916d2f4cba3e9a745c7cfdbe41c09b56bafa
595
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1c237f9da2 |
mudanza: decidir SIN teclas unitarias, /mnt/vvv entra al censo, y 25 rutas de secretos más
Tres cosas que salieron de usar la herramienta de verdad.
1. `--decidir <fichero>`: decidir EN LOTE desde `<ruta-o-nombre> <decision>` por línea.
El usuario: «no sé cómo activar o desactivar cosas aquí desde shuma llimphi remoto, que las
opciones son con teclas unitarias». El modo interactivo lee una tecla por entrada, y hay
terminales donde eso no se puede usar — una herramienta cuya única forma de decidir exige un
tipo de terminal no es una herramienta, es una herramienta PARA ESA TERMINAL. El fichero anda en
cualquiera, se revisa antes de aplicarlo, se versiona y se vuelve a correr.
Una clave que no empareja con nada, o que empareja con varias, es ERROR RUIDOSO y no se escribe
NADA: un lote a medias deja decidido lo que nadie revisó.
2. `/mnt/vvv` entra a las raíces del censo. No estaba, y ahí vive el trabajo: los repos de la
persona, el monorepo, el store, work/sources. El agujero se vio preguntando por `humanoid`: el
censo sólo conocía `/home/sergio/humanoid` —200 M de `build/` de junio— mientras el proyecto de
verdad, con su `.git`, estaba en `/mnt/vvv/humanoid`, fuera de toda raíz censada. `repos_git` SÍ
lo miraba: una parte del censo conocía el árbol y la otra no.
3. 25 rutas más en `rutas-fuera-de-git.txt`, del barrido que hizo el frente tawasuyu sobre su home:
`~/.wawa/seeds` (sin él ninguna app nueva del génesis nace), `~/.tejido` (la identidad de esta
máquina en la flota), `~/.local/share/agora`, `~/.config/{wawa,minga,thasnuna,shuma,mirada,hcloud}`,
`~/keys` y `~/fdroid` (⚠ firma de apps Android), `~/.gnupg`, `~/.pgpass`, `/etc/wireguard`,
`/etc/{shuma,sandokan,tawasuyu/agente,local.d}`. Son identidades y semillas: no se «vuelven a
generar», porque generar otras significa ser OTRA máquina para el resto de la flota.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5ec4e2b213 |
mudanza: el tercer camino — mudar, RESPALDAR o abandonar, cosa por cosa y con su descripción
Pedido del usuario: «una lista de cada cosa con una corta descripción y la opción de yo elegir mudar, mover a un directorio para respaldar ese directorio, o abandonarlo». Eran tres huecos distintos. 1. «CADA COSA». El censo medía RAÍCES: `/home` era UNA entrada de 25 G y UNA decisión, con repos, SDKs, 4,3 G de caché y trabajo irrepetible adentro. Ahora emite un renglón por cosa —169 en gioser contra 5—, un nivel hacia adentro de cada raíz y DOS en `/home`, porque su primer nivel son usuarios y la decisión no es por usuario. `--solo-totales` conserva la vista vieja, que es la que dimensiona la mudanza; las dos viajan en el censo (`datos` y `datos_raiz`). ⚠ El primer intento mandaba las ~110 sondas en UNA llamada, se pasaba del timeout y devolvía lista VACÍA: «no hay datos» en vez de «no pude medirlos». Va en tandas, y una tanda que falla se nombra. 2. «UNA CORTA DESCRIPCIÓN», y son hechos: lo sirve el servidor web · lo usa un proceso vivo · es repo git y a dónde apunta · tiene cambios sin commitear · adentro hay node_modules/target/venv · lo más nuevo que hay dentro. «Sin señales» también es un hecho, y es el que dice dónde mirar. 3. «ELEGIR ENTRE TRES»: DECISIONES pasa de (muda, muere) a (muda, RESPALDA, muere). Faltaba el camino que se usa de verdad —no lo quiero corriendo allá, tampoco lo quiero perder—; con dos opciones, todo lo dudoso se marcaba `muda` y la mudanza engordaba. La regla de fondo no cambió (qué datos VALEN no lo dice la máquina), pero cuatro hechos sí los sabe: servido/usado ⇒ muda · caché ⇒ muere · repo limpio con remoto ⇒ muere · repo sin remoto o sucio ⇒ respalda. Y el cruce que evita el peor error: un remoto que apunta a ESTA MISMA máquina no es un respaldo, es el mismo disco (medido: /mnt/vvv/humanoid → ssh://git@127.0.0.1:2345). 4. LAS DECISIONES SE GUARDAN. `--decide` las usaba sólo para el plan de esa corrida: cerrabas la terminal y se perdían las 169. Ahora reescribe el censo (temporal+fsync+rename, `.bak`, y releído antes de pisar nada; aborta si perdió alguna). Para eso hubo que hacer el emisor IDEMPOTENTE: escribía `decision = ""` fijo, así que re-emitir un censo decidido duplicaba la clave y el TOML dejaba de parsear — la herramienta no podía reescribir su propio fichero. 5. EL PASO `respaldo`: corral por defecto leído de respaldo-storagebox.sh (no una segunda dirección a mano), nombre aplanado, y NO se instala en el destino. Sin corral, aborta: un rsync a ninguna parte devuelve 0 y se lee como un respaldo. Probarlo con rotura a propósito Y con el control que tiene que pasar destapó dos bugs: `--mkpath` es obligatorio (el primer respaldo siempre estrena un nivel) y un FICHERO no lleva barra final — `rsync fichero/ dst/` es un error, y el paso de `datos` tenía el mismo defecto latente. La verificación es rsync en seco contra el destino real, no `du` ni `find`: el Storage Box no tiene find, no acepta tuberías, y su `du` comprime. 6. De paso: `aplicar.py --dry-run` decía «✅ todos los pasos ejecutados y verificados» sin haber ejecutado nada. Ahora dice EN SECO: N mostrados, NINGUNO ejecutado y NINGUNO verificado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2098e86ba4 |
atuq: la boveda ANDA — 19 guardianes en verde, y el bloqueo de tres semanas era un fichero sin empujar
El pin de `puriy-costura` subio a `23a292863` y con eso se apagaron los dos rojos del cable. La suite entera sobre `b3:e556024b`: **19 verdes, 0 rojos, 32,1 min**. No se toco ningun guardian — que la suite existiera ANTES del arreglo es lo que permite afirmarlo: el cuadro de ayer y el de hoy son el mismo instrumento. La medicion que cierra es la que abrio el §7.quinquies, leida al reves: `strings` sobre el binario sellado da `vault` **10** veces donde daba 0, con `cas`/`sct` de control; y `vigia-atuq-verbos` pasa de 3 verbos sin dueno a cero, con su control positivo intacto. Lo destrabo publicar el `Cargo.lock` de tawasuyu (`23a292863` alla), regenerado en un arbol limpio: 215 lineas, todas de contabilidad de deps por ruta, cero `checksum` y cero `source` movidos. Y el remate, que es la leccion: **ese lock era byte a byte el que la otra sesion ya tenia en su arbol sin commitear**. Tres semanas de bloqueo y el fichero correcto estaba escrito, sin empujar, a un directorio de distancia. Antes de decir «bloqueado por otro repo», mirar si lo que falta no esta ya hecho y sin publicar. Commiteado alla con indice temporal (`GIT_INDEX_FILE` + `commit-tree`), que es la unica forma de publicar una ruta en `MM` sin llevarse por delante lo del otro agente: su arbol quedo byte a byte igual, comprobado con `cmp` antes y despues. ⚠ Y la cabecera del runner se desmintio SOLA dos veces: anuncio «un rojo» cuando eran dos, y «dos» cuando ya no quedaba ninguno. Un numero escrito como prediccion envejece callado. Con todo en verde el numero ya no distingue un producto sano de un runner que no corre nada, asi que lo que sostiene la corrida queda escrito: la puerta del sello (verificada contra un store vacio) y el control interno de cada guardian. |
||
|
|
e2bb4f9b89 |
censar: «no existe» y «no puedo mirar» se confundían — 572 M invisibles, y el censo de hoy
El censo ya tenía tres estados a propósito (`ausente` / `sin_permiso` / `ok`), pero el tercero sólo se detectaba sobre la PROPIA ruta: cuando el que no deja pasar es un ANCESTRO, `[ -e "$p" ]` contesta que no existe y la ruta caía en `ausente`, que el censo descarta sin decir nada. Medido en gioser: `/root/.local` es 0700 ⇒ un censo corrido como `sergio` daba `/root/.local/bin` por AUSENTE. Como root son 572 M de binarios puestos a mano que ningún paquete provee — justo la clase que la mudanza no puede reconstruir. Arreglado: si un ancestro existe y no es atravesable, el estado es `sin_permiso`. Probado en los dos sentidos, que es lo que separa un guardián de un adorno: ancestro 0700 de otro dueño → sin_permiso (sale en el ⚠ del resumen) ruta que NO existe de verdad → ausente (no se reporta) ← el control que tiene que pasar Con el arreglo, el censo sin privilegio pasa de 2 rutas ciegas a 13, entre ellas el home entero de `artix`, que antes no figuraba de ninguna manera. Y el §6.34 con el estado MEDIDO de la mudanza hoy: de los 29 dominios queda UNO sirviéndose desde gioser (`sergio.gioser.net`); los otros seis vivos ya contestan 200 contra 2.29.29.217. El rescate del §6.20 sigue coincidiendo bit a bit con cuatro de los seis procesos que corren desde un inodo borrado, y los otros dos tienen en disco una versión más nueva. Lo que queda abierto son los pasos 6 y 7: el montón de daemons propios (shuma, willay, pacha, tejido, tupu, matilda…) y los datos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fb7dba530d |
atuq: el que estaba «nombrado como hueco» entra a la suite — 17 verdes y 2 rojos en 32 min
La primera version dejaba afuera al guardian de notificaciones (`dunst-headless.sh --via-atuq`) con
la excusa de que vive en el arnes de sway, y lo escribia en la cabecera «para que su ausencia no se
lea como cobertura». Eso dura tres dias: al cuarto la lista ES la cobertura y el parrafo no lo lee
nadie. Corrido aparte: verde en 180 s. Entra.
Es ademas el que mide lo que ningun auditor de ELF puede ver: una pagina llama `new
Notification(...)`, `libxul` hace `dlopen("libnotify.so.4")` —que no es NEEDED de nada—, el bus
activa dunst y dunst DIBUJA. Cinco piezas y la unica forma de saber que estan las cinco es verlo.
Para que entrara, la lista pasa a llevar el COMANDO de cada guardian en vez de su nombre de fichero:
lo que lo tenia afuera era el despacho por extension, no una decision.
Corrida entera de los diecinueve sobre `b3:e556024b`: **17 verdes, 2 rojos, 32,2 min**, que es
exactamente lo que la cabecera predice — y la cabecera es el control, no un adorno.
Los caros: sct 242 s, archivo semantico 238 s, ia 182 s, instalacion 180 s, notificaciones 180 s.
Los cuatro primeros salen en 5 s y no tocan el navegador.
|
||
|
|
eaa7d02725 |
el corte, con hora: 93 minutos — y en un servicio que ya autoriza no va límite por tasa
Del `access.log`, sin ambigüedad. La IP del usuario (`45.234.61.160`, autenticada como `sergio`, 2709 peticiones) tuvo tres silencios: **93,2 min (11:59:37 → 13:32:49 UTC)**, 50,1 min (10:44:58 → 11:35:06) y 10,0 min (11:45:06 → 11:55:05). Los tres son el `timeout` de 10 minutos del ban disparándose una y otra vez: cada vez que volvía, su primera ráfaga lo baneaba de nuevo. El corte grande terminó exactamente cuando vacié los sets. 🧨 Y LA EXENCIÓN QUE TENÍA NO SERVÍA: la ACL histórica de squid exime `45.234.60.0/24` y él entra desde `45.234.61.160` — **una red de al lado**. Parecía cubierto y no lo estaba. La pertenencia se MIDE (`ip in red`), no se deduce del parecido del prefijo. DECISIÓN DE FONDO: en un servicio que YA AUTORIZA no va límite por tasa. El uso de esta caja es INTENSIVO —varias sesiones de agente en paralelo, cada una abriendo túneles a la vez— y un límite calibrado para «una persona normal navegando» no protege de nada: el atacante ajusta su ritmo, el usuario no puede trabajar más despacio. Squid ya pide usuario y contraseña y tiene su ACL. El ban del puerto queda apagado (60000/min, ráfaga 1000) y contra un flood queda el tope GLOBAL `syn_nuevas_por_seg`, que no distingue usuarios ni banea a nadie por ser rápido. Control tras aplicar: su túnel pasando —`45.234.61.160 … CONNECT api.anthropic.com:443 sergio`— y `45.234.61.160 ∈ 45.234.60.0/23` comprobado con aritmética de redes, no a ojo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
618dcfd851 |
🧨 el cortafuegos baneaba a los usuarios del proxy — y squid nunca se cayó
Preguntaron si el proxy estaba caído. NO lo estaba: el ente llevaba horas corriendo con ↻ 0 y contestando 407. Lo caído era el ACCESO — el cortafuegos de ayer baneaba a los autorizados. La prueba en una línea: **154.197.1.2, una IP que está en la ACL de squid (gente con contraseña), apareció en `@ban4`**, con el contador del ban en 38.246 paquetes tirados. LA CAUSA NO ES LA TASA, ES LA RÁFAGA: `limit rate over 300/minute burst 5 packets` tolera cinco conexiones por encima del promedio, y un navegador o un agente detrás de un proxy abre DECENAS de CONNECT en el mismo instante aunque su promedio sea bajísimo. Y como @ban4 es UN SOLO set consultado antes que todo, caer por el puerto del proxy te tira también el web y el git. ⚠ El tipo ya traía el campo `rafaga` (default 5) con un comentario que dice «para un servicio web va alto (100-200)». Otra vez leí el .ron de EJEMPLO y no el TIPO. Hecho, en orden de urgencia: flush de los sets (servicio restaurado en el acto) · los 21 IPs y 5 redes de la ACL de squid a `confiables` · `rafaga` por servicio según su clientela (ssh 5, git 20, web 100, proxy 200) · proxy a 1200/min con ban de 60 s, que es red de contención contra un flood y no control de uso. Control después: CERO autorizados en el set, 24 baneados (escáneres) y tráfico real pasando —`154.194.14.37 … CONNECT api.deepseek.com:443 sigma`, 298 K tunelados—. ⚠⚠ Y un fallo de método que casi lo tapa: **`nft -f` SUMA a la tabla existente**. Cargué el reglaset corregido encima del viejo y quedaron 30 reglas con las VIEJAS ADELANTE, así que los `confiables` nuevos no se evaluaban nunca. Por eso `cortafuegos apply-input` borra la tabla antes de cargar. La lección: un cortafuegos correcto y uno usable no son lo mismo, y la diferencia NO se ve al aplicarlo. El control que lo habría cazado es el que no corrí: una ráfaga de conexiones desde una IP que no esté en `confiables`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e903f486d9 |
el enlazador va DENTRO del compilador — /etc/cargo/config.toml es una ruta que cargo no lee
La prueba de la caja recién instalada —`cargo build` en un proyecto nuevo, sin flags ni rutas a mano— falló con `linker `cc` not found`, y destapó que la pieza que había empaquetado era falsa: **cargo NO LEE `/etc/cargo/config.toml`**. Sólo lee `$CARGO_HOME/config.toml`, los `.cargo/config.toml` del proyecto y sus padres, y lo que se le pase con `--config` — que es lo que yo venía pasando a mano en cada prueba, y por eso «funcionaba». Y el arreglo no era mover el fichero: **el que enlaza es `rustc`, y rustc no lee la config de cargo**. Las flags van DENTRO del compilador, en el spec del triple: · `linker = "ld.lld"` + `linker_flavor = Gnu(Cc::No, Lld::Yes)` — sin esto rustc busca `cc`, que una caja takana no tiene. · `crt_static_default = true` — sin esto enlaza dinámico y pide `-lgcc`, que no existe (la distro se construye con zig, que trae compiler-rt). · `crt_static_allows_dylibs = true` — su pareja obligatoria: sin ella `crt-static` apaga los dylibs y con ellos los PROC-MACROS (serde_derive, clap_derive, las 44 recetas `cargo-*` con `derive`). El compilador en sí sigue dinámico: el `bootstrap.toml` fija `crt-static = false` para el build, y eso gana sobre el default del spec. ⇒ `cargo-config` (receta, árbol y copia en scripts/) se van enteros: un paquete que instala un fichero que nadie abre es peor que no tenerlo. Y de paso, `lld21` instala los cuatro alias como SYMLINKS y no como copias: cmake los duplicaba, 5 × 96 M = 478 M de artefacto para 97 M de enlazador. Ahora 108 M, y `ld.lld` sigue despachando por `argv[0]`, que es como upstream lo diseñó. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
72ba86c290 |
la cadena rehecha: llvm21 arreglado, lld enlaza, rust reconstruido — los seis controles pasan
Un cambio de dos líneas en `llvm21` re-selló tres artefactos y los molió la granja sola, en orden,
por dependencia: llvm21 95956a16→bd4a4094, lld21 27be53c1→95a4794b, rust 015a07fb→702094a8.
llc --version Stack dump: + segfault → LLVM 21.1.2, Optimized build
opt --version ídem → LLVM 21.1.2
ld.lld «not built with zlib» → ENLAZA
rustc --version → 1.97.0 (built from a source tarball)
--print target-list → x86_64-alpine-linux-musl
cargo build + correr → 0,16 s y el binario habla
La config del sitio pasa a usar `lld` (más rápido); el `ld` de binutils queda documentado como
alternativa que también funciona.
LA REGLA QUE SE GANÓ SU LUGAR: **`link = "static"` en el encabezado decide cosas que la receta no
dice**. Van tres, todas medidas: apagó los threads de openssl, le mintió a libtool, y acá rompió los
registros estáticos de LLVM (`cl::opt`, `TargetRegistry`) porque el `static-pie` descarta sus
constructores globales. Con C++ grande la postura por defecto pasa a ser `dynamic` y medirlo — y el
control no es que selle: es CORRER UNA HERRAMIENTA del artefacto.
Y el corolario de por qué nadie lo vio en meses: el único consumidor de llvm21 era rust, que usa
`llvm-config` y las `.a`, nunca una herramienta. **Un artefacto puede estar roto en todo lo que nadie
usa** — como los 44 `cargo-*` sin cargo y los 23 binarios sin cargador.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ef1f9252a1 |
✅ EL TOOLCHAIN COMPILA, ENLAZA Y CORRE — en una caja sin compilador de C
$ cargo build --offline
Compiling prueba v0.1.0 … Finished in 0.44s
$ ./target/debug/prueba
cargo, rustc y ld: los tres nuestros
`cargo` y `rustc` son los que construyó la granja; el enlazador es el `ld` de nuestro `binutils`.
EL MURO 8 SE CERRÓ SIN `lld`. La cadena, que es lo que vale: `-C linker-flavor=ld.lld` → «linker
`lld` not found» (el build con LLVM externo no genera rust-lld); `rust.lld = true` → el bootstrap lo
RECHAZA; `lld21` propio → crasheaba con cualquier entrada; `lld21` con zlib → **no se puede encender
desde ahí**, porque `LLVMConfig.cmake` dice `LLVM_ENABLE_ZLIB 0` y el build standalone lo hereda.
Lo que faltaba NO era un enlazador: era decirle a rustc que enlace en ESTÁTICO. Con `+crt-static`,
rustc usa su propio libunwind/compiler_builtins y deja de pedir `-lgcc` — que es lo que rompía el
intento con `ld`, porque la distro se construye con zig (compiler-rt) y libgcc no existe. Y estático
es el modo natural de musl.
⇒ `scripts/servidor/cargo-config.toml` (instalado en `/etc/cargo/config.toml`) cierra el pendiente
«config del sitio» del §5, con las tres flags y el porqué de cada una.
🧨 Y `lld21` destapó algo que nadie había visto: **las 79 herramientas de `llvm21` están ROTAS**.
`llc --version` crashea igual —también en el worker— porque salen `static-pie` y el enlace estático
descarta los constructores globales de los que dependen `cl::opt` y `TargetRegistry`. Funciona lo
que no los necesita (`llvm-ar`, `llvm-config`) y revienta lo que sí; nadie lo notó porque `rust` usa
`llvm-config` y las `.a`, nunca una herramienta. Queda como deuda DECLARADA: arreglarlo (o encender
zlib) re-hashea `llvm21` y con él `rust`. Hoy no bloquea nada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
9f325a3c05 |
🐛 la granja molía UNA receta por cola y por ciclo — el xargs -I{} recibía la cola entera junta
Medido hoy al encolar `lld21` junto a `rust`: el ciclo decía «2 recetas en recipes/incoming» y
construía UNA. La causa está en el filtro de «ya sellado en el hub»: acumula en `filtrada` uniendo
con ESPACIOS, y más abajo la cola se le pasa a `xargs -I{}`, que trata **una línea = un elemento**.
Unidas por espacios, N recetas llegan como UN SOLO argumento:
printf '%s\n' " a.toml b.toml c.toml" | xargs -I{} sh -c 'echo [$1]' _ {}
recibe: [a.toml b.toml c.toml] ← uno solo
⇒ la fase paralela fallaba SIEMPRE con «No such file», y la fase 1b —el reintento serial— salvaba
exactamente UNA receta: la última, porque `basename` de esa ristra da el último nombre. El log
parecía normal, con su ✗ seguido de un ✓ (reintento serial), y ese patrón está en TODAS las colas
(yambar, spidermonkey, rust…). O sea que el reintento serial, que existe para conciliar colisiones
de concurrencia, venía tapando el bug desde que se escribió el filtro.
El arreglo son dos líneas: acumular con salto de línea y descartar la línea vacía con la que arranca
el acumulador (una línea vacía también es un elemento para `xargs -I{}`). Probado en chiquito, en
los dos sentidos: antes 1 elemento, después 3.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
24a6dd3b2f |
el cortafuegos puesto en la caja — y el primer baneado fui yo
`table inet tawasuyu_input` con policy drop, 15 reglas de puerto, y el set `ban4` llenandose solo: a los pocos minutos, 27 IPs baneadas y paquetes tirados por el contador. El reemplazo de fail2ban funcionando sobre trafico real, dentro del kernel y sin daemon. 🧨 EL PRIMER BANEADO FUI YO, EN SEGUNDOS. Aplicada la primera version: ssh y https bien, y `http`, proxy y git-ssh muertos; al minuto la caja entera dejo de contestarme. La causa no es una regla mal escrita: **el set @ban4 es UNO SOLO para todos los servicios** y `ip saddr @ban4 drop` esta antes que todo, asi que pasarse de tasa en UN puerto te tira en TODOS — y el `nuevas_por_min: 5` del puerto de administracion es exactamente lo que me pasa a mi, porque cada comando remoto es una conexion nueva. Lo devolvio el INTERRUPTOR DE HOMBRE MUERTO (un `nft flush ruleset` programado a 180 s que sólo se desarma si la verificacion externa sale bien). Sin consola serie, esa es la diferencia entre un susto y un rescue. ⚠ Y el tipo ya lo avisaba: `PoliticaEntrada` tiene un campo `confiables` cuyo comentario describe palabra por palabra lo que me paso. Copie el .ron de EJEMPLO en vez de leer el TIPO. La cura fue una linea con el hub y el worker, que se aceptan ANTES de la logica de tasa. ⚠ `nft -c` rechazo 5 reglas antes de aplicar nada: `ct count over N` es CONFIG_NFT_CONNLIMIT y este kernel no lo trae. Quedan COMENTADAS y no borradas en el fichero que se aplica — lo que la politica declara y el kernel no puede tiene que verse. El simbolo ya entro a la receta del kernel. Y para que sobreviva al reinicio, `nftables` declara su [[service]] con `lifecycle = "oneshot"`: cargar un reglaset no es un daemon, es un acto. Va PRIMERO en el genesis — entre que la red esta y que las reglas cargan, la caja esta abierta. Probado con `nft flush ruleset` + `arjectl start`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ba202d7826 |
atuq: los dieciocho guardianes en UN comando — y el sello llevaba nueve horas atrás
`atuq` es `source.dir` entero, asi que el commit de la boveda de esta manana (`6f0ba974`) movio el ArtifactHash y dejo el artefacto vigente SIN SELLAR. Durante esas nueve horas ningun guardian podia medir: se niegan a correr contra uno que no sea el vigente, que es lo correcto. Pero un guardian que se niega solo grita CUANDO alguien lo corre, y nadie lo corria — el repo se veia sano y la unica senal era un `hash --check` que nadie tenia motivo para teclear. Resellado en 3,7 s (es derivado y el firefox vigente estaba en el store) y corridos los dieciocho sobre `b3:e556024b`: 16 en verde y 2 en ROJO, los dos por la MISMA causa —los tres verbos de la boveda del §7.quinquies—. O sea que la unidad 12 no rompio nada de al lado, que es lo que habia que saber antes de seguir: toco `atuq.cfg`, la politica y el empaquetado de extensiones. `scripts/test-atuq-suite.sh` deja eso en un comando, en serie (cuatro nucleos sin swap; dos navegadores a la vez es un OOM) y cada guardian a SU fichero, porque dos corridas sobre el mismo log se mezclan y el resultado parece un pase. Lo primero que mira es el sello y NO corre nada si falta: verificado contra un store vacio, sale por ahi y devuelve 1. Dos cosas quedan escritas como control y no como adorno: · el numero de rojos esperados va en la cabecera. Un cuadro entero en verde puede ser un producto sano o un runner que no corre nada, y se ven igual; · esa cabecera decia «un rojo» —prediccion escrita antes de correr— y la primera corrida dijo dos: el guardian de coherencia de la boveda lleva el mismo chequeo del cable adentro. Coste medido en gioser: ~29 min. Los cuatro primeros salen en 10 s y no tocan el navegador. |
||
|
|
0a7afebe9c |
la caja tiene HORA, LATIDO y ROTACIÓN — y el respaldo del gitea dejó de ser a mano
`planear.py --revisar` sobre el censo ordena lo que queda, y lo primero no eran los servicios del usuario sino TRES CAPACIDADES que ninguna metrica reclama: el disparador periodico, la hora y la rotacion de logs. Los tres paquetes ya estaban sellados y declarados en `perfil.servidor` desde que ese perfil nacio — lo que faltaba era el eslabon que los ARRANCA, igual que con squid. · `chronyd`: la caja iba **15 s atrasada** y nadie la corregia. Ahora stratum 3 contra los NTP de Hetzner, 0,000005 s de NTP. Sin hora no hay TLS ni firmas, y el sintoma no se parece a la causa. · `crond`: no existia el latido. Verificado con una entrada `* * * * *` en el crontab REAL. · `logrotate`: `/var/log/squid` sin rotar. Diario, con `squid -k rotate` (squid mantiene los ficheros abiertos: un rename a secas lo deja escribiendo en un inode que nadie puede leer). · `scripts/respaldo-gitea.sh` + `17 3 * * *`: cierra el hueco del §6.15 — los 44 repos vivian en UNA copia y el respaldo se hacia a mano. Sube 23+21 repos (1,5 G) y un `gitea.db` de 332 M tomado con `.backup`, consistente con el servidor vivo. El control no es que el script salga 0: es CONTAR los repos de los dos lados. `chrony` y `cronie` NO declaraban `[[service]]` — el paquete llegaba a la imagen y no lo arrancaba nadie. Ahora lo declaran (fuera de `hash_inputs`: los hashes no se movieron) y el perfil los habilita. TRES MEDICIONES que valen mas que el resultado: 1. **La deriva era de gioser, no de la caja**: despues de sincronizar, el HUB quedo 3 s adelantado. Medir «contra el otro» sin un tercero no dice quien esta mal. 2. **El spool de cronie es `/var/spool/cron/<usuario>`**, no `…/crontabs/<usuario>`: `crontab -l` mostraba las entradas y `crontabs/` estaba vacio. Un crontab restaurado en el sitio equivocado es un fichero perfecto que nadie lee. 3. 🧨 **`sqlite3` seguia INERTE en la caja** (`compressBound: symbol not found`): el ultimo de los 23 del §6.4. Le faltaba `zlib-shared`, sellado y declarado en `perfil.base` desde el 11-09 — pero la caja se instalo antes. Sin el no habia snapshot consistente. Un paquete declarado no es un paquete instalado: en una caja viva vale lo que el `upgrade` proyecto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
db24d206dc |
arnés de la imagen: el piso del sello se COMPRUEBA — un piso que falla callado no existe
Los 180 s de `PISO_SELLO` son de esta máquina (TCG sin KVM y con la carga que tenga el anfitrión, igual que los +97/+120/+137 de la pintura). Con el anfitrión cargado el mismo arranque puede no llegar al gancho que sella el éxito, y entonces la corrida suma al contador de caídas y envenena la siguiente EN SILENCIO — que es exactamente como empezó todo esto. Ahora que `toolkit.startup.last_success` se lee antes y después, eso se puede decir, así que se dice: ✓ el arranque SELLÓ el éxito (last_success X → Y): esta corrida no envenena la siguiente ⚠⚠ el sello NO se movió: este arranque no terminó ⇒ la corrida SUMA al contador. Subí PISO_SELLO Es la diferencia entre un piso confiado y un piso comprobado, y cuesta cuatro líneas porque el dato ya estaba medido y guardado. |
||
|
|
5b47cd596c |
atuq: el reloj del sello es el del LANZAMIENTO, no el de la pintura — y el piso va en el arnés
Leyendo el perfil en cada captura, el sello (`toolkit.startup.last_success`) y la limpieza del contador llegan al disco entre +100 s y +137 s DESDE EL LANZAMIENTO, no desde el primer cuadro. Y es UN gancho y no dos: el mismo arranque que limpia escribe el sello, con su propia hora de arranque. Las dos cotas salen de corridas independientes, que es lo que hace al intervalo creíble: · a los +100 s NO había sellado — la corrida S1 vivió 100 s desde el lanzamiento (primer cuadro a +97 s, fin del proceso tres segundos después, medido con los tiempos de sus propios ficheros); · a los +137 s YA había sellado — la corrida con `--watch-prefs` lo ve aparecer en esa captura. S1 parecía la excepción del modelo y resulta ser su cota inferior: si el reloj fuera el de la pintura tendría que haber sellado, y no selló. La consecuencia es contraintuitiva y es la que muerde: cuanto MÁS RÁPIDO pinta, más fácil es envenenar el perfil, porque `--until-paint` corta antes. La serie de ayer no se rompió por ser larga sino por ser rápida. Por eso la regla va en el arnés y no en un runbook: `PISO_SELLO = 180`, margen sobre la cota alta, y `--until-paint` no corta antes de ese piso. El comentario del piso lleva los dos números medidos al lado — un piso que se lee como elegido lo baja el próximo que lo vea. Las corridas `cierre-1`/`cuanto-1`, el mando `--watch-prefs` y el piso los midió y escribió la otra sesión del frente; acá van con la cota de S1 que los cierra por abajo. |
||
|
|
1de1fc31f6 |
atuq: lo que limpia el contador es SOBREVIVIR un rato después de pintar, no pintar
El par aísla la variable —mismo pin, mismo perfil, misma imagen, y la única diferencia es cuánto vive el navegador después del primer cuadro—: S1 +97 s, muerta en el acto (`--until-paint`) ⇒ contador después: 1 cierre-1 +120 s, ~360 s de vida después de pintar ⇒ contador después: AUSENTE Con eso queda contestada la pregunta que dos secciones atrás este documento daba por cerrada y no lo estaba, y A deja de ser una anomalía: también siguió viva minutos después del primer cuadro. Se dice además lo que la tabla NO prueba: si ese arranque además SELLÓ el éxito. El volcado del «después» sólo grepeaba el contador; ahora `toolkit.startup.last_success` va en los DOS volcados y se guarda en la medición (`last_success_antes` / `_despues`), que es lo que faltaba para no repetir la corrida. Si el sello se mueve, es un gancho que sella y limpia a la vez; si no, son dos. Y queda escrito que el 16 sigue sin explicación, ahora con una contradicción medida encima: desapareció en una corrida diálogo + ATUQ-EXIT=0, y las fases k2/k3 del umbral fueron diálogo + ATUQ-EXIT=0 y no limpiaron nada. La respuesta buena no tapa la sucia. La corrida `cierre-1` y el mando `--watch-prefs` son de la otra sesión del frente. |
||
|
|
c30905e2b9 |
atuq: vigía de las dos puntas del cable — y el extractor de verbos que no veía la mitad
El chequeo del commit anterior miraba una extensión. El cable tiene la misma forma para las diez, y
este vigía lo recorre entero: qué verbos manda cada extensión y si el commit que `recipes/
puriy-costura.toml` pinea los atiende. Corre en un segundo y no construye nada.
Medido hoy, 13 verbos sobre 8 extensiones: sólo los TRES de la bóveda están sin dueño. O sea que
esto no se podía deducir mirando cuándo se tocó la receta por última vez — las otras nueve
extensiones conviven bien con el mismo pin viejo.
⚠ Y lo que costó más que el vigía: **el extractor de verbos no veía la mitad de lo que hay**. Estaba
buscando `verb: "…"`, que es como lo escribe la bóveda. Pero la extensión de IA arma el mensaje con
`postMessage({ id, verb: verbo, … })` —el verbo llega por VARIABLE—, así que ese patrón encontraba
cero verbos en `ia` y la declaraba sana sin haber mirado nada: cero verbos encontrados y cero verbos
faltando son el mismo cero con dos causas. Ahora recoge toda cadena con forma `ns.verbo` y descarta
las que son nombres de fichero, que es lo único que se les parece. Con eso aparecen `ai.ask` y
`archive.ask`, que antes no estaban en ninguna cuenta.
De ahí sale el tercer control, que no es negativo ni positivo sino de COBERTURA: una extensión de la
que no se extrae ningún verbo se REPORTA. Y las dos que de verdad no le hablan al host —`inicio` y
`proxy`, que viven enteras dentro del navegador— están nombradas en el código, para que «no manda
verbos» no se pueda confundir con «el extractor se quedó mudo».
Los otros tres controles: `sct.observe` tiene que aparecer en el commit pineado (si no, lo roto es
el vigía y lo dice); `--negative-control` agrega un verbo inventado a cada extensión y exige que lo
vea; y si el clon de tawasuyu no está, o no conoce el commit pineado, esto FALLA — «no se pudo
comprobar» no es «está bien». Los cuatro probados, incluida la rama del commit desconocido.
Pregunta por `git grep '"<verbo>" =>' <commit> -- '*.rs'` leyendo del objeto: sin checkout, porque
el árbol de tawasuyu es compartido y siempre tiene ficheros en vuelo de otras sesiones.
|
||
|
|
e7369efe82 |
atuq: la bóveda le habla a un host que no sabe sus verbos — y eran CINCO lugares, no cuatro
Este guardián nació esta mañana diciendo que una extensión está enchufada en CUATRO sitios y que
ninguno da error si falta. Los cuatro estaban bien. El quinto no, y es el que hoy está roto.
El quinto lugar vive en otra receta y en otro repo: `recipes/puriy-costura.toml` pinea un commit de
tawasuyu, y es ESE commit el que decide si `vault.match` es un verbo o una cadena que nadie atiende.
Medido, con su control al lado, sobre el artefacto SELLADO y vigente (`hash --check` dice SELLADO
para `b3:1eb2b692`):
strings del binario sellado → «vault» 0 veces · «cas» 5 veces ⇐ EL CONTROL
El cero solo no probaba nada —un grep que devuelve cero puede ser una ausencia o un sitio
equivocado, y las dos se ven igual—; lo que lo convierte en evidencia es el «cas» que TENÍA que dar
positivo en el mismo binario. Y del lado de la fuente sale lo mismo: la receta pinea `e19bb0e5`, que
es ANCESTRO de `bc6903f9e` —el commit que agregó los verbos—, así que el host sellado es anterior a
la bóveda por construcción.
Cómo se ve ese fallo desde la silla del usuario, que es por qué esto merece guardián: el manifiesto
está, el permiso está, la política instala, `connectNative` CONECTA, la extensión manda
`vault.match` y el host contesta `verbo desconocido`. `fondo.js` lee `r.ok !== true`, borra la
insignia y se calla. Un navegador sin bóveda, y todos los ficheros en orden.
El chequeo pregunta `git grep '"<verbo>" =>' <commit> -- '*.rs'` sobre el clon, sin checkout: el
árbol de tawasuyu es compartido y siempre tiene ficheros en vuelo de otras sesiones. Los verbos los
saca de `fondo.js` y no de una lista escrita acá, que se desincronizaría justo en el único sitio
donde eso no se ve.
Tres controles, porque el chequeo es un grep que espera cero:
- POSITIVO: `sct.observe` tiene que aparecer en el commit pineado. Si no aparece, lo roto es el
guardián y lo dice así, en vez de acusar a la receta;
- `--negative-control-verbo`: le agrega un verbo inventado y exige que lo detecte;
- y si el clon no está, «no se pudo comprobar» NO es «está bien»: falla ruidosamente.
Lo que este commit NO hace: subir el pin. Eso es su propia unidad y tiene un muro medido delante —el
`Cargo.lock` de tawasuyu en `main` todavía describe a `puriy-costura` con sus once deps viejas, sin
`pacha-boveda` ni `pacha-cifrador`, y `pacha-boveda-daemon` no está en el lock en absoluto. Con eso,
`cargo vendor --locked` muere sin nombrar al culpable, que es exactamente la trampa que la propia
receta dejó escrita.
|
||
|
|
98c9e4daa6 |
atuq: la tasa con perfil sano es +97 y +98 s — y PINTAR NO ES TERMINAR el arranque
Cuatro arranques fríos en serie, el perfil partiendo del 5 que dejó la corrida del umbral: S1 pin 0 antes 5 después 1 el navegador ✓ +97 s S2 sin pin antes 1 después 2 el navegador ✓ +98 s S3 sin pin antes 2 después 3 (matada a los 60 s) S4 sin pin antes 3 después 4 EL DIÁLOGO ✗ en 420 s Tres cosas: · la tasa, por fin con perfil sano: dos corridas independientes y pegadas, +97 y +98 s, contra los +123/+195/+197/+204 de antes. Con el anfitrión descargado el mismo artefacto pinta en la mitad del tiempo ⇒ el número es de la máquina y la carga tanto como del producto, y se publica con sus condiciones; · el umbral se reprodujo solo y en otra secuencia: S4 arrancó con 3, sumó a 4 y abrió el diálogo — segunda medición independiente del mismo borde, esta vez identificando la ventana por `min_size` y no por el título (que está traducido, así que un grep por «Troubleshoot Mode» en una imagen en castellano no engancha y su ausencia se lee como la conclusión contraria); · ⚠ y una corrida que PINTA no limpia el contador: S2 pintó y dejó 2. Más todavía, `toolkit.startup.last_success` vale lo mismo en las CUATRO (1789494795, las 17:53) ⇒ el gancho de cierre no corrió ni una vez, ni en las que pintaron. Pintar no es terminar de arrancar. Entonces «quién limpia el contador» SIGUE SIN MEDIR, y el propio documento decía que esta serie lo cerraría: no lo cierra, y queda escrito así. Hace falta una corrida que deje al navegador terminar de arrancar y salir limpio. Lo más caro es para el instrumento: con `--until-paint` cada corrida suma uno, pinte o no, así que una serie se rompe sola a la cuarta — que es lo que pasó ayer sin que nadie lo viera. El arnés ahora AVISA cuando el contador está en 3 («esta corrida va a abrir el diálogo, no el navegador») y la receta para una serie queda escrita: pinnear `--crashes 0` en cada corrida, que resetea la base (S1 lo muestra partiendo de 5). También cae una candidata mía, con un cero que viene con control (medido por la otra sesión): el pref no tiene default en este build, así que fijarlo en 0 SÍ se persiste y el «ausente» no era el pin. |
||
|
|
7997464337 |
atuq: el umbral son 4 — y mostrar el diálogo NO limpia el contador, como este documento decía
Medido en los dos lados, partiendo del perfil en 2 y con tres fases más en un arranque: el que encuentra 2 guardado PINTA (y queda en 3); el que encuentra 3 lo sube a 4, compara 4 > 3 y abre el diálogo (y queda en 4); el siguiente queda en 5 y vuelve a abrir el diálogo. O sea que la comparación va DESPUÉS del incremento del propio arranque: contando desde la última corrida que terminó bien, son cuatro arranques interrumpidos seguidos y el quinto lanzamiento ya no abre el navegador. `toolkit.startup.last_success` vale lo mismo en las tres fases (1789494795, el arranque de la última corrida sana): el contador sube mientras esa marca no se mueve. Con las dos cifras juntas, un contador que no cambió deja de ser ambiguo entre «no subió» y «subió y se limpió». ⚠ Y eso refuta una atribución que yo había escrito ayer y que estaba en tres sitios: mostrar el diálogo NO limpia el contador (k2 lo dejó en 4, k3 en 5). Queda retirada del SDD y del comentario del arnés. La desaparición del 16 entre las 17:12 y las 17:20 pasa a ser un hecho SIN explicación medida, con dos candidatas escritas y ninguna dada por buena — y una de ellas es mía y fea: todas las corridas que pintaron llevaban `--crashes 0` pinneado, que es el valor por DEFECTO, y un pref igual al default no se persiste. El «ausente» puede ser eso y no una limpieza. Queda dicho cómo se cierra: dejar el contador en 2 SIN pinnearlo, correr hasta que pinte y leerlo después. Un discriminador que salió gratis: `ATUQ-EXIT=137` es «la matamos» (128+9) y `ATUQ-EXIT=0` es «se fue sola» por el diálogo. El código de salida contesta lo que una captura no podía. Medido por la otra sesión del frente (work/umbral-1.txt), con la predicción escrita antes de mirar. |
||
|
|
2f90d8eb40 |
arnés de la imagen: el contador de caídas viaja con la medición, y las envenenadas se apartan
`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. |
||
|
|
6f0ba974c7 |
feat(atuq): la boveda de la suite guarda las contrasenas, y el gestor de Gecko se aparta
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. |
||
|
|
798cfab7e8 |
atuq: el 117x70 lo pide el CLIENTE, no el compositor
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. |
||
|
|
f4e1e99f19 |
atuq: la ventana SI esta — mide 117x70 px (lo dice el protocolo)
Como cosmic-comp no emite DEBUG (macros compiladas fuera), se le pregunto por el protocolo con WAYLAND_DEBUG=1. Manda configure_bounds(0,0) al mapear, despues bounds(1280,692) y configure(117,70): la ventana queda de 117x70. El pixel lo confirma — restando el cuadro de antes aparece un cluster de 286x86 con la decoracion de COSMIC en (496,115). Cae la hipotesis del §6.10.nonies: hay 43 wl_callback.done, o sea que SI hay frame callbacks. Y la tasa 2 de 10 no medía «pinto/no pinto» sino «ventana grande / ventana de 117x70». Arnes: --wayland-debug y --set-rust-log (perilla por /etc/cosmic-mode, que cosmic-start lee antes de RUST_LOG); margen de espera 420->780s porque con carga ~13 la VM no llegaba al shell. |
||
|
|
21381b598f |
atuq: la serie con UNA sola tarjeta — 2 de 10, y el hueco resulta real
Con -vga none la captura ya no tiene punto ciego, asi que un ✗ significa «la ventana no esta». Clasificado el ultimo cuadro de las 10: 2 con la pagina pintada, 1 con la ventana en blanco y 7 con SOLO el escritorio. Las dos salidas explicaban los ✗ inflados de las series VGA=1, no el fenomeno. El MOZ_LOG separa los dos regimenes: las que tienen ventana escriben 952-1505 lineas hasta el apagado; las 7 sin ventana se cortan a los ~40s y quedan 6 minutos de silencio, sin crash (WebRender arranca). Quedan como hipotesis NO medida los frame callbacks que el compositor no manda. Ademas: tasa-primera-pintura.sh nombra el log por VGA (la serie nueva habia sobrescrito los crudos de la anterior). |
||
|
|
77732edead |
hydrate-profile: encontrar el binario donde ESTÉ — no sólo en el árbol de desarrollo
`HAMMER` estaba cableado a `ROOT/target/release/takana`, así que el guión no corre en una caja
INSTALADA, donde el binario es `/usr/bin/takana` y `target/` ni existe:
FileNotFoundError: [Errno 2] … '/opt/takana/target/release/takana'
Es la misma deuda que este frente ya pagó dos veces —`respaldo-storagebox.sh` y el «unhashable 875»
con el lab perfecto—: el instrumental asume que el hub es un árbol de desarrollo. Ahora busca en
`$TAKANA`, `$HAMMER`, el árbol de desarrollo y el `PATH`, en ese orden.
Salió al ir a actualizar la caja de producción a la imagen nueva, que es exactamente el caso de uso
para el que no servía.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
5b98d1b2bf |
atuq: no era el perfil — son DOS SALIDAS, y el «3 de 13» medía la cámara
Dos cosas antes de la medición: el MOZ_LOG de las que pintan y las que no termina IDÉNTICO (las dos
commitean WaylandBufferSHM de 1280x696), y el cruce de las 15 corridas está confundido — las 13 de
«perfil del usuario» salieron todas con VGA=1.
`screendump` de QMP fotografía UN dispositivo. Con VGA=1 hay dos, y hasta ahora se miraba uno. Con
`id=` en cada uno y capturando los dos en cada toma, en la misma corrida y el mismo instante:
+312s ██ PRIMERA PINTURA — 704456 px EN vga0
gpu0 = 962675 px de fondo + panel y dock (el escritorio, SIN navegador)
vga0 = 703766 px magenta (la ventana, con la página)
⇒ cosmic-comp maneja las DOS salidas y la ventana cae en una u otra. «Pinta 3 de 13» era cuántas
veces cayó en la pantalla que yo fotografiaba: una tasa de mi cámara, no del producto. El §6.10.ter
tiene la misma explicación (aquel arranque también traía `drm: card0 card1`).
Queda en pie, ya sin confundido: el navegador arranca, mapea y commitea SIEMPRE; cuando la ventana
está en la pantalla que se mira, se ve entre +184 y +312 s; el perfil no era la causa y cosmic-comp
nesteado tampoco.
⚠ Tercera vez en el día con la misma forma: un ✗ de una captura de UNA pantalla no dice «la ventana
no está», dice «no está en ESA» — igual que `mapped 1` no probaba que se viera. El estado anota ahora
en qué pantalla apareció y cuántas se fotografiaron, y el vigía NO cuenta las ciegas: las nombra.
|
||
|
|
49b2842ddb |
atuq: la TASA — pinta 3 de 13 con el perfil del usuario, y cuando pinta siempre a ~200 s
Diez corridas idénticas con `scripts/cosmic/tasa-primera-pintura.sh` (--as-user, VGA=1, ventana 480 s,
captura cada 30 s), más las cinco anteriores, todas anotadas en docs/state/primera-pintura.json:
perfil del usuario 3 de 13 pintaron 184, 199, 204 s
perfil nuevo en tmpfs 2 de 2 197, 197 s
Dos cosas que la tasa dice y una anécdota no podía: cuando pinta, pinta SIEMPRE en la misma ventana
(184…204 s) y nunca a los 300, 400 ni 900 ⇒ no es una cola larga, son DOS REGÍMENES —o sale a los
~200 s o no sale—; y con el perfil del usuario falla ~3 de cada 4, mientras que con perfil nuevo en
tmpfs no falló (2 de 2, pocas corridas para afirmar que nunca, suficientes para saber dónde mirar:
qué hace el primer arranque del perfil que el perfil ya hecho no hace).
⚠ Condición de la medida, parte del número: las diez salieron con el anfitrión a load ~10 (tanda de
KDE de otro agente + una VM de otra sesión, en 4 cores). El número es un PISO, no una constante.
⚠ Y una trampa del arnés medida en las corridas 8–10: con esa carga el guest tarda más de 420 s en
llegar al shell y los `esperar` vencen. No las invalida —se verificó una por una que el navegador se
lanzó y dejó su MOZ_LOG de 1200–5000 líneas— pero una corrida abortada de verdad se ve casi igual:
por eso el guion cuenta las abortadas APARTE en vez de sumarlas a los fallos.
|
||
|
|
79a9ac5bda |
farm: sembrar-fuente.sh — el worker construye lo de tawasuyu SIN tener la clave
El worker no puede clonar el gitea de tawasuyu y **no debe poder**: esa clave es el SSH de todo y
él sólo necesita leer un repo. Lo que viaja es el ÁRBOL, no la credencial: el hub —que sí la
tiene— clona, y el guión manda el mirror.
⚠ Y copiar el mirror tal cual NO alcanza, que es lo que costó descubrir: el fetch de takana clona
con `--filter=blob:none`, así que el mirror copiado **no tiene los blobs** y los va a buscar a un
remoto que allá no responde. El síntoma no nombra la causa:
tar: This does not look like a tar archive
Error: git archive | tar -x falló (tar exit Some(2))
Por eso el guión HIDRATA (`fetch --refetch --no-filter`, 17 M → 210 M) y verifica con la prueba del
CONSUMIDOR —`git archive` de verdad, en el worker— y no con `cat-file -e`, que pasa igual sobre un
mirror parcial. Más el `chown -R root:root`, sin el cual git rechaza el repo por «dubious
ownership» cuando el builder corre como root: falla al construir, no al copiar.
Dos bugs propios, cazados corriéndolo:
· `grep -q .` sobre la salida de `git archive` decía «vacío» con un archive perfectamente bueno: es
un tar BINARIO y puede no traer un salto de línea en los primeros bytes. Va `wc -c`.
· `head -c` cierra el pipe, `git archive` muere con SIGPIPE y con `pipefail` eso hacía fallar el
pipeline entero: el guión se cortaba EN SILENCIO justo en la línea que dice que verifica. Un
verificador que aborta sin decir nada es peor que no verificar.
Probado con `recipes/arjectl.toml`: idempotente (segunda pasada no reclona) y el worker extrae el
árbol. Y el control del contrato: con una receta de tarball dice que no es una receta git y sale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
fd09ca77b0 |
arjectl en la imagen: relanzar en caliente — y el genesis NO alcanzaba para eso
El servicio que falla y agota su backoff sólo se podía recuperar reiniciando la máquina entera.
Ya no. Y no hubo que escribir nada: **el cliente existía en tawasuyu y el crate se llama `arje-ctl`**
(el binario, `arjectl`). Buscarlo por `arjectl` no lo encontraba, y de ahí salió mi conclusión falsa
de que había que implementarlo — el protocolo ya traía ListEntes, SpawnCardFromDisk,
StopCardFromDisk, KillEnte y EnteStatus.
`recipes/arjectl.toml` lo construye del MISMO commit que `arje-zero` (98db584f), y no por comodidad:
el bus es un protocolo entre dos binarios y un cliente de otro árbol puede conectar sin entenderse
con el init. Publica sólo `arjectl`; el crate también produce un `systemctl` de camuflaje que acá no
se instala — en una distro sin systemd, ese nombre en el PATH invita a escribir runbooks con el
verbo ajeno.
⚠ EL HUECO QUE SÓLO SE VE USÁNDOLO: el genesis de la seed dice qué arranca AL BOOT, pero
`start`/`restart` usan `SpawnCardFromDisk`, que lee `/etc/arje/cards.d/<label>.json` — y el armado
no lo escribía:
$ arjectl start gitea
Error: arje-zero rechazó: card gitea: No such file or directory
(buscada en /etc/arje/cards.d/gitea.json)
Son dos preguntas distintas —qué arranca solo, y qué se puede encarnar a pedido— y arje las responde
desde sitios distintos. `inyectar-cards.py` escribe ahora los dos árboles, y en `cards.d` escribe
TODAS las cards, no sólo las nuevas: `sshd` viene del product-rootfs y tampoco era relanzable.
Medido con la VM arrancada UNA sola vez: la imagen trae `cards.d/{gitea,sshd}.json` · `list-units`
da PID/CPU/MEM/HILOS/reinicios · poner el `app.ini` + `arjectl start gitea` ⇒ **GET / 200 sin
reiniciar** (uptime 6 min) · `arjectl restart gitea` cambia el PID (118 → 192) y sigue en 200.
⚠ Y un aviso que costó un HTTP intermitente: **`arjectl start` sobre un Ente YA VIVO lo DUPLICA** —
`SpawnCardFromDisk` no deduplica por label y arje le da un ULID nuevo. En gitea el síntoma fue
`unable to lock level db … resource temporarily unavailable` y un `[F]`: dos servidores peleando por
el mismo estado. Para relanzar se usa `restart`, o se mira `list-units` antes. Un `start` idempotente
es trabajo de arje, no de esta imagen.
⚠ Deuda anotada, no barrida: `arjectl` va en `perfil.servidor` porque este frente es el que lo pagó.
TODA imagen de takana corre arje-zero como PID 1 y ninguna se puede operar sin él ⇒ el argumento
para subirlo a `base` es fuerte, y es una línea. Se deja como decisión.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
467a87cdb8 |
la imagen del servidor, armada y booteada — tres fallos que sólo aparecen ahí
Se armó la imagen del perfil `servidor` con gitea y se arrancó en QEMU. Los tres hallazgos, en el orden en que aparecieron, son los que justifican probar la imagen en vez de dar por buena la receta. 1) ⚠⚠ ESCRIBIR EN EL ROOTFS HIDRATADO ES ESCRIBIR DENTRO DEL STORE `takana users --merge` hacía `fs::write` sobre `<rootfs>/etc/passwd`. Un rootfs hidratado se arma con HARDLINKS contra el store: medido, ese fichero y el del artefacto `product-rootfs` eran **el mismo inode (1225824, 2 links, modo 444)**. Un `write` habría modificado el artefacto SELLADO, y todas las imágenes futuras habrían salido con la cuenta metida dentro del producto. Acá se salvó porque el store es de sólo lectura y salió `Permission denied` — confiar en eso es confiar en un permiso. Ahora `escribir_rompiendo_hardlink()`: temporal + `rename`. Con su control, que afirma lo que importa: tras escribir, el fichero del store **conserva su contenido**, baja a 1 link y el del rootfs tiene otro inode. Verificado también sobre la imagen real. 2) LA IMAGEN TRAÍA EL BINARIO, LA CUENTA… Y NADIE LO ARRANCABA Primera imagen: `gitea` instalado, `gitea:x:916` en `/etc/passwd`, y en el `genesis` de la seed sólo `sshd`, `console-getty`, `hammerd`, `hammer-product`. `servidor-image.sh` no inyectaba las Cards — eso sólo estaba en el camino de las imágenes de escritorio. Y ninguna métrica lo dice: `--services` responde que el perfil lo habilita, y lo habilita; lo que faltaba era el paso que lleva esa declaración a la imagen. Ahora llama a `service-cards` + `inyectar-cards.py` (idempotente por label). 3) ⚡ EL BINARIO MORÍA CON `trap invalid opcode` — Y EL BUG ESTABA EN EL BUILDER Con la card en el genesis y la config puesta, gitea arrancaba y moría al instante: traps: gitea[91] trap invalid opcode ip:79ea992 ... in gitea[...] El sandbox exporta `CC` apuntando a un wrapper que pone `-mcpu=baseline` (`sandbox.rs` ya avisaba: «de paso cierra el SIGILL de AVX en qemu64»), y la fase Go del propio builder lo PISABA con `CC="zig cc"` a secas — que por defecto es `-mcpu=native` y hornea la ISA del que compila. El binario corría perfecto en el worker y moría en QEMU-TCG. No falla al compilar ni al sellar: falla al EJECUTAR en otra CPU. Y el `ArtifactHash` no lo puede cazar, porque la CPU del builder no entra en `hash_inputs` — dos workers distintos sellan bytes distintos bajo la misma dirección. Arreglado en el builder y en la receta; re-hashea las CINCO recetas `cgo = true` (gitea, usql, sq, gocryptfs, naabu), que es correcto: lo que había sellado no es portable. gitea: `b3:391a613e…` → `b3:5edf9c16…`. Lo verificado en la VM: PID 1 = arje-zero · la cuenta de `[[user]]` en `/etc/passwd` y `/etc/group` de la imagen · la card de gitea en el `genesis` · y arje encarnándola, con la guarda saliendo 78 y `/var/log/arje/ente-gitea.log` diciendo exactamente «falta /etc/gitea/app.ini — es config del SITIO». Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
c14db82263 |
atuq: el tiempo de primera pintura, en el vigía de la imagen — y es 3 de 5, no un número
`scripts/cosmic/atuq-en-imagen.py` ya no contesta «pinta / no pinta» sino CUÁNTO TARDA: mide cada
captura, imprime `+Ns ██ PRIMERA PINTURA`, acepta `--budget` (def. 420 s) y `--until-paint`, y sale
≠0 si no pintó o si se pasó del presupuesto. El magenta se cuenta CON TOLERANCIA porque `cosmic-idle`
atenúa la pantalla al 46 % a los ~+590 s y un contador por color exacto lo lee como «desapareció».
El número se publica en `docs/state/primera-pintura.json` y `scripts/vigia-imagen.py` lo informa como
sexto dato, marcado como «no lo mide este vigía» (arrancar la imagen son ~15 min sin KVM). Se ACUMULA
una entrada por corrida, no se pisa, y el vigía informa la tasa:
⚠ pintaron 3 de 5 corridas · primera pintura +197…+204s
⚠ Y eso corrige lo que publiqué hace una hora. Con cinco corridas sobre la MISMA imagen: pintó en
tres (+197, +197, +204 s) y NO pintó en dos —una con 900 s de observación—, siendo la 4 y la 5 el
mismo comando. El fallo es INTERMITENTE: ni «nunca pinta» ni «sólo tardaba».
El argumento que me llevó a «tarda» era el MOZ_LOG (`mapped 1` + «has buffer» + WaylandBufferSHM
commiteado). La corrida 5 lo refutó: commiteaba cuadros desde ≤+377 s con la pantalla vacía a los
900 s. Que el cliente se crea visible NO es evidencia de que se vea; la evidencia es el píxel. Queda
escrito en el vigía, al lado de la tasa, para que no se vuelva a usar como prueba.
De paso, las flags nuevas nacen en inglés (CLAUDE.md §4): --as-user, --wait, --budget, --until-paint,
--only-cosmic; los mensajes siguen en castellano.
|
||
|
|
d611f8577d |
[[user]] en la receta: la otra mitad del SDD 30 — un demonio que no corre como root necesita cuenta
El SDD 30 sacó las Cards de una constante de Rust y las puso en la receta. Los USUARIOS se quedaron
donde estaban las Cards: `/etc/passwd` de la imagen es `takana_bootstrap::PRODUCT_PASSWD`, un literal
con `root` y `sshd`, y añadir un tercero era editar Rust y recompilar takana.
No es simetría por elegancia, es un servicio que no arranca: `gitea` se niega a correr como root, su
Card hace `setuidgid gitea`, y sin la cuenta arranca, muere y reintenta para siempre con un log que
dice `unknown user` — no «a la imagen le falta una cuenta».
[[user]]
name = "gitea"
uid = 916
home = "/var/lib/gitea"
**El uid se declara, no se asigna**, por la misma razón que el ULID de la Card: un «primero libre a
partir de 1000» hace que dos imágenes del mismo perfil salgan con dueños distintos y el rootfs deje
de reproducir SIN QUE NADA FALLE — los ficheros se ven iguales y `ls -l` dice otro número. Fuera de
`hash_inputs`, medido: el hash de gitea no se movió (`b3:391a613e…` antes y después).
`takana users <recetas…> [--merge <rootfs>]` es el gemelo de `service-cards`, y fusiona **por clave,
no por línea entera** — componer dos veces no duplica, y `grep -q` de la línea completa no serviría
porque la misma cuenta con otro GECOS se leería como nueva. Tres decisiones con su control:
· Una cuenta ya presente con OTRA línea es CONFLICTO y no se pisa: sale ≠0. Pisarla es cambiarle el
uid a ficheros que ya son de alguien, y eso se descubre dentro de la VM.
· Se planea todo y sólo entonces se escribe. Fichero a fichero, un conflicto en `passwd` dejaba el
`group` ya escrito: media cuenta es peor que ninguna, porque parece que está. Comprobado con el
caso exacto — `group` sin la cuenta, `passwd` con otro uid — y los DOS ficheros quedan intactos.
· Un `passwd` ausente es un error, no un fichero a crear: crearlo dejaría una imagen SIN `root`.
La validación rechaza lo que rompe tarde: `root`/`sshd`/`nobody` y sus uids, uid fuera de
100..=65533, `home` relativo y un `:` en cualquier campo — que partiría la línea y fallaría lejos.
Y `--user-paths` NO es `--service-paths` con otro nombre: aquél lista los servicios HABILITADOS,
éste los paquetes INSTALADOS que declaran cuentas. `postgres` instalado y sin levantar necesita su
usuario igual, porque los ficheros de la imagen ya son suyos.
`scripts/servidor-image.sh` lo aplica entre hidratar el perfil y sellar la imagen, y aborta si hay
conflicto: un rootfs con el uid equivocado produce ficheros de un dueño que no existe.
Verde: 6 tests del módulo, core 234, cli 88, bootstrap 42, `targets.py --selftest` 7/7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
de7c3faa9c |
gitea arranca: su [[service]], probado con el entorno de verdad — y dos fallos que sólo salen ahí
La receta ya declara su servicio (SDD 30) y `perfil.servidor` lo habilita. El ArtifactHash NO se mueve (`b3:391a613e…` antes y después): `[[service]]` está fuera de `hash_inputs`. ⚠ Primero, una corrección del commit anterior: declaraba `gitea` en `perfil.servidor` con SEIS LÍNEAS DE COMENTARIO y sin la línea `"gitea",`. El perfil cargó igual, el grafo no dijo nada y el paquete simplemente no estaba. Lo cazó `targets.py --services` al resolver el label: «lo declara gitea, que NO pertenece al perfil». Un comentario que explica una entrada que no existe se lee como la entrada. La card no encarna el binario directo: **gitea se niega a correr como root** (`[F] Gitea is not supposed to be run as root`), así que va por `setuidgid`. Y comprueba dos cosas del SITIO antes de arrancar, saliendo 78 con un mensaje que las nombra: `/etc/gitea/app.ini` y el usuario `gitea`. Un gitea sin config no falla — arranca y ofrece el asistente de «crear administrador» a quien pase. Probado en el worker con el argv exacto y `env -i`, que es lo que arje hace de verdad. Tres fallos que con una shell normal no se ven NUNCA: · `exec setuidgid …` a secas ⇒ `sh: exec: line 0: setuidgid: not found`. El `sh` de busybox de la imagen no trae `FEATURE_SH_STANDALONE`: no despacha sus applets, los busca en `PATH`, y PID 1 no garantiza ninguno. Todo con ruta absoluta (`/bin/grep`, `/usr/bin/setuidgid`). · sin `PATH` en el `envp` ⇒ `git not found: executable file not found in $PATH`. **gitea lanza `git` como subproceso**, y el mensaje se lee como «falta git» con git instalado y raíz de `base`. Un envp vacío no es «limpio»: es sin PATH. · sin `cd` ⇒ `fatal: error reading '/root/.git'`. El cwd se hereda y gitea corre `git config` en él. `Service` no tiene campo `cwd`, así que va en el argv, a la vista. Con eso: escucha, `GET /` responde **200** y el proceso corre como `gitea`. Las dos guardas verificadas por separado (78 y su mensaje cada una). El worker quedó limpio y censado: sin usuario, sin /etc/gitea, sin /var/lib/gitea, sin symlinks y sin procesos. Y `/etc/gitea` entra en las rutas de la mudanza: el `app.ini` guarda los SECRETOS generados (gitea los escribe en el propio fichero, que por eso tiene que ser suyo, `gitea:gitea 0660`). ⚠ Lo que queda abierto y está anotado en la receta: **el usuario `gitea` no existe en el producto**. `/etc/passwd` de la imagen es la constante `takana_bootstrap::PRODUCT_PASSWD` y sólo trae `root` y `sshd`. El SDD 30 sacó las Cards de una constante de Rust y las puso en la receta; los USUARIOS siguen donde estaban las Cards. Hasta que se declaren, el usuario llega con los datos del sitio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
76535f8e42 |
censar: el home de la PERSONA no se miraba nunca — y ahí está la clave privada de release
`fuera_de_git()` era una lista cableada de 10 rutas con `~` adentro, y el censo corre como root: `~` se expandía a `/root`. La huella quedó en `work/mudanza/censo-gioser.toml`, donde `~/.ssh` y `/root/.ssh` salen IDÉNTICOS. Lo que nunca se censó por su nombre: /home/sergio/.config/takana ⚠ la clave PRIVADA de release (el .pub está EN el repo) /home/sergio/.config/gh el token con el que se crean los espejos de los 26 repos /home/sergio/.ssh/config el bloque `git.gioser.net` `Port 2345`, sin el cual no se clona /home/sergio/.gitconfig los `insteadOf`, que reescriben remotos y esconden que uno es local /home/sergio/.claude 6,8 G la memoria y los transcripts del proyecto Ninguna de esas falla el día que se borra la máquina: la clave falla la próxima vez que alguien firma, y para entonces no hay de dónde sacarla. Hoy sólo viajaban dentro del bloque `/home` (22 G, `destino = ""`), o sea sin que nadie las hubiera mirado. Cuatro cambios: · **La lista sale a `rutas-fuera-de-git.txt`**, un fichero de datos con el motivo de cada ruta. Una lista cableada se queda vieja sin que nada falle: la anterior preguntaba por `~/.config/hammer` —muerto desde el renombre, existe VACÍO— y no preguntaba por `~/.config/takana`. · **`homes()` lee `/etc/passwd`** y expande `~/…` por cada home real (root incluido, cuentas de servicio con `nologin` fuera). En gioser: 6 homes, 19 entradas contra las 9 de antes. · **Tres estados, no dos.** Un `ls` sin permiso se lee igual que un directorio ausente: `ausente` se descarta, `sin_permiso` se REPORTA («2 rutas EXISTEN y no pude leerlas») para que nadie decida sobre una lista incompleta creyéndola completa. Probado corriendo el censo como no-root. · **Una sola llamada** en vez de una por ruta: N rutas × M homes por SSH era el censo tardando más que el trabajo que describe. Y cada entrada lleva dueño, tamaño y por qué importa. Y el paso que faltaba en `planear.py`: `fuera_de_git` no lo consumía NADIE, así que el censo lo listaba y el plan no proponía copiarlo — se veía y no se actuaba. Ahora es el paso 0 bis, pegado al del código, con revisión humana para decidir y, por cada ruta `muda`, su `rsync -aHAX --numeric-ids` (permisos: una llave con el modo cambiado no falla al copiarse, falla al usarse) verificado POR CONTENIDO: el digest de los digests, que no revela nada y caza lo que contar ficheros no caza — una llave truncada cuenta como un fichero igual que la entera. Probado en los dos sentidos: idéntico ⇒ 0, un byte distinto ⇒ 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
589efee4f8 |
atuq: en la imagen el navegador SÍ pinta — la segunda pantalla no era
La sospecha salía del propio serial: el arranque del §6.10.ter veía DOS dispositivos DRM
(`drm: card0 card1 renderD128`) y uno nuevo ve uno solo. Sin `-vga none`, QEMU agrega una VGA
estándar además del virtio-gpu, OVMF pinta su GOP ahí y el kernel levanta simpledrm encima: un
compositor con dos tarjetas puede componer en la que el `screendump` no muestra, y eso se ve igual
que «la ventana no aparece». Por eso el guion lleva `VGA=1`, para pedir ese caso por su nombre.
Medido con las dos configuraciones y el mismo lanzamiento:
-vga none → card0 → 703766 px magenta
VGA=1 → card0 card1 → 703766 px magenta
El mismo número al píxel. La ventana aparece —panel, dock y el navegador con su barra lateral—,
`nsWindow::Create() Toplevel` está en el MOZ_LOG, la superficie se mapea y `mIsFullyOccluded 0`, con
`WaylandBufferSHM` de 1280x696, el mismo camino de buffers que bajo sway.
⇒ la segunda pantalla no es la causa. Queda una sola diferencia con aquella corrida: cómo se lanzó
el navegador. Acá va con perfil nuevo y MOZ_ENABLE_WAYLAND puesto; allá fue un `atuq` pelado con su
perfil por defecto tecleado en el serial. Para eso está `--como-usuario`, que es la corrida que sigue.
⚠ Dos trampas del arnés, medidas y escritas en el guion: el terminal hace ECO de lo que se le
escribe, así que una marca de fin literal se lee en el eco y las órdenes se pisan; y `cmd & ; echo`
es error de sintaxis en ash ⇒ el navegador no se lanzó y la corrida siguió dando capturas de un
escritorio vacío, que se leen igual que el fallo que se investiga.
|
||
|
|
d79299d351 |
granja: cuatro sondas quedaron ciegas tras el renombre — el informe decía «idle» compilando
Todas buscaban el proceso `release/hammer`, y desde el ADR 0016 el worker invoca
`./target/release/takana`. Ninguna daba error: simplemente no encontraban nada.
Medido hoy, no deducido: `estado-granja.sh` imprimía «moliendo: (nada — idle o entre
colas)» mientras en el worker corría
`./target/release/takana --store ./store build recipes/centrifugo.toml`. Leyendo ese
informe se concluye que la granja está seca. Tras el arreglo, el mismo informe dice
«moliendo: cilium-cli.toml» y `pgrep` allá confirma exactamente ese proceso.
Las cuatro:
· estado-granja.sh «moliendo» — mentía sobre si hay trabajo
· farm-worker-loop.sh×2 detección de build en vuelo
· deadman.sh `hay_trabajo` — y acá el precio es un worker BORRADO a mitad
de un build. En el LXC no llegó a morder (sólo borra cajas
hcloud y su timer no está activo allá), pero la próxima caja
de pago sí lo habría pagado.
Se aceptan LOS DOS nombres: cargo sigue compilando `hammer` como alias, y una sonda que
sólo mira el nombre nuevo se rompería con un worker que arrastre binario viejo — que es
justo lo que pasa acá, porque la siembra excluye `/target`.
Es el mismo renombre que dejó colgado el enlace de `go` (commit anterior). Un renombre
no rompe sólo lo que compila: rompe las cadenas que alguien escribió a mano.
|
||
|
|
756ed9c6a6 |
atuq: el compositor NO era la causa — con cosmic-comp nesteado el navegador pinta
El §6.10.ter dejó dos variables cambiando a la vez (compositor y arranque real) y ninguna medición que las separe. `scripts/cosmic/atuq-en-cosmic.sh` saca la primera de encima en ~2 min por vuelta, sin QEMU y sin imagen: sway headless de andamio, cosmic-comp nesteado encima por winit, atuq adentro, y `grim` capturando del lado de sway. El `--control` corre EL MISMO binario de atuq —el del cierre hidratado para la imagen— directo sobre sway. Las dos capturas coinciden al píxel en la región de la página (586331 px magenta, mismo bbox), y no es que el navegador se escape a sway: antes de lanzarlo, la ventana de cosmic-comp ocupa 1276x637 en ese mismo rectángulo, y el MOZ_LOG del widget ve `mode output size 1276 x 637` con cosmic contra `1280 x 720` en el control. Las dos corridas enteras difieren en 1130 píxeles, todos en las barras de título. ⇒ cosmic-comp no es el que impide la ventana. Queda como variable el arranque de verdad: kms/DRM sobre virtio-gpu en vez de winit, el PID1 de arje, el seat. De paso queda anotado el camino de buffers cuando SÍ funciona —`WaylandBufferSHM`, memoria compartida y no dmabuf—, que es la línea de base contra la que comparar la traza de la imagen. Y `scripts/cosmic/atuq-en-imagen.py` para medir allá: arranca la imagen, maneja el serial, lanza el navegador con el mismo MOZ_LOG y pide capturas por QMP. La corrida del §6.10.ter fue a mano y no dejó un solo log que se pueda releer. |
||
|
|
d570a8e03f |
granja: el worker llevaba 5 días sin poder construir NINGUNA receta Go, en silencio
El enlace `~/.cargo/bin/go` del worker apuntaba a `/opt/hammer/.dev-fs/tools/go/bin/go` — la ruta de ANTES del renombre a takana (ADR 0016, 2026-09-09). `/opt/hammer` ya no existe, así que el enlace estaba COLGADO desde entonces. Nada falló. `go` lo invoca takana DEL LADO DEL HOST (`go mod vendor` durante el fetch, con red), fuera del sandbox, así que el rootfs no lo cubre — es el mismo modo de fallo que `patch` en agosto, y `vps-setup.sh` ya lo tiene escrito como regla: «toda herramienta que takana invoque HOST-SIDE va en esta lista». `go` no estaba. Cómo se descubrió: por casualidad, verificando reproducibilidad. 16 de 25 recetas Go murieron en SEGUNDOS. Un fallo sistémico se lee como 16 recetas rotas si nadie abre el error — el verificador manda la salida del build a /dev/null. El error era `spawn go mod vendor: No such file or directory (¿está `go` en el PATH del host?)`, que lo decía todo. Arreglado en el worker (enlaces repuestos a /opt/takana) y COMPROBADO con un build real: `gron` no construía, ahora construye y reproduce bit a bit (why-differs: 5 entradas idénticas, 0 divergen). Acá va la parte que evita la repetición: el script daba «go enlazado ✓» sin comprobar nada — `ln -sfn` crea un enlace colgado tan campante. Ahora repone un enlace colgado preexistente (avisando) y comprueba que RESUELVE ejecutando `go version` A TRAVÉS DE ÉL; si no ejecuta, sale 1 con el síntoma escrito. Un enlace que existe no es un `go` que corre. Probado en los tres sentidos, con control que TIENE que pasar: · enlace sano ⇒ ok, rc=0 · enlace colgado a /opt/hammer ⇒ avisa, lo repone, rc=0 · destino inexistente ⇒ falla ruidoso, rc=1 |
||
|
|
3d79cdf98b |
SDD 30 4c: GNOME con los demonios supervisados por arje — y el control arregló colord
Los demonios de sistema pasan de lanzarse con `&` desde un script de 500 líneas a ser Cards del `genesis`. El bloqueo que este frente daba por corpus se levantó solo: otro agente selló evolution-data-server y gnome-shell mientras esto se escribía, y escritorio-gnome quedó 309/309. SE CORRIÓ UN CONTROL PRIMERO, y es lo único que hace interpretable el resultado: colord control: `MURIÓ al arrancar` cards: `ya vive (pid 175)` ColorManager control: `NO apareció en 40s` cards: `OK` login1/Accounts/UPower OK en los dos compositor wayland-0 y shell vivo en los dos El fallo del control DESAPARECIÓ, y no lo buscaba: colord moría arrancado por el script y vive arrancado por arje, con el bus ya listo porque la espera está dentro de su argv. Sin el control, «ColorManager OK» sería un dato suelto en vez de una diferencia. Los PIDs lo confirman: polkit=119, colord=175, upowerd=178, accounts=180 — de antes de que el lanzador de sesión existiera. QUÉ NO SE COMPROBÓ: no hay screendump; QEMU salió por timeout y el control tampoco lo tuvo. La comparación es serial contra serial y lo que se afirma es sobre los DEMONIOS, no sobre el pintado. Las piezas donde corresponde: `takana service-cards` (UNA sola implementación de receta→Card; el formato es contrato con card_core::Card), `targets.py --service-paths` (une qué-es con si-arranca), y un inyector en FICHERO APARTE porque anidar dos heredocs de python falló en vivo — el terminador del interno cerró el externo y media cosa corrió como shell. La espera del bus va DENTRO del argv de las 5 recetas de sistema: sin ella un daemon arranca antes de que dbus escuche y queda en modo idle sin registrar su nombre — un fallo que no se ve, porque el proceso vive y el bus no lo tiene. Los 5 hashes intactos. Y el guardia de gnome-start es por «¿está corriendo?», no por una perilla: así es correcto venga de donde venga el proceso y la misma copia sirve donde no se inyectaron cards. |
||
|
|
60202bf526 |
imagen: el navegador arranca en la imagen booteada y NO PINTA VENTANA — medido, con lo descartado
Primera prueba de `atuq` dentro de una imagen de disco arrancada, y el resultado no es el de la jaula. La cadena previa funcionó, y eso también es medición: el perfil `escritorio-cosmic` hidratado con el cierre de hoy (275 nodos, 8,4 G) trae `/usr/bin/atuq`, `/usr/bin/llama-server` y el modelo — declarar en el perfil SÍ pone las cosas en la imagen—, la imagen EFI de 12 G arranca en QEMU y COSMIC pinta panel y dock en ~2 min. Y `atuq` arranca —sus extensiones inician, la del foco sondea cada minuto, WebRender inicializa— sin que la ventana aparezca nunca. Descartado, para que nadie lo repita: no es el sandbox de Gecko (relanzado con los cinco MOZ_DISABLE_* puestos, idéntico), no es que el proceso muera (sigue ejecutando el JS de las extensiones), y no es la IA (pasa con about:blank). La pista: bajo sway headless el MISMO artefacto pinta perfecto y hay capturas del panel contestando. La diferencia son el compositor (cosmic-comp+llvmpipe contra sway+pixman) y el arranque real contra bwrap. Es su propia unidad de trabajo, no un parche apurado. ⚠ La lección del método: quince guardianes en verde, `vigia-imagen.py` en ✓, y el navegador igual no se puede usar en la imagen. «Sella», «hidrata» y «los tests pasan» son tres cosas distintas de «arranca y se ve». De paso, `metal-iso.sh` acepta ahora STORE y AUG por entorno, porque en esta máquina no funcionaba: arma el rootfs con `cp -al` desde el store y el store es un BIND-MOUNT del volumen — `linkat` rechaza cruzar mounts aunque sea el mismo disco, así que hay que nombrar los dos lados dentro del mismo mount. Es la cuarta vez que aparece el mismo EXDEV hoy. |
||
|
|
5e056b6177 |
atuq: una CAPTURA de la barra lateral — y la barra ya no se abre sola
`scripts/atuq-captura-ia.py`: el arnés de sway headless de los guardianes + `grim` + un PNG. No es un guardián (los veredictos siguen saliendo del `dump`); es la evidencia que ningún log puede dar: que el panel se pinta y que la respuesta está ahí. Encontró tres cosas, ninguna visible en un log: 1. **la barra lateral se abría SOLA** en el primer arranque, ocupando un tercio de la ventana. Gecko lo hace al instalar una extensión con `sidebar_action`, y para un navegador que la trae de fábrica eso es imponerle un panel a todo el mundo. `"open_at_install": false`, con captura del después; 2. el diálogo «Close Firefox» en la segunda corrida — el arnés mataba el navegador sin despedirse y quedaba el `.parentlock`. Arreglado en el arnés; 3. dos barras de notificación VACÍAS en el arranque. El atajo era reportarlas como fuga de marca, y era falso: instrumentando una copia del artefacto para volcar el DOM salieron `sandbox-content-disabled` (lo apaga el propio arnés) y `startup-restore-session-suggestion` (por el cierre abrupto anterior). El texto está en el DOM y no en los píxeles — comprobado además quitando nuestro CSS, con el mismo resultado ⇒ es el render por software de la jaula. ⇒ Una captura muestra síntomas; el DOM dice de quién son. Sin ese segundo paso, dos de los tres habrían entrado al SDD como bugs nuestros. El log del navegador viaja junto a la foto, siempre: una captura muestra que algo se ve raro y no por qué. Y `test-atuq-ia.py` + `test-atuq-archivo-semantico.py` siguen verdes tras el cambio. |
||
|
|
5e5369f775 |
libffi-shared en base: python3 volvía a ser INERTE en servidor, cli y base — y el vigía no miraba ahí
`vigia-sonames.py` corría por defecto SÓLO sobre los cinco perfiles de escritorio
(`p.startswith("escritorio")`). O sea que `base`, `cli` y `servidor` —el objetivo de la mudanza—
**nunca se miraron**. Apuntándolo a ellos a mano:
== servidor 147 nodos · 183 sonames provistos · 1 sin proveedor
FALTA libffi.so.8 ← lo piden: python3
`python3` no arrancaba en las tres imágenes, por el mecanismo EXACTO que `musl-shared` y
`zlib-shared` arreglaron dos días antes: la receta canónica de libffi es `--disable-shared`, ningún
artefacto del cierre publica ese soname, y dentro del lab el agujero lo tapa el rootfs de Alpine —
así que el build pasa y la imagen sale rota. `libffi-shared` ya existía en el corpus y ya estaba
declarada en otros dos perfiles; sólo faltaba en `base`.
servidor 148 nodos · 186 sonames · **0 sin proveedor** (base y cli, igual)
## Y el vigía pasa a mirar TODOS los perfiles
⚠ **Es la tercera vez en dos días que un guardián de este repo mide menos de lo que su resumen
afirma**: `static-audit.sh` sólo globeaba `recipes/` y no las 5 colas (3 mentiras ocultas); el bloque
de raíces sucias sólo corría al hidratar un perfil y no veía las 649 recetas fuera de toda imagen
(qdrant con 69 M de basura); y éste dejaba fuera medio sistema. **Un «0 huecos» sobre la mitad del
sistema se imprime igual que uno sobre todo él.**
⇒ Al leer un guardián, leer su SCOPE antes que su veredicto: el glob, el bucle, de dónde saca la
lista. Los ocho perfiles dan 0 sin proveedor, así que ampliar el alcance no mete ruido — sólo deja de
esconder.
|
||
|
|
1fc9de6a01 |
espejar-repos: mostrar el error de gh y cortar ante el límite de la API
Corrida real contra gioser: los 21 repos fallaron al crearse y el guión dijo «no se pudo crear» A SECAS, porque mandaba el stderr de `gh` a /dev/null. La causa era el LÍMITE DE LA API DE GITHUB agotado —que se resuelve esperando— y sin el mensaje eso parecía un problema de permisos, de nombre o del propio guión: hubo que reproducirlo a mano para enterarse de algo que la herramienta ya sabía. Dos arreglos: · El error de `gh` se muestra, recortado a su primera línea. · Ante «rate limit» se CORTA en el primero: los 20 restantes van a fallar igual y cada intento gasta cuota. Dice a qué hora se repone (leído de `gh api rate_limit`) y recuerda que reintentar es seguro, porque el guión es idempotente. No quedó estado parcial de la corrida fallida: el `pushurl` se agrega DESPUÉS de crear el repo, así que los 21 clones siguen con su remoto original intacto. Verificado en tres de ellos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
df1230d2a5 |
vigía de subcomandos: separar el hueco CONOCIDO del NUEVO — «44 inertes» todos los días no se lee
El vigía escribe `docs/state/subcomandos.txt` en cada ciclo del latido desde ayer, y decía
`TOTAL: 44 herramientas selladas que no se pueden invocar` a secas. Un número grande, constante y sin
explicación al lado entrena a saltearse el informe — y entonces el hueco NUEVO, que es el único que
pide acción, pasa desapercibido entre el ruido del viejo. Es la misma trampa que ya tienen resuelta
`static-audit.sh` (deuda decidida) y `--auditar-raices`.
Los 44 son todos del mismo hueco, y ahora el informe dice cuál y por qué:
• falta el binario `cargo` (el toolchain de Rust)
PENDIENTE DE DECISIÓN, no olvido. `cargo`/`rustc` viven hoy en el LAB y en la cadena de
selfhost (mrustc→1.91.1), no como receta del catálogo. Empaquetarlos es una decisión de
TOOLCHAIN —qué Rust publica la distro, y desde qué cadena— no trabajo de granja.
TOTAL: 44 … (44 de hueco CONOCIDO, 0 NUEVAS) ⇒ exit 0
⚠ **«PENDIENTE DE DECISIÓN» y no «deuda decidida»**, que es distinto: en el audit estático las tres
mentiras tienen un arreglo evaluado y descartado por su coste; acá nadie decidió nada todavía. La
etiqueta tiene que decir cuál de las dos cosas es, o dentro de tres meses se lee como cerrada.
El código de salida ahora refleja sólo los huecos NUEVOS. Probado con los dos controles: sacando
`cargo` de la tabla salen `44 NUEVAS` y exit 1; restaurándola, `0 NUEVAS` y exit 0.
⚠ Y el primer intento de ese control estaba MAL y daba verde: escribí `CONOCIDOS = {} or {…}` para
vaciar la tabla, y en Python `{}` es falsy ⇒ la expresión devuelve el segundo dict y la tabla seguía
llena. El control «pasaba» sin probar nada. Rehecho renombrando la clave.
|
||
|
|
713128f26d |
mudanza: guión que le da copia FUERA a los 25 repos que sólo existen en gioser
Hace por un árbol entero lo que `scripts/espejo-setup.sh` hace por takana: `pushurl` doble sobre `origin`, de modo que cualquier `git push origin` que ya exista empiece a espejar sin tocar ningún script. Crea el repo en GitHub como PRIVADO. Cuatro decisiones: · **Por defecto NO hace nada**: imprime qué haría; hay que pedirlo con `--apply`. Crear repos y empujar código a un servicio externo no es algo que deba pasar por correr un script sin leerlo. · **Varios clones del MISMO origen van a UN repo remoto.** En gioser hay cinco de `tawasuyu/tawasuyu`; cinco repos distintos multiplican la confusión y empujarlos todos al mismo `main` los haría chocar. El primario empuja normal; un secundario sólo se empuja —bajo `refs/heads/clon/<nombre>/*`— SI aporta historia propia, y eso se comprueba preguntándole al primario si ya tiene esos objetos (`cat-file -e`). Medido: los cinco tienen CERO commits que no estén en su remoto, así que los cuatro secundarios se saltan y se dice por qué. · **Sólo agrega; nunca toca el `fetch` de `origin`**, que sigue siendo el canónico. · **Lo sucio no viaja, y se dice.** Un espejo guarda historia commiteada, no el árbol de trabajo, y `tawasuyu` tiene 980 ficheros sin commitear. Creer que está a salvo cuando la mitad del trabajo no está commiteado es justo el fallo que este guión existe para evitar. ⚠ Y un criterio que escribí mal y corregí midiendo: el primario era «el clon con más commits alcanzables», y contra los cinco de tawasuyu dio un EMPATE EXACTO en 9343 — `rev-list --all` cuenta las refs de `origin`, que los cinco comparten, así que la métrica estaba dominada por lo común y no medía nada. El desempate quedaba en el orden del `find` y elegía `tw-deploy` (HEAD del 6/9) sobre `tawasuyu` (del 12/9), que es el que se está trabajando. Ahora manda la fecha del HEAD en ISO, que distingue y ordena como texto. Ensayo sobre gioser: 25 repos sin copia fuera ⇒ 21 a espejar, 4 saltados por redundantes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
01e721db84 |
verificar-repro: decir que además es el CENSO de la deuda invisible, con el número medido
`no construyó` no es ruido del verificador: es EL hallazgo. Un `sealed` en el grafo dice que alguien construyó eso alguna vez con algún lab, no que se construya hoy — y el lab rueda desde Alpine edge. Este script es lo único que convierte esa sospecha en un número SIN RIESGO, porque aparta en vez de borrar y restaura si el build falla. Barriendo las 15 recetas CMake baratas del corpus: REPRODUCEN 9 · DERIVA 1 · no construyeron 5. Cinco selladas y rotas a la vez, todas por el mismo crash del lld de zig con `--dependency-file`. Y la nota de método que hace legible el censo: barrer por FAMILIA de sistema de build. Si el fallo es del toolchain se concentra en una familia y el patrón salta; barrer al azar lo diluye. |
||
|
|
eb85c5976e |
latido: el vigía de servicios entra al cron — 18 demonios se embarcan y nadie los arranca
Contesta la pregunta que ni el grafo ni `vigia-sonames` contestan: no «¿está sellado?» ni «¿arranca el binario?» sino **«¿hay alguien que lo LEVANTE?»**. Un demonio puede estar sellado, con contenido, con todos sus sonames resueltos, y que ninguna imagen lo arranque nunca. Va al latido por la lección que `vigia-sonames` ya dejó escrita doce líneas más arriba —un vigía que hay que acordarse de invocar no se distingue de no tenerlo— y con más motivo: su entrada son DOS ficheros que cambian por separado (las recetas y `targets.toml`), así que la divergencia entre «declarado» y «habilitado» aparece sola, sin que nadie toque el vigía. Corre PRIMERO su propio `--selftest`: si el guardián está roto, su silencio no es una respuesta. Primera lectura, ya en el repo: 0 errores y 18 avisos. El más ruidoso es `dbus-system`, que viaja en SEIS perfiles y sólo GNOME lo arranca. |
||
|
|
1b56b16402 |
SDD 30 §4a+§4c: los 9 demonios de GNOME declarados — y aparecieron dos que no estaban en NINGÚN perfil
Lo que el script de sesión lanza con `&` ahora está declarado en las recetas y habilitado en el perfil. Ninguna receta movió su hash: 9/9 idénticos a los que los grafos ya registraban. EL HALLAZGO, y no lo buscaba: la comprobación inversa del resolutor rechazó `arje-logind-compat` y `arje-polkit-compat` porque están en CERO perfiles — y sin embargo qemu-desktop-image.sh los copia al rootfs a mano y el de COSMIC hace `exit 1` si falta logind-compat. Dos binarios imprescindibles, presentes en la imagen y ausentes del destino declarado: la misma forma del agujero de `foot`, encontrada por una comprobación en vez de por una imagen inusable. Son raíces de escritorio-gnome (los dos) y de escritorio-cosmic (sólo logind, verificado que sus scripts no nombran polkit). DOS COSAS QUE NO SON TRANSCRIPCIÓN: - `dbus-daemon --fork` no se traduce tal cual: arje supervisa al HIJO DIRECTO y Type=forking no existe, así que un daemon que forkea y sale deja a arje viendo morir al padre con éxito y reencarnándolo para siempre. La card usa --nofork. - `scope = system|session` decide DÓNDE va la card. Las de sesión necesitan XDG_RUNTIME_DIR y usuario logueado; en el genesis arrancarían antes de que exista ninguno. Y fuera de mirada NADIE entrega cards de sesión todavía, así que salen con AVISO: el hueco queda contado, no omitido. Correcciones propias: la unicidad del label es DENTRO del perfil, no del corpus (upower vive legítimamente en dos colas); la membresía se lee de los CINCO grafos, no sólo el del corpus; una RAÍZ manda sobre el grafo, que es derivado y lo regenera el cron; y la flag nace en inglés (`--services`) como manda la regla 4, aunque `--lista` sea deuda vieja del mismo fichero. `--selftest`: 7 casos, el primero es el CONTROL que tiene que pasar en verde. |