diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md index b7979540..5b565795 100644 --- a/docs/26-atuq-envoltorio-gecko.md +++ b/docs/26-atuq-envoltorio-gecko.md @@ -2629,6 +2629,44 @@ el `BuildID` sea el que `SOURCE_DATE_EPOCH` obliga. **La regla que sale de todo el día:** cuando la duda es «¿llegó al artefacto?», la respuesta no está en el log del build ni en la receta — está **dentro del artefacto**, y hay que ir a buscarla ahí. +### 2.septies Los dieciocho guardianes, en una corrida — y el sello que se había quedado atrás (2026-09-15) + +Cada afirmación de este documento tiene quién la mida, y son **dieciocho guardianes**. Hasta hoy +ninguno corría solo: se corrían de a uno, el día que alguien tocaba esa parte. Ya se sabe cómo +termina eso —el de descargas estuvo ROJO dos días y nadie lo corrió (`29831e62`)—, así que la corrida +entera es ahora **un comando**: `scripts/test-atuq-suite.sh`. + +**Y lo primero que encontró fue un hueco que no era de ningún guardián, sino del ciclo.** Todo +`recipes/atuq/` es `source.dir`, así que el commit de la bóveda de esta mañana (unidad 12, +`6f0ba974`) movió el `ArtifactHash` — y **`atuq` quedó NO-SELLADO durante las nueve horas +siguientes**. En ese lapso ningún guardián podía medir nada: se niegan a correr contra un artefacto +que no sea el vigente, que es lo correcto. Pero un guardián que se niega **sólo grita cuando alguien +lo corre**, y nadie lo corría: el repo se veía sano, el commit estaba pusheado, y la única señal era +un `hash --check` que nadie tenía motivo para teclear. Por eso lo primero que hace el runner es mirar +el sello y NO correr nada si falta, diciendo el comando exacto para construirlo. Verificado con +control: contra un store vacío sale por ahí y devuelve 1. + +El re-sello costó **3,7 s** —`atuq` es derivado y el `firefox` vigente estaba en el store— y el ciclo +que manda es el de siempre: **editar todo → resellar UNA vez → medir**. + +**El cuadro, sobre `b3:e556024b` (gioser, en serie, 29 min):** + +| | | +|---|---| +| **16 en verde** | política, rootfs, inicio, chrome, nativo, foco, instalación, ruteo, archivo, descargas, medios, torrent, `sct`, códecs, IA y archivo semántico | +| **2 en ROJO** | `vigia-atuq-verbos` y `test-atuq-boveda-coherente`, **los dos por la misma causa**: los tres verbos de la bóveda (§7.quinquies) | + +O sea: **el commit de la bóveda no rompió nada más**. Los quince guardianes que no tienen nada que +ver con ella siguen verdes sobre el artefacto que la trae adentro, y eso es lo que hacía falta saber +antes de seguir — porque la unidad 12 tocó `atuq.cfg`, la política y el empaquetado de extensiones, +que es exactamente donde un cambio se lleva puesto algo de al lado sin decirlo. + +⚠ **Y el control del runner se corrigió solo en la primera corrida.** La cabecera anunciaba «17 en +verde y UN rojo» —predicción escrita antes de correr— y el cuadro dijo dos: el guardián de coherencia +de la bóveda lleva el mismo chequeo del cable adentro, que es justamente lo que el §7.quinquies le +pidió. El número quedó escrito en la cabecera **como control**: un cuadro entero en verde puede ser +un producto sano o un runner que no corre nada, y se ven igual. + ## 7. La costura: un host de native messaging en Rust Todo lo del §6 que no es CSS pasa por **un solo mecanismo**: un proceso Rust que habla native diff --git a/scripts/test-atuq-suite.sh b/scripts/test-atuq-suite.sh new file mode 100755 index 00000000..d60f10b3 --- /dev/null +++ b/scripts/test-atuq-suite.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# test-atuq-suite.sh — corre TODOS los guardianes de `atuq`, en orden de coste, y da un cuadro. +# +# ══ POR QUÉ EXISTE ════════════════════════════════════════════════════════════════════════════ +# Los guardianes de atuq son dieciocho y ninguno corre solo. Ya pasó lo que tenía que pasar: el de +# descargas estuvo ROJO dos días y nadie lo corrió (commit `29831e62`), y el 2026-09-15 el artefacto +# vigente estuvo SIN SELLAR desde el commit de la bóveda hasta que alguien miró — durante esas horas +# ningún guardián podía medir nada y el repo se veía igual de sano. Un guardián que nadie corre es +# documentación, no una medición. +# +# ══ LO PRIMERO QUE MIRA ES EL SELLO, Y NO ES UN DETALLE ═══════════════════════════════════════ +# Todo `recipes/atuq/` es `source.dir` ⇒ tocar una extensión, `atuq.cfg`, la política o el README +# cambia el ArtifactHash, y los guardianes se NIEGAN a medir un artefacto que no sea el vigente. El +# ciclo es **editar todo → resellar UNA vez → medir**; al revés se pagan 340 M de store por edición. +# Si falta el sello, esto NO corre nada: dice cómo construirlo y sale. +# +# ══ EL CONTROL DE ESTA CORRIDA ════════════════════════════════════════════════════════════════ +# Hoy (2026-09-15) el resultado CORRECTO es **16 en verde y DOS en rojo**: `vigia-atuq-verbos` y +# `test-atuq-boveda-coherente`, los dos por la MISMA causa —la receta de `puriy-costura` pinea un +# host anterior a los verbos de la bóveda, abierto y medido en el SDD 26 §7.quinquies—. **Si esto +# sale todo verde, lo roto es esto**: un cuadro entero en verde puede ser un producto sano o un +# runner que no corre nada, y se ven igual. +# +# ⚠ Esta cabecera decía «un rojo» hasta que la primera corrida la desmintió: el segundo guardián +# lleva el mismo chequeo del cable ADENTRO. Queda escrito porque es la trampa de siempre —predecir +# el resultado y no volver a mirarlo—, y porque el número es el control: si mañana hay UN rojo, o +# tres, cambió algo que hay que ir a ver. +# +# ══ LO QUE CUESTA ═════════════════════════════════════════════════════════════════════════════ +# **~29 min** en gioser, en serie (medido 2026-09-15). Los cuatro primeros salen en 10 s y no tocan +# el navegador: para «¿rompí algo del cruce política↔XPI?» alcanza con `--only` y no hace falta la +# media hora. Los caros son `sct` (241 s, seis cargas), `archivo-semantico` (227 s, embeddings), +# `instalacion` (180 s, tres sesiones) y `ia` (180 s, levanta `llama-server`). +# +# ══ QUÉ NO ESTÁ ACÁ ═══════════════════════════════════════════════════════════════════════════ +# `scripts/wlr/dunst-headless.sh --via-atuq` (una página web notifica y se DIBUJA): vive en el arnés +# de sway y se corre desde ahí. Se nombra para que su ausencia no se lea como cobertura. +# +# NO CONSTRUYE NADA y NO TOMA EL LOCK de la granja: sólo lee artefactos ya sellados, así que se puede +# correr con la granja a pleno. Va en SERIE a propósito — cada guardián levanta un navegador y esta +# máquina tiene cuatro núcleos sin swap; dos a la vez es un OOM. Y cada uno escribe en SU fichero: +# dos corridas sobre el mismo log se MEZCLAN y el resultado parece un pase. +# +# scripts/test-atuq-suite.sh # los dieciocho, en orden de coste +# scripts/test-atuq-suite.sh --list # qué afirma cada uno, sin correr nada +# scripts/test-atuq-suite.sh --only sct # sólo los que contengan «sct» en el nombre +# scripts/test-atuq-suite.sh --store DIR --logs DIR +set -u + +STORE="./store" +LOGS="work/atuq-suite" +SOLO="" +LISTAR=0 + +while [ $# -gt 0 ]; do + case "$1" in + --store) STORE="$2"; shift 2 ;; + --logs) LOGS="$2"; shift 2 ;; + --only) SOLO="$2"; shift 2 ;; + --list) LISTAR=1; shift ;; + -h|--help) sed -n '2,40p' "$0"; exit 0 ;; + *) printf 'no entiendo «%s» — ver --help\n' "$1" >&2; exit 2 ;; + esac +done + +# nombre|orden de coste implícito|qué AFIRMA. El orden es de barato a caro: lo que se rompe gratis +# se descubre en el primer minuto y no después de media hora de navegadores. +GUARDIANES=( + "vigia-atuq-verbos.py|el host PINEADO atiende los verbos que mandan las diez extensiones (§7)" + "test-atuq-boveda-coherente.py|la bóveda está enchufada en los CINCO lugares (SDD-BOVEDA §8)" + "test-atuq-politica.py|el cruce XPI empaquetado ↔ política, con cinco roturas a propósito" + "test-atuq-rootfs.py|el rootfs resuelve todos los NEEDED y nada se cae al LAB" + "test-atuq-inicio.py|la página de inicio y la pestaña nueva son las NUESTRAS (§4.e)" + "test-atuq-chrome.py|el JS de atuq.cfg llega al gBrowser, y la vista dividida (§4.g)" + "test-atuq-nativo.py|una extensión habla con un proceso NATIVO y le vuelve la respuesta (§7)" + "test-atuq-foco.py|el navegador MUESTRA el foco del sistema y no lo apaga (§6.5)" + "test-atuq-instalacion.py|quién instala de verdad las extensiones: el ESCANEO, no la política" + "test-atuq-ruteo.py|el contenedor sale por SU proxy — saludo SOCKS5 (§6.8)" + "test-atuq-archivo.py|congelar lo leído al CAS y buscarlo; nunca lo privado (§6.3)" + "test-atuq-descargas.py|la descarga entra al CAS con su BLAKE3 y el fichero no se toca (§6.2)" + "test-atuq-medios.py|el medio lo abre mpv, no una pestaña (§6.6)" + "test-atuq-torrent.py|magnet → daemon propio que sobrevive al navegador (§6.9)" + "test-atuq-sct.py|la cadena de sct entera: seis cargas y un servidor HTTP real (§6.1)" + "test-atuq-codecs.sh|H.264+AAC · VP9+Opus · AV1 · MP3 · FLAC REPRODUCEN (§4.f)" + "test-atuq-ia.py|la barra lateral le pregunta a un modelo de ESTA máquina (§6.7)" + "test-atuq-archivo-semantico.py|el archivo contesta por PARECIDO, no por palabra exacta (§6.3)" +) + +if [ "$LISTAR" = 1 ]; then + printf '── los guardianes de atuq, de barato a caro ──\n' + for g in "${GUARDIANES[@]}"; do printf ' %-32s %s\n' "${g%%|*}" "${g#*|}"; done + printf '\n no está acá: scripts/wlr/dunst-headless.sh --via-atuq (vive en el arnés de sway)\n' + exit 0 +fi + +# ── el sello, antes que nada ────────────────────────────────────────────────────────────────── +H=$(./target/release/takana --store "$STORE" hash recipes/atuq.toml --check 2>/dev/null | tail -1) +case "$H" in + *SELLADO*) : ;; + *) printf 'no pude preguntar el hash de atuq (¿sin `target/release/takana`?)\n'; exit 2 ;; +esac +if [ "${H#* }" != "SELLADO" ]; then + printf '✗ el `atuq` VIGENTE no está sellado: %s\n' "${H%% *}" + printf ' Ningún guardián puede medir — se niegan, y con razón. Construilo UNA vez y volvé:\n\n' + printf ' flock -o work/.farm-build.lock ./target/release/takana --store %s build recipes/atuq.toml\n\n' "$STORE" + exit 1 +fi +printf '── los guardianes de atuq sobre %s ──\n' "${H%% *}" +mkdir -p "$LOGS" + +verdes=0; rojos=0; saltados=0 +RESUMEN=() +for g in "${GUARDIANES[@]}"; do + nombre="${g%%|*}"; afirma="${g#*|}" + case "$nombre" in *"$SOLO"*) : ;; *) continue ;; esac + log="$LOGS/${nombre%.*}.txt" + printf ' %-32s ' "$nombre" + t0=$(date +%s) + case "$nombre" in + *.sh) bash "scripts/$nombre" >"$log" 2>&1 ;; + *) python3 "scripts/$nombre" >"$log" 2>&1 ;; + esac + rc=$? + t=$(( $(date +%s) - t0 )) + # 77 es «no están las condiciones» (falta un rootfs, falta un modelo), no un fallo del producto. + case "$rc" in + 0) veredicto="✓ verde "; verdes=$((verdes+1)) ;; + 77) veredicto="— saltado"; saltados=$((saltados+1)) ;; + *) veredicto="✗ ROJO "; rojos=$((rojos+1)) ;; + esac + printf '%s %4ds %s\n' "$veredicto" "$t" "$afirma" + RESUMEN+=("$veredicto|$nombre|$t|$log") +done + +printf '\n' +if [ "$rojos" -gt 0 ]; then + printf 'los ROJOS, con su salida entera:\n' + for r in "${RESUMEN[@]}"; do + case "$r" in "✗"*) printf ' %s → %s\n' "$(echo "$r" | cut -d'|' -f2)" "$(echo "$r" | cut -d'|' -f4)" ;; esac + done + printf '\n' +fi +printf '%d verde(s) · %d rojo(s) · %d saltado(s) — salidas en %s/\n' "$verdes" "$rojos" "$saltados" "$LOGS" +[ "$rojos" -eq 0 ]