tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1506Shell 193Rust 108Markdown 983676 Definitions

atuq: la bóveda ENTREGA una contraseña, y sólo cuando alguien dice que sí — las seis etapas en verde

    A ✓ sin la app dueña, el host contesta locked:true — y con ok:true, que es la trampa
    B ✓ con la app, vault.status dice ABIERTA: el socket sube en 1-2 s
    C ✓ el diálogo ABRE en el compositor y se lo CONTESTA: «1» ⇒ yes, «2» ⇒ no
    D ✓ vault.save con consentimiento real guarda, y vault.match la encuentra SIN contraseña
    E ✓ el navegador real, sobre una página servida por HTTP: la contraseña llega al CAMPO
    F ✓ contestando que NO, al campo no llega NADA

Dos corridas seguidas en verde, 6 min cada una. La unidad 12 del SDD queda CERRADA.

La E es la promesa entera del §6 y sola no probaría nada —una bóveda que entrega siempre se ve
idéntica a una que entrega con permiso—; por eso la F corre lo mismo diciendo que no. Y lo que se
mide no es lo que el host contesta sino lo que hay EN EL CAMPO: la página delata al servidor lo que
le pongan. Es la lección del §7.quater aplicada al otro extremo.

Tres fallos del ARNÉS, los tres disfrazados de fallo del producto:
· `wait` a secas esperaba también a la app de la bóveda ⇒ la jaula se comía su timeout y a la app la
  mataba un KILL, y con ella lo recién guardado (sled no alcanzaba a volcar). La etapa siguiente no
  encontraba la credencial: «la bóveda no guarda», el diagnóstico contrario al verdadero;
· el diálogo NO responde al teclado mientras la app de la bóveda inicializa su GPU — dos llimphi
  arrancando a la vez sobre render por software se pisan: doce teclas sin efecto y `denied` por
  timeout con la ventana abierta. Ahora se espera a que la app PINTE (12-13 s), no a su socket;
· una sola tecla no alcanza y el borde se mueve: el contestador insiste hasta que la ventana se va,
  que es la única señal que no depende de adivinar cuánto tarda.

El guardián entra ENTERO a la suite (era `--hasta A`): 20 en verde, ~38 min.

Lo que costó llegar: el guardián se escribió para medir una función y destapó tres piezas rotas,
NINGUNA en atuq — el dueño y el diálogo no estaban en el corpus, ninguna ventana llimphi podía pintar
sin Vulkan, y el dueño atendía de a un cliente. Las tres se veían igual desde el navegador: una
bóveda que no ofrece nada, sin un solo error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

4be4680310f6780dcf59e6381951dd6817d26a13 · by Sergio · (1 month ago) · parent 057332363 · browse the tree at this commit

Semantic delta · 3

scripts/test-atuq-boveda-metal.py

3 files · +87 −16

diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md
index dd0ac53e..c4203502 100644
--- a/docs/26-atuq-envoltorio-gecko.md
+++ b/docs/26-atuq-envoltorio-gecko.md
@@ -3264,6 +3264,59 @@ La mínima agrega sólo la arista que falta. La otra re-resuelve el workspace en
 control de siempre: `cargo metadata --locked` falla antes y pasa después. Publicado en `b80f7567c`,
 con índice temporal porque el `Cargo.lock` estaba `MM` en el clon compartido.
 
+#### 7.novies CERRADA: la bóveda entrega una contraseña, y sólo cuando alguien dice que sí (2026-09-18)
+
+Las seis etapas del guardián de metal, en verde, sobre artefactos vigentes:
+
+```
+A ✓ sin la app dueña, el host contesta locked:true — y con ok:true, que es la trampa
+B ✓ con la app, vault.status dice ABIERTA: el socket sube en 1-2 s
+C ✓ el diálogo ABRE en el compositor y se lo CONTESTA: «1» ⇒ yes, «2» ⇒ no
+D ✓ vault.save con consentimiento real guarda, y vault.match la encuentra SIN contraseña
+E ✓ el navegador real, sobre una página servida por HTTP: la contraseña llega al campo
+F ✓ contestando que NO, al campo no llega NADA
+```
+
+**Lo que la E afirma, y por qué la F es la mitad que la hace valer.** La E es la promesa entera del
+§6: un servidor de verdad sirve un formulario, `atuq` lo carga, se aprieta el botón de la extensión,
+`vault.fill` abre el diálogo, alguien dice que sí y la contraseña aparece en el campo. Sola no
+probaría nada: **una bóveda que entrega siempre se ve idéntica a una que entrega con permiso.** La F
+corre lo mismo contestando que no y exige que al campo no llegue nada.
+
+Y lo que mide no es lo que el host contesta sino **lo que hay en el campo**: la página tiene un
+guión que delata al servidor lo que le pongan. Es la lección del §7.quater —«el guardián leyó el
+disco, no la respuesta»— aplicada al otro extremo.
+
+##### Tres fallos del ARNÉS, y los tres se veían como fallos del producto
+
+Ninguno era de la bóveda, y los tres habrían quedado escritos como «la bóveda no anda»:
+
+1. **`wait` a secas esperaba también a la app de la bóveda**, que es un trabajo en segundo plano.
+   La jaula no cerraba nunca, se comía su timeout y a la app la mataba un KILL — y con ella se iba
+   lo recién guardado, porque `sled` no alcanzaba a volcar. La etapa siguiente no encontraba la
+   credencial: **«la bóveda no guarda», que es el diagnóstico contrario al verdadero.** `wait
+   "$contestador"`;
+2. **el diálogo no responde al teclado mientras la app de la bóveda está inicializando su GPU.**
+   Con render por software en cuatro núcleos, dos llimphi arrancando a la vez se pisan: la ventana
+   del diálogo entra en el árbol del compositor, toma el foco y se come doce teclas seguidas sin
+   efecto; `vault.save` vuelve `denied` por timeout **con la ventana todavía abierta**. Se espera a
+   que la app PINTE (aparezca en el árbol, 12-13 s), no sólo a que atienda el socket;
+3. **una sola tecla no alcanza y el borde se mueve**: con el mismo `wtype rc=0`, una corrida
+   contestaba y otra volvía `denied`. El contestador insiste hasta que la ventana se va, que es la
+   única señal que no depende de adivinar cuánto tarda en estar listo.
+
+⚠ Y uno más, del lado del chrome: **`WebExtensionPolicy` no es global en el scope de una ventana del
+navegador** —la sonda moría con `ReferenceError` y eso se leía como «la extensión no está»—. Sale
+del módulo `ExtensionParent`, que además la reexporta.
+
+##### Lo que costó llegar hasta acá, en fallos ajenos al navegador
+
+El guardián se escribió para medir una función y terminó destapando tres piezas rotas, **ninguna en
+`atuq`**: que el dueño de la bóveda y el diálogo no estaban en el corpus (§7.sexies), que ninguna app
+llimphi de escritorio podía pintar sin Vulkan (§7.septies), y que el dueño atendía de a un cliente
+(§7.octies). Las tres se veían igual desde el navegador: una bóveda que no ofrece nada, sin un solo
+error. Es el argumento entero a favor de medir en metal y no leer ficheros.
+
 ## 8. Plan, por unidades de trabajo
 
 Cada una cierra sola, se commitea y se pushea. El orden no es preferencia: cada una destraba a la
@@ -3291,7 +3344,7 @@ siguiente.
 | 9 | **Medios (6.6) ✅ y torrent (6.9) ✅ — 2026-09-10.** El medio lo abre `mpv`; el torrent lo toma `puriy-costura-torrent`, un daemon **propio, configurable y perezoso** que sobrevive al navegador y cosecha al CAS. Medido antes de decidir: la pila de librqbit compila para musl (226 crates), así que la decisión fue «no ahí» y no «no se puede». **Faltan** el foco (6.5) y la IA local (6.7), ésta bloqueada porque el corpus no tiene modelo ni embeddings | 6.5–6.7, 6.9 | 5 ✅ |
 | 10 | **Foco (6.5) — 2026-09-10.** Entra al corpus la cadena `nftables` (libmnl + libnftnl + nft 1.1.6), que el grafo pedía y nadie había puesto, con dos arreglos de fábrica: el sello de tiempo que rompía la reproducción y un bashismo. Medido con root: el cgroup en foco no sale y el de al lado sí. Y del lado del navegador, la extensión `foco` MUESTRA el estado y **no tiene verbo para apagarlo**. **Falta** quién pone a `atuq` en un cgroup (delegación, decisión de arje) y quién aplica la política (root) | 6.5 | 6 ✅ |
 | 11 | **Motor de inferencia local (6.7) — 2026-09-11.** Entra `recipes/llama-cpp.toml` (b10901, estática, 199 M, **REPRODUCE**), que resulta ser **un solo muro para dos pendientes**: el §6.7 y la mitad semántica del §6.3 no eran dos problemas, era que el corpus no tenía con qué correr un modelo (`pluma-llm` sólo tiene backends de nube; `rimay-verbo-fastembed` DESCARGA onnxruntime glibc + el modelo). ⚠ Y dejó medido lo que no se podía deducir: **`SOURCE_DATE_EPOCH` —la variable que nos da reproducibilidad— apagaba las SEIS perillas de ISA de ggml**, y el artefacto sellaba y corría con 0 `%ymm`. Ahora van declaradas y el `install` las comprueba. Guardián `scripts/test-llama-cpp.py` con control negativo vivo. **Faltan** el modelo (fuente pineada, no receta), los verbos del host y quién levanta el servidor | 6.7, y la mitad semántica de 6.3 | 5 ✅ |
-| 12 | **La bóveda (SDD-BOVEDA §8) — a medias, 2026-09-15.** La décima extensión: ofrece la credencial del sitio abierto y no puede sacar una contraseña por su cuenta —`vault.match` contesta títulos y usuarios; la contraseña sale por `vault.fill`, que **pregunta en el escritorio**—. La dirección la pone el chrome, nunca la página. El gestor de Gecko se aparta como VALOR DE ARRANQUE y no como política. **Destrabada el 2026-09-16** (§7.quinquies.bis): el `Cargo.lock` de tawasuyu publicado —y resultó ser byte a byte el que la otra sesión ya tenía sin commitear—, el pin subido a `23a292863` ⇒ `b3:7d63655a`, construido en el worker (el vendoreo son 2,4 G y el hub estaba al 99%) y medido con el mismo `strings` que había diagnosticado el hueco: `vault` de **0 a 10**, con `cas`/`sct` de control. **Falta**, y no era lo que este renglón decía (§7.sexies, 2026-09-18): el host sellado contesta `vault.status → locked:true` porque **el dueño de la bóveda y el diálogo de consentimiento no estaban en el corpus** — la función está apagada de fábrica en las cuatro imágenes. Entran `recipes/boveda.toml` y `recipes/shuma-pregunta.toml`, **las dos SELLADAS el 2026-09-18** (`b3:59ffd74b` y `b3:99763eca`, construidas en el worker). ⚠ Y con eso apareció el muro de verdad, que no es de la bóveda: **ninguna app llimphi de escritorio abre ventana en esta distro** porque no hay Vulkan en ninguna imagen y el camino GL de llimphi arma la instancia sin display handle (§7.septies). El guardián de metal existe —`scripts/test-atuq-boveda-metal.py`, seis etapas— y hoy llega hasta la A. La B se destraba en llimphi, no acá | el gestor de contraseñas de la suite dentro del navegador | 5 ✅ |
+| 12 | **La bóveda (SDD-BOVEDA §8) — a medias, 2026-09-15.** La décima extensión: ofrece la credencial del sitio abierto y no puede sacar una contraseña por su cuenta —`vault.match` contesta títulos y usuarios; la contraseña sale por `vault.fill`, que **pregunta en el escritorio**—. La dirección la pone el chrome, nunca la página. El gestor de Gecko se aparta como VALOR DE ARRANQUE y no como política. **Destrabada el 2026-09-16** (§7.quinquies.bis): el `Cargo.lock` de tawasuyu publicado —y resultó ser byte a byte el que la otra sesión ya tenía sin commitear—, el pin subido a `23a292863` ⇒ `b3:7d63655a`, construido en el worker (el vendoreo son 2,4 G y el hub estaba al 99%) y medido con el mismo `strings` que había diagnosticado el hueco: `vault` de **0 a 10**, con `cas`/`sct` de control. **Falta**, y no era lo que este renglón decía (§7.sexies, 2026-09-18): el host sellado contesta `vault.status → locked:true` porque **el dueño de la bóveda y el diálogo de consentimiento no estaban en el corpus** — la función está apagada de fábrica en las cuatro imágenes. Entran `recipes/boveda.toml` y `recipes/shuma-pregunta.toml`, **las dos SELLADAS el 2026-09-18** (`b3:59ffd74b` y `b3:99763eca`, construidas en el worker). ⚠ Y con eso apareció el muro de verdad, que no es de la bóveda: **ninguna app llimphi de escritorio abre ventana en esta distro** porque no hay Vulkan en ninguna imagen y el camino GL de llimphi arma la instancia sin display handle (§7.septies). **CERRADA el 2026-09-18** (§7.novies): el guardián de metal —`scripts/test-atuq-boveda-metal.py`— pasa sus **seis etapas**, con el navegador de verdad, el diálogo a la vista y el control de decir que NO. Para llegar hubo que arreglar **tres piezas ajenas al navegador**: el dueño y el diálogo no estaban en el corpus (§7.sexies), ninguna ventana llimphi podía pintar sin Vulkan (§7.septies, arreglado en llimphi) y el dueño atendía de a UN cliente, lo que con el navegador abierto dejaba la bóveda muda (§7.octies, arreglado en pacha con su test de regresión) | el gestor de contraseñas de la suite dentro del navegador | 5 ✅ |
 | 9.a | **Proxy por contenedor ✅ v0.5** — contenedores por política + extensión con `proxy.onRequest` | 6.8, y NO dependía de 5: es API de Firefox | — |
 
 Las unidades 2 y 4 son **paralelizables**: la toolchain no toca el chrome y el chrome no toca la
diff --git a/scripts/test-atuq-boveda-metal.py b/scripts/test-atuq-boveda-metal.py
index 233fa92f..b119b7b8 100644
--- a/scripts/test-atuq-boveda-metal.py
+++ b/scripts/test-atuq-boveda-metal.py
@@ -406,6 +406,18 @@ def guion_dentro(cuerpo, con_boveda, log="pasos.log", salida_rel="/salida"):
   [ -S "$XDG_RUNTIME_DIR/boveda.sock" ] \\
     && echo "@@ socket de la boveda LISTO en ${{i}}s" >> {salida_rel}/{log} \\
     || echo "@@ SIN socket de la boveda tras ${{i}}s" >> {salida_rel}/{log}
+  # ⚠ Y se espera además a que la app PINTE, no sólo a que atienda. El socket lo levanta un hilo
+  # antes de que haya ventana, así que seguir acá deja a la app inicializando su GPU al mismo
+  # tiempo que el diálogo inicializa la suya — y con render por software en cuatro núcleos eso se
+  # paga en el diálogo: aparece en el árbol del compositor, toma el foco y **no responde al
+  # teclado**. Medido: doce teclas seguidas sin efecto y `vault.save` volviendo `denied` por
+  # timeout, con la ventana todavía abierta.
+  j=0
+  while [ $j -lt 90 ]; do
+    /usr/bin/swaymsg -t get_tree -r 2>/dev/null | grep -q '"app_id": *"boveda"' && break
+    sleep 1; j=$((j+1))
+  done
+  echo "@@ ventana de la boveda a los ${{j}}s" >> {salida_rel}/{log}
 """
     return f"""#!/bin/sh
 export XDG_RUNTIME_DIR=/run/user/0 HOME={salida_rel}/hogar LANG=C XDG_DATA_DIRS=/usr/share
@@ -630,11 +642,12 @@ def correr_etapas(capas, dirs, salida, hasta, url, fallos):
   for par in si:%s:1 no:%s:2; do
     quien=${par%%%%:*}; resto=${par#*:}; tecla=${resto%%%%:*}; n=${resto#*:}
     ( python3 /salida/contestar.py "$tecla" 120 "pregunta|shuma" > /salida/contestador-C-$quien.log 2>&1 ) &
+    contestador=$!
     /usr/bin/shuma-pregunta --title "Prueba del instrumento" --detail "esto no toca la boveda" \
         --option "yes=Si, contesta" --option "no=No" \
         > /salida/dialogo-C-$quien.json 2> /salida/dialogo-C-$quien.err
     echo "@@ dialogo $quien salio con $? y dijo: $(cat /salida/dialogo-C-$quien.json)" >> /salida/pasos-C.log
-    wait
+    wait "$contestador"
   done
 """ % (TECLA_SI, TECLA_NO)
     correr_jaula(capas, salida, guion_dentro(cuerpo, con_boveda=False, log="pasos-C.log"), segundos=420)
@@ -659,8 +672,14 @@ def correr_etapas(capas, dirs, salida, hasta, url, fallos):
     print("\n== D · vault.save con el consentimiento dado, ¿guarda? ¿y después la encuentra?")
     cuerpo = """
   ( python3 /salida/contestar.py "%s" 150 "pregunta|shuma" > /salida/contestador-D.log 2>&1 ) &
+  contestador=$!
   python3 /salida/preguntar.py guardar >> /salida/pasos-D.log 2>&1
-  wait
+  # ⚠ `wait "$contestador"` y NO `wait` a secas: la app de la bóveda TAMBIÉN es un trabajo en
+  # segundo plano, así que un `wait` pelado no vuelve nunca, la jaula se come su timeout y a la
+  # app la mata un KILL. Y lo que se pierde ahí es lo que se acaba de guardar: `sled` no alcanza a
+  # volcar, la etapa siguiente no encuentra la credencial, y el cuadro que queda es «la bóveda no
+  # guardó» — el diagnóstico contrario al verdadero.
+  wait "$contestador"
 """ % TECLA_SI
     correr_jaula(capas, salida, guion_dentro(cuerpo, con_boveda=True, log="pasos-D.log"), segundos=420)
     print(indentar(leer(salida, "pasos-D.log")))
@@ -753,13 +772,14 @@ def correr_navegador(capas, dirs, salida, url, tecla, etapa):
   # El patrón del contestador NO es opcional acá: con el navegador abierto, teclear «la ventana
   # enfocada» le contestaría a la PÁGINA. Se busca la ventana del diálogo por app_id/título.
   ( python3 /salida/contestar.py "%s" 150 "pregunta|shuma" > /salida/contestador-%s.log 2>&1 ) &
+  contestador=$!
   export MOZ_ENABLE_WAYLAND=1 GDK_BACKEND=wayland
   # Los sandboxes de Gecko se apagan porque esto YA corre dentro de una jaula sin userns anidado:
   # con ellos puestos los procesos hijos mueren en bucle. Es el entorno de prueba, no el artefacto.
   export MOZ_DISABLE_CONTENT_SANDBOX=1 MOZ_DISABLE_RDD_SANDBOX=1 MOZ_DISABLE_UTILITY_SANDBOX=1
   timeout 150 /usr/bin/atuq --no-remote --profile /salida/perfil-%s '%s' >> /salida/atuq-%s.log 2>&1
   echo "@@ atuq salio con $?" >> /salida/pasos-%s.log
-  wait
+  wait "$contestador"
 """ % (tecla, etapa, etapa, url, etapa, etapa)
     correr_jaula(capas + [capa], salida, guion_dentro(cuerpo, con_boveda=True, log=f"pasos-{etapa}.log"),
                  segundos=420)
diff --git a/scripts/test-atuq-suite.sh b/scripts/test-atuq-suite.sh
index 43854eba..8fb345a9 100755
--- a/scripts/test-atuq-suite.sh
+++ b/scripts/test-atuq-suite.sh
@@ -15,10 +15,9 @@
 # Si falta el sello, esto NO corre nada: dice cómo construirlo y sale.
 #
 # ══ EL CONTROL DE ESTA CORRIDA ════════════════════════════════════════════════════════════════
-# Hoy (2026-09-18) el resultado CORRECTO es **20 en verde** — el vigésimo entró ese día y corre
-# TRUNCADO (`--hasta A`) a propósito: ver su renglón en la lista y el §7.septies. Un verde suyo
-# NO dice que la bóveda ande; dice que el host contesta lo que tiene que contestar sin dueño.
-# Lo fue «19 en verde» desde el 2026-09-16. Lo fue «17 y dos rojos» hasta esta
+# Hoy (2026-09-18) el resultado CORRECTO es **20 en verde**. El vigésimo entró ese día: primero
+# truncado —sólo su etapa A, porque las otras cinco chocaban con un muro ajeno (§7.septies)— y desde
+# esa misma tarde ENTERO, con sus seis etapas. Lo fue «19 en verde» desde el 2026-09-16. Lo fue «17 y dos rojos» hasta esta
 # tarde: los dos eran los verbos de la bóveda, y se apagaron al subir el pin de `puriy-costura`
 # (SDD 26 §7.quinquies.bis). Si mañana hay un rojo, cambió algo que hay que ir a ver.
 #
@@ -36,7 +35,8 @@
 # envejece callado.** Si el cuadro no coincide con esto, el sospechoso es esta cabecera.
 #
 # ══ LO QUE CUESTA ═════════════════════════════════════════════════════════════════════════════
-# **~34 min** en gioser, en serie (32,1 min medidos el 2026-09-16 sobre 19; el vigésimo suma ~2). Los cuatro primeros salen en 10 s y no tocan
+# **~38 min** en gioser, en serie (32,1 min medidos el 2026-09-16 sobre 19; el vigésimo suma 6,
+# medidos en dos corridas seguidas — levanta cuatro compositores y dos navegadores). 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`).
@@ -78,13 +78,11 @@ done
 GUARDIANES=(
   "vigia-atuq-verbos|python3 scripts/vigia-atuq-verbos.py|el host PINEADO atiende los verbos que mandan las diez extensiones (§7)"
   "test-atuq-boveda-coherente|python3 scripts/test-atuq-boveda-coherente.py|la bóveda está enchufada en los CINCO lugares (SDD-BOVEDA §8)"
-  # ⚠ CORRE TRUNCADO, `--hasta A`, y el motivo está en el §7.septies: `boveda` y `shuma-pregunta`
-  # están sellados y NO ABREN VENTANA —ninguna app llimphi de escritorio puede, porque no hay Vulkan
-  # en ninguna imagen y el camino GL de llimphi arma la instancia sin display handle—, así que las
-  # etapas B..F miden un muro ajeno a la bóveda. Lo que la A afirma es la medición del §7.sexies, que
-  # es la que costó tres semanas de no ver: **sin la app dueña, el host contesta `locked:true` con
-  # `ok:true`**. Cuando llimphi se destrabe, se saca el `--hasta A` y esta nota se va con él.
-  "test-atuq-boveda-metal|python3 scripts/test-atuq-boveda-metal.py --hasta A|sin el dueño de la bóveda, el host contesta CERRADA — y «cerrada» es un ok (§7.sexies)"
+  # Corría truncado (`--hasta A`) mientras el muro del §7.septies tapaba las otras cinco etapas.
+  # Desde el 2026-09-18 corre ENTERO: seis etapas, ~6 min, con el navegador de verdad, el diálogo de
+  # consentimiento a la vista y el control de decir que NO. Es el único de la suite que mide la
+  # cadena hasta el CAMPO del formulario y no hasta la respuesta del host.
+  "test-atuq-boveda-metal|python3 scripts/test-atuq-boveda-metal.py|la bóveda entrega la contraseña, y SÓLO con el sí de una persona (§7.novies)"
   "test-atuq-politica|python3 scripts/test-atuq-politica.py|el cruce XPI empaquetado ↔ política, con cinco roturas a propósito"
   "test-atuq-rootfs|python3 scripts/test-atuq-rootfs.py|el rootfs resuelve todos los NEEDED y nada se cae al LAB"
   "test-atuq-inicio|python3 scripts/test-atuq-inicio.py|la página de inicio y la pestaña nueva son las NUESTRAS (§4.e)"