From 4be4680310f6780dcf59e6381951dd6817d26a13 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 18 Sep 2026 15:57:14 +0000 Subject: [PATCH] =?UTF-8?q?atuq:=20la=20b=C3=B3veda=20ENTREGA=20una=20cont?= =?UTF-8?q?rase=C3=B1a,=20y=20s=C3=B3lo=20cuando=20alguien=20dice=20que=20?= =?UTF-8?q?s=C3=AD=20=E2=80=94=20las=20seis=20etapas=20en=20verde?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/26-atuq-envoltorio-gecko.md | 55 ++++++++++++++++++++++++++++++- scripts/test-atuq-boveda-metal.py | 26 +++++++++++++-- scripts/test-atuq-suite.sh | 22 ++++++------- 3 files changed, 87 insertions(+), 16 deletions(-) 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)"