atuq: la bóveda estaba SELLADA y en ninguna imagen — y estar en la imagen no es poder abrirla

El §7.novies dio la función por cerrada: las seis etapas del guardián de metal en verde, con el
navegador de verdad y el diálogo a la vista. Lo que seguía abierto era la decisión 1 del §7.sexies
—«en qué imágenes se declaran»—, escrita como NO mientras ninguna app llimphi pudiera pintar. Ese
motivo se cayó el 2026-09-18, así que antes de tomarla se volvió a medir en vez de darla por sabida:

    atuq             sealed   perfiles=[cosmic, gnome, kde, sway]
    puriy-costura    sealed   perfiles=[cosmic, gnome, kde, sway]
    boveda           sealed   perfiles=[]
    shuma-pregunta   sealed   perfiles=[]

`sealed` con `perfiles: []` es sellado ≠ instalado: la lección de `foot`, que targets.toml repetía
QUINCE veces antes de hoy y que igual volvió a morder. Las dos entran a los cuatro perfiles de
escritorio, las dos o ninguna —sin el dueño `vault.match` no ofrece nada; sin el diálogo,
`Command::new` falla y TODO `vault.fill` se deniega—: media bóveda es una que niega todo en
silencio. ~43 M por imagen (22 M + 21 M medidos), contra los ~1,25 GiB que ya lleva el §6.7.

Y al declararlas apareció el hueco de una capa más arriba: la receta instalaba `/usr/bin/boveda` y
nada más, y los lanzadores de los cuatro escritorios leen `/usr/share/applications`. La app viajaría
en la imagen sin existir para quien la usa — la misma forma de fallo que esto viene persiguiendo.
Entra `boveda.desktop`, con tres cosas medidas antes de escribirlo:

· el icono existe: `dialog-password` está en breeze-icons (6), adwaita (1) y cosmic-icons (2). El
  cuarto perfil lleva sólo hicolor, que no trae iconos: ahí cae al genérico, que es degradarse;
· lo acepta el `desktop-file-validate` del store, con `atuq.desktop` de control. Deja un hint sobre
  `Security`, y las dos formas de callarlo lo cambian por uno PEOR (dos categorías principales ⇒ la
  app aparece dos veces en el menú). Se queda como está;
· ⚠ y lo que NO puede hacer: emparejar la ventana con el lanzador. `llimphi_ui::run` no llama nunca
  a `with_name` ⇒ winit no manda `set_app_id` y la ventana sale SIN app_id y con el título
  "llimphi". Por eso no hay `StartupWMClass`. Vale para toda app llimphi; se arregla en llimphi.

La receta se reconstruyó en el worker con la guarda del §7.quinquies puesta (`### receta verificada
3f1072cc` antes de compilar nada, porque el latido revierte la receta cada media hora y un acierto
de caché sobre la vieja imprime SELLADA en cero segundos): `b3:b0c6adc4` ⇒ `b3:3f1072cc`, 22 M, con
el árbol mirado por dentro y la entrada dentro del artefacto.

Y el guardián de coherencia pasa de CINCO lugares a SEIS: el sexto es `targets.toml` —quién DECLARA
al dueño en la imagen—, con control positivo (`atuq` tiene que estar, o el chequeo está leyendo el
campo equivocado) y su propio control negativo, el tercero. Probado en los dos sentidos: cuatro
perfiles en verde, y `--negative-control-perfil` en rojo.

Abierto, y dicho como lo que es: quién levanta la app con la sesión (atado a la decisión 2 del
§7.sexies, la raíz de las claves), y que el único proveedor de GL de las cuatro imágenes es iris
—mesa-llvmpipe en ningún perfil—, que la bóveda hereda y no agrega.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-09-21 01:30:57 +00:00
co-authored by Claude Opus 5
parent 35cc75be0c
commit e6ab5a5016
6 changed files with 326 additions and 14 deletions
+81 -7
View File
@@ -3,31 +3,38 @@
python3 scripts/test-atuq-boveda-coherente.py
python3 scripts/test-atuq-boveda-coherente.py --negative-control
python3 scripts/test-atuq-boveda-coherente.py --negative-control-perfil
── QUÉ MIRA, Y POR QUÉ ESTE GUARDIÁN EXISTE APARTE ───────────────────────────────────────────
Una extensión de `atuq` está enchufada en CINCO lugares distintos, y ninguno da error si falta:
Una extensión de `atuq` está enchufada en SEIS lugares distintos, y ninguno da error si falta:
manifest.json el id que la extensión declara
distribution/policies.json la política que la instala
native-messaging/*.json el permiso para hablarle al host
atuq.cfg las preferencias que la acompañan
recipes/puriy-costura.toml el COMMIT del host, que decide si sus verbos existen
docs/state/targets.toml quién DECLARA al dueño y al diálogo en las imágenes
Si falta la política, la extensión no se instala y el usuario ve un navegador sin la función. Si
falta el permiso del host, la extensión se instala y **se conecta a nada** —`connectNative` falla
en silencio y la insignia no aparece nunca—. Si faltan las preferencias, la bóveda y el gestor de
Gecko se pelean por el mismo campo. Y si el commit pineado del host es ANTERIOR a los verbos, la
extensión conecta, manda `vault.match` y recibe «verbo desconocido» —que `fondo.js` lee como «no hay
bóveda» y se calla—. Ninguno de los cuatro se ve como un error: se ven como «no anda».
bóveda» y se calla—. Y si `boveda` y `shuma-pregunta` no están declaradas en el perfil, la
extensión se instala, conecta, manda `vault.match` y el host contesta `{"ok":true,"locked":true}`
—«cerrada», que es una respuesta EXITOSA— porque del otro lado del socket no hay nadie: el dueño
está sellado y en ninguna imagen. Ninguno de los cinco se ve como un error: se ven como «no anda».
Éste NO reemplaza al guardián de metal (servidor HTTP, navegador real, login real, el diálogo de
consentimiento a la vista). Lo precede: mide lo que se puede medir sin construir nada, en un
segundo, y ahorra descubrir en metal un renglón que faltaba en un JSON.
── LOS DOS CONTROLES NEGATIVOS, UNO POR AFIRMACIÓN ──────────────────────────────────────────
── LOS TRES CONTROLES NEGATIVOS, UNO POR AFIRMACIÓN ──────────────────────────────────────────
`--negative-control` saca el permiso del host de una copia en memoria y exige que esto lo detecte.
`--negative-control-verbo` le manda al quinto chequeo un verbo inventado y exige lo mismo. Sin
ellos, un guardián que siempre dice que sí se vería idéntico a uno que funciona.
`--negative-control-verbo` le manda al quinto chequeo un verbo inventado y exige lo mismo.
`--negative-control-perfil` saca a `boveda` de los cuatro perfiles de escritorio, también en
memoria, y exige que el sexto lo vea. Sin ellos, un guardián que siempre dice que sí se vería
idéntico a uno que funciona.
Y el quinto chequeo trae además un control POSITIVO —`sct.observe`, que el host pineado tiene que
tener sí o sí—, porque es un `grep` que espera cero y un cero puede ser una ausencia o un sitio
@@ -38,6 +45,7 @@ import os
import re
import subprocess
import sys
import tomllib
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ATUQ = os.path.join(ROOT, "recipes/atuq")
@@ -151,6 +159,56 @@ def revisar_el_host(verbo_extra=None):
mal(f"el host pineado ({commit[:9]}) NO atiende {v}: la extensión lo manda y recibe "
f"«verbo desconocido», que fondo.js lee como «no hay bóveda» y se calla")
# ── 9. EL SEXTO LUGAR: ¿alguien DECLARA al dueño y al diálogo en las imágenes? ───────────────
# El quinto lugar pregunta si el host SABE los verbos. Éste pregunta lo de una capa más abajo, que
# es lo que costó tres semanas de no ver (SDD 26 §7.sexies): quién CONTESTA del otro lado del
# socket. El host no abre la bóveda nunca, a propósito —`sled` toma un lock exclusivo y el proceso
# que lanza Gecko muere con cada pestaña—, así que le habla a un DUEÑO; y el 2026-09-18 el dueño
# estaba SELLADO y en `perfiles: []`, o sea en ninguna imagen. Sellado ≠ instalado.
#
# Se mira `targets.toml` —el manifiesto de objetivo— y no `build-state.json`, que es su derivado:
# lo que decide qué va en la imagen es la declaración, y el estado se recalcula solo.
PERFILES_ESCRITORIO = ["escritorio-kde", "escritorio-gnome", "escritorio-cosmic", "escritorio-sway"]
# Las DOS o ninguna: media bóveda es una que niega todo en silencio. Sin el dueño, `vault.match` no
# ofrece nada; sin el diálogo, `Command::new` falla y TODO `vault.fill` se deniega.
LA_BOVEDA_EN_LA_IMAGEN = ["boveda", "shuma-pregunta"]
# El control POSITIVO, por lo mismo que el del §quinto lugar: un `in` que espera encontrar puede
# fallar porque la raíz falta o porque estoy leyendo el campo equivocado, y las dos se ven igual.
# `atuq` TIENE que estar en los cuatro — si no está, este chequeo no está midiendo el fichero.
RAIZ_CONTROL = "atuq"
TARGETS = os.path.join(ROOT, "docs/state/targets.toml")
def revisar_los_perfiles(sin_esta=None):
try:
with open(TARGETS, "rb") as f:
perfiles = tomllib.load(f)["perfil"]
except FileNotFoundError:
mal(f"no existe {os.path.relpath(TARGETS, ROOT)}: el sexto lugar queda SIN COMPROBAR")
return
except Exception as e: # TOML roto es un fallo ruidoso, no un «está bien»
mal(f"no se pudo leer {os.path.relpath(TARGETS, ROOT)} ({e})")
return
for perfil in PERFILES_ESCRITORIO:
if perfil not in perfiles:
mal(f"{perfil} no existe en targets.toml: ¿se renombró un perfil de escritorio?")
continue
raices = list(perfiles[perfil].get("paquetes", []))
if sin_esta:
raices = [r for r in raices if r != sin_esta]
if RAIZ_CONTROL not in raices:
mal(f"CONTROL ROTO: {perfil} no declara {RAIZ_CONTROL}. O el navegador se cayó de la "
f"imagen, o estoy leyendo el campo equivocado — y entonces lo de abajo no prueba nada")
continue
faltan = [r for r in LA_BOVEDA_EN_LA_IMAGEN if r not in raices]
if faltan:
mal(f"{perfil} NO declara {', '.join(faltan)}: la extensión se instala igual y la "
f"bóveda contesta `locked:true` con `ok:true` — apagada, y sin un solo error")
else:
bien(f"{perfil} declara el dueño y el diálogo ({RAIZ_CONTROL} de control)")
fallas = []
@@ -168,7 +226,7 @@ def leer(p):
return f.read()
def revisar(permisos_host, verbo_extra=None):
def revisar(permisos_host, verbo_extra=None, sin_perfil=None):
# 1. La extensión existe y declara el id que todos los demás nombran.
try:
m = json.loads(leer("extensions/boveda/manifest.json"))
@@ -232,10 +290,14 @@ def revisar(permisos_host, verbo_extra=None):
# 8. Y el quinto lugar, que vive en otra receta y en otro repo.
revisar_el_host(verbo_extra)
# 9. Y el sexto, que vive en el manifiesto de objetivo: quién la pone en la imagen.
revisar_los_perfiles(sin_perfil)
def main():
control = "--negative-control" in sys.argv
control_verbo = "--negative-control-verbo" in sys.argv
control_perfil = "--negative-control-perfil" in sys.argv
host = json.loads(leer("native-messaging/puriy_costura.json"))
permisos = list(host.get("allowed_extensions", []))
verbo_extra = None
@@ -247,9 +309,15 @@ def main():
# mirando el host sino contestando que sí.
verbo_extra = "vault.este_verbo_no_existe"
print(f"⚠ control negativo: se le agrega el verbo inventado {verbo_extra}")
sin_perfil = None
if control_perfil:
# El dueño, sacado de una copia en memoria de los cuatro perfiles: si el sexto chequeo no
# lo ve faltar, está contestando que sí en vez de mirar `targets.toml`.
sin_perfil = "boveda"
print(f"⚠ control negativo: se saca {sin_perfil} de los perfiles de escritorio")
print(f"── la bóveda en atuq ──")
revisar(permisos, verbo_extra)
revisar(permisos, verbo_extra, sin_perfil)
if control:
if any("allowed_extensions" in f for f in fallas):
@@ -263,6 +331,12 @@ def main():
return 0
print("\n✗ el control del verbo NO lo detectó: el quinto chequeo no mide nada")
return 1
if control_perfil:
if any(sin_perfil in f and "NO declara" in f for f in fallas):
print("\n✓ el control negativo del perfil lo detectó")
return 0
print("\n✗ el control del perfil NO lo detectó: el sexto chequeo no mide nada")
return 1
if fallas:
print(f"\n{len(fallas)} problema(s)")
return 1
+8 -5
View File
@@ -6,12 +6,15 @@
python3 scripts/test-atuq-boveda-metal.py --list # qué afirma cada etapa, sin correr nada
── POR QUÉ ESTE GUARDIÁN, Y POR QUÉ NO ALCANZA CON EL DE COHERENCIA ──────────────────────────
`test-atuq-boveda-coherente.py` mira los CINCO lugares donde una extensión se enchufa y tarda un
`test-atuq-boveda-coherente.py` mira los SEIS lugares donde una extensión se enchufa y tarda un
segundo. Es lo que hay que correr primero y no reemplaza a nada. Lo que no puede contestar es si del
otro lado del cable hay alguien: el 2026-09-18 los cinco lugares estaban en orden —manifiesto,
política, permiso, prefs y el commit del host con sus verbos— y la bóveda contestaba **cerrada** en
las cuatro imágenes, porque el DUEÑO de la base y el DIÁLOGO de consentimiento no estaban en el
corpus. Ningún guardián de ficheros podía verlo; éste empieza justamente por ahí.
otro lado del cable hay alguien: el 2026-09-18 los cinco lugares que entonces se miraban estaban
en orden —manifiesto, política, permiso, prefs y el commit del host con sus verbos— y la bóveda
contestaba **cerrada** en las cuatro imágenes, porque el DUEÑO de la base y el DIÁLOGO de
consentimiento no estaban en el corpus. Ningún guardián de FICHEROS podía verlo; éste empieza
justamente por ahí. (El sexto lugar nació de eso: desde el 2026-09-21 aquél mira además quién
DECLARA al dueño en la imagen, que es la mitad del §7.sexies que sí se puede ver sin construir
nada — y la que volvió a morder el 2026-09-21, con las dos recetas selladas y en `perfiles: []`.)
── LAS SEIS ETAPAS, Y QUÉ AFIRMA CADA UNA ────────────────────────────────────────────────────
A sin la app `boveda`, el host contesta `locked:true` ← el control, y es el bug del §7.sexies
+1 -1
View File
@@ -77,7 +77,7 @@ done
# se descubre en el primer minuto y no después de media hora de navegadores.
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)"
"test-atuq-boveda-coherente|python3 scripts/test-atuq-boveda-coherente.py|la bóveda está enchufada en los SEIS lugares (SDD-BOVEDA §8); el sexto es quién la DECLARA en la imagen (SDD 26 §7.decies)"
# 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