gioser

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 1572Shell 203Rust 109Markdown 993728 Definiciones

atuq: la bóveda entró a las imágenes y NADIE puede abrirla — y al navegador le servía una TEMPORAL

Seguir con lo que el §7.decies dejó abierto —quién levanta la app, de dónde sale la raíz de las
claves— terminó en una respuesta incómoda y en un bug que valía la pena encontrar.

La cadena, medida hacia atrás desde la app:

· `raiz_de_identidad()` saca la seed del llavero del KERNEL (`pacha_llavero::SEED_IDENTIDAD`);
· la escriben dos lugares en todo tawasuyu: `agora-cli identity unlock` y `churay-welcome-runner`;
· `agora-cli` está sellado y en `perfiles: []` — en ninguna imagen. Y aunque se declarara, su pin es
  del 2026-06-18, donde no existen ni `SEED_IDENTIDAD` ni `desbloquear` (git grep sobre el pin, con
  HEAD de control: 4 y 1 aciertos);
· `churay-welcome` no tiene receta en takana.

⇒ en ninguna imagen hay un binario capaz de sembrar la identidad. No es que el usuario no la
desbloqueó: es que no tiene con qué.

Y lo que el navegador veía NO era «cerrada». Cuando `abrir()` falla, la app levantaba el socket
igual, sobre `bóveda_imposible()` —un sled en /tmp/boveda-sin-abrir-<pid>—, así que la extensión
recibía `locked:false, count:0` y un `vault.save` consentido guardaba en un temporal que muere con
el proceso. La ventana decía la verdad; el navegador, lo contrario. Arreglado en tawasuyu
(`7917fbb96`) con test de regresión en los dos sentidos, y probado al revés: con el `if`
neutralizado, el test falla con el mensaje que corresponde.

Pin de las dos recetas a `6f0408d40`, que trae además el `Cargo.lock` cerrado. Ahí apareció la
vuelta que no estaba escrita: la «operación mínima» del §7.octies (`cargo metadata` sin `--locked`)
corrida en una jaula con el registro de cargo INCOMPLETO devuelve 0 y deja un lock al que le faltan
dos miembros del workspace — y su diff se ve MÁS mínimo que el correcto (−15 líneas contra +1). Que
diera byte a byte igual al de otra sesión no lo confirmaba: las dos se calcularon con el mismo
instrumento roto.

Y una trampa más, medida hoy: la guarda de receta del §7.quinquies.bis NO alcanza para una TANDA.
Puesta una vez al principio, `shuma-pregunta` selló bien y `boveda` —once minutos después— dio
«artefacto ya en el store» sobre la receta VIEJA, porque el latido revierte el árbol del worker en
el medio. La guarda va pegada a CADA build, y la cura de fondo es publicar la receta antes de
construir.

De paso, dos correcciones a lo que este mismo frente escribió hace unas horas:
· el `app_id`: llimphi SÍ lo pone (`with_name`, con el nombre del ejecutable de piso) ⇒ la ventana
  es `boveda`, igual que el basename del `.desktop`, y por eso no hace falta `StartupWMClass`. Lo
  que sí queda mal es el TÍTULO, que dice «llimphi»;
· el GL: que las imágenes sean iris-only no es una decisión pendiente sino una escrita (SDD 14), y
  el caso de la VM ya tiene camino — los runbooks montan `mesa-llvmpipe` como capa.

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

557e127f6f66879eef612db180b3ebce8d2ce9aa · por Sergio · (hace 1 mes) · padre c33de6001 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

3 archivos · +235 −45

diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md
index dae478bc..d9642585 100644
--- a/docs/26-atuq-envoltorio-gecko.md
+++ b/docs/26-atuq-envoltorio-gecko.md
@@ -3363,13 +3363,15 @@ Entra `boveda.desktop` en la fase `install` de la receta. Tres cosas se midieron
   dos formas que callan ese hint lo cambian por uno peor** —medido: `Utility;Security;System;` y
   `Utility;Security;Settings;` traen dos categorías principales ⇒ «application might appear more
   than once in the application menu»—. Se queda como está, que es además lo que usa KeePassXC;
-- **⚠ y la ventana NO se va a poder emparejar con el lanzador, y no es de acá.** `llimphi_ui::run`
-  no llama nunca a `with_name`, y winit 0.30.13 sólo manda `set_app_id` `if let Some(name) =
-  attributes.platform_specific.name` ⇒ **la ventana sale sin `app_id`**, y con el título `"llimphi"`,
-  que es el default de `App::title` y `BovedaApp` no sobrescribe. Por eso el `.desktop` no lleva
-  `StartupWMClass`: no habría contra qué emparejarlo. Vale para **toda** app llimphi, no para ésta:
-  el dock las ve a todas iguales y sin nombre. Es de llimphi —como el muro del §7.septies— y se
-  arregla allá.
+- **el `app_id` está, y el `.desktop` no necesita `StartupWMClass`.** `llimphi_ui` arma la ventana
+  con `with_name` —el `app_id` del xdg-toplevel en Wayland—: toma el `App::app_id()` que la app
+  declare y, si no declara ninguna (el caso de `BovedaApp`), cae al **nombre del ejecutable**, que
+  es el piso que llimphi se puso el día que 163 de sus 178 apps compartían el genérico del
+  compositor. ⇒ `app_id = "boveda"`, el mismo basename que el `.desktop`, y el emparejamiento
+  estándar ya funciona. Corroborado en metal: el guardián espera `'"app_id": *"boveda"'` en el
+  árbol de sway y lo encuentra a los 12-13 s. ⚠ Lo que sí queda mal es el **título**: `App::title()`
+  devuelve `"llimphi"` por defecto y `BovedaApp` no lo sobrescribe, así que la barra y el conmutador
+  dicen «llimphi». Es de la app, y es su propia unidad.
 
 El `.desktop` mueve el hash de la receta: `b3:b0c6adc4` ⇒ `b3:3f1072cc`, reconstruida en el worker
 con la guarda del §7.quinquies puesta (`### receta verificada 3f1072cc…` antes de compilar nada,
@@ -3410,13 +3412,178 @@ uno que funciona. Probado en los dos sentidos: los cuatro perfiles en verde, y e
    pero nadie decidió todavía si debe autoarrancar con la sesión. No se decide de paso: autoarrancar
    la ata a que la identidad del llavero esté desbloqueada en el login (la decisión 2 del §7.sexies,
    que sigue abierta);
-2. **el único proveedor de GL de las cuatro imágenes es `iris` (Intel).** `mesa-llvmpipe` no está en
-   ningún perfil y `mesa-swrast` sólo en `escritorio-mirada` — y softpipe no le alcanza a wgpu, que
-   pide compute (§7.septies). O sea que en metal sin GPU Intel el diálogo no pinta… igual que no
-   pintaría el escritorio entero, que también es cliente de GL: **la bóveda no agrega un hueco, lo
-   hereda**. Lo que taparía el caso de la VM es un proveedor por software en el perfil, y eso es una
-   decisión de tamaño con dos candidatos (`mesa-llvmpipe`, o lavapipe como receta nueva), no un
-   arreglo de paso.
+2. **el único proveedor de GL de las cuatro imágenes es `iris` (Intel)** —`mesa-llvmpipe` no está en
+   ningún perfil y `mesa-swrast` sólo en `escritorio-mirada`—, así que en metal sin GPU Intel el
+   diálogo no pinta. ⚠ Pero **esto no es un hueco que abra la bóveda, ni una decisión pendiente**:
+   el recorte iris-only es una decisión ESCRITA (SDD 14 §«Por qué iris-only»), el target declarado
+   es una laptop Intel Iris Xe, y `llvmpipe`/`radeonsi` piden la cola de LLVM que ese recorte existe
+   para no traer. El caso de la VM ya tiene camino y también está escrito: los runbooks de QEMU
+   montan `mesa-llvmpipe` COMO CAPA por fuera de la imagen, que es exactamente lo que hace el
+   guardián de metal. La bóveda hereda el alcance de hardware de la imagen y no le agrega nada: sin
+   GL tampoco pinta el escritorio, que también es cliente de GL.
+
+#### 7.undecies La bóveda entró a las imágenes y NADIE puede abrirla — y lo que el navegador veía era una bóveda TEMPORAL (2026-09-21)
+
+El §7.decies dejó dos cosas abiertas y las dos resultaron la misma: **quién levanta la app** y **de
+dónde sale la raíz de las claves** (la decisión 2 del §7.sexies). La cadena, medida eslabón por
+eslabón hacia atrás desde la app:
+
+| eslabón | qué dice | medido con |
+|---|---|---|
+| `raiz_de_identidad()` | saca la seed del **llavero del kernel**, clave `pacha_llavero::SEED_IDENTIDAD` | el fuente de `pacha-boveda-llimphi` |
+| quién la ESCRIBE | **dos lugares** en todo tawasuyu: `agora-cli identity unlock` y el onboarding de diseño, `churay-welcome-runner` (que la guarda bajo el mismo nombre) | `git grep` de los `guardar(…)` contra el llavero |
+| `agora-cli` en las imágenes | receta sellada, **`perfiles: []`** — en ninguna | `build-state` |
+| …y aunque se declarara | su pin es `9967b02c`, **del 2026-06-18**, donde no existen ni `SEED_IDENTIDAD` ni `desbloquear` | `git grep` sobre el pin, con HEAD de control (4 y 1 aciertos) |
+| `churay-welcome` | **no tiene receta en takana**: no está en el corpus | `ls recipes/` |
+
+⇒ **Hoy, en ninguna imagen de takana hay un binario capaz de sembrar la identidad.** No es que el
+usuario no la desbloqueó: es que no tiene con qué. La bóveda de la app abre `Err(boveda-cerrada)`
+siempre, en las cuatro.
+
+##### Y lo que el navegador veía NO era «cerrada»: era una bóveda temporal que se traga contraseñas
+
+Acá está la parte que importa, y es la que ningún guardián de ficheros podía ver. Cuando `abrir()`
+falla, la ventana se levanta igual sobre `bóveda_imposible()` —un `sled` en
+`/tmp/boveda-sin-abrir-<pid>`— **y dice el problema en pantalla**, que está bien y se queda. Lo que
+estaba mal es la línea siguiente:
+
+```rust
+let compartida = Arc::new(Mutex::new(boveda));
+atender_al_navegador(&compartida);      // ← sin preguntar si abrió
+```
+
+El socket del navegador se levantaba **sobre esa bóveda temporal**. Y del lado del host, `acceso()`
+prefiere al dueño si lo hay (`AccesoPrestado::Ajeno`), así que la extensión veía:
+
+```
+vault.status  ⇒  {"ok":true,"locked":false,"count":0}      ← ABIERTA, no «cerrada»
+vault.save    ⇒  guarda… en /tmp/boveda-sin-abrir-<pid>
+```
+
+O sea: una persona apretaba el botón, el diálogo de consentimiento abría de verdad, decía que sí, y
+la contraseña se guardaba **en un temporal que muere con el proceso** — y que ni el arranque
+siguiente de la misma app volvería a mirar, porque el nombre lleva el pid.
+
+⚠ **Hasta dónde llega cada afirmación, porque no son todas del mismo tipo.** Que el socket se
+levantaba sin preguntar si la bóveda abrió está MEDIDO: es el test de regresión, que falla con el
+arreglo neutralizado. Que la extensión veía `locked:false` es LECTURA del host —`Costura::acceso()`
+prefiere al dueño (`AccesoPrestado::Ajeno`) y `vault_status` contesta `locked:false` con el `count`
+del dueño, sea cual sea la bóveda que ese dueño tenga—; medirlo de punta a punta pide el guardián de
+metal, que no corre en esta máquina (no hay rootfs de sway ni store acá). La etapa que lo mediría es
+una hermana de la B: lanzar la app SIN `BOVEDA_TEST_ROOT` y exigir `locked:true`. El comentario de
+`bóveda_imposible` dice «nunca se escribe nada ahí»: es cierto para la ventana, y el navegador es el
+OTRO cliente. Es la regla 3 del CLAUDE.md entera: **un ausente falla ruidosamente; esto llegaba hasta
+el final diciendo que todo fue bien.**
+
+**Arreglado en tawasuyu** (`7917fbb96`): `atender_al_navegador` toma el problema y **no levanta el
+socket si la bóveda no abrió**; la extensión ve `locked:true`, que es la verdad y lo que ya sabe
+mostrar. La ventana sigue levantándose y diciendo por qué, que era lo bueno del diseño y no se toca.
+Con test de regresión en los dos sentidos —sin bóveda no hay socket; con bóveda abierta el socket
+aparece, que es el control sin el cual un `atender_al_navegador` vacío pasaría igual—, y probado
+además AL REVÉS: con el `if` neutralizado a propósito el test falla con el mensaje que corresponde.
+
+##### ⚠⚠ Y el muro del `Cargo.lock`, por tercera vez — con una vuelta que NO estaba escrita
+
+Antes de poder subir el pin: `cargo metadata --locked` sobre el HEAD publicado de tawasuyu muere con
+«cannot update the lock file», el mismo modo de fallo del §7.quinquies.bis y del §7.octies. Se
+aplicó la operación MÍNIMA que el §7.octies dejó escrita —`cargo metadata` SIN `--locked`—, dio un
+diff de **15 líneas de borrado y cero checksums movidos**, y **resultó byte a byte idéntico** al que
+otra sesión ya tenía sin commitear en el árbol compartido. Todo encajaba con la vez anterior. Se
+publicó, se subió el pin… y el build murió con **el mismo error de siempre** sobre el árbol ya
+pineado.
+
+**La causa, y es la parte que hay que recordar: esa operación mínima sólo es correcta donde el
+registro de cargo está COMPLETO.** Corrida en la jaula qorpa —cuyo registro local no tiene todos los
+crates— `cargo metadata` falla una descarga, **termina con estado 0** y deja un lock al que le faltan
+**dos miembros del workspace**. Las dos corridas, lado a lado:
+
+| dónde | diff del lock | qué hizo |
+|---|---|---|
+| jaula (registro incompleto) | **−15 líneas** | se llevó `cuentas-allichay` y `allichay` |
+| worker (registro completo) | **+1 línea** | agregó `"llimphi-widget-panel"`, la que faltaba |
+
+O sea que **el diff equivocado se ve MÁS mínimo que el correcto** —quince líneas contra una, y cero
+checksums movidos en los dos—, que es exactamente el criterio con el que el §7.octies enseñó a
+distinguir el arreglo bueno del malo. Y el «byte a byte idéntico al de la otra sesión», que la vez
+anterior fue la confirmación de que estaba bien, acá sólo significaba que la otra sesión lo había
+calculado **en la misma jaula**. Dos mediciones que coinciden no son dos mediciones independientes si
+comparten el mismo instrumento roto.
+
+Publicado el lock bueno en `6f0408d40`, y con él `cargo metadata --locked` pasa en el worker. El pin
+de las dos recetas sube ahí —se mueven juntas porque comparten árbol de fuentes por `<repo>-<sha>`—
+⇒ `boveda` `b3:5c43a827` y `shuma-pregunta` `b3:cd4277c5`, construidas en el worker con la guarda de
+receta puesta.
+
+##### ⚠ La guarda de receta del §7.quinquies.bis NO alcanza para una TANDA
+
+Las dos recetas se construyeron en un solo comando, con la guarda puesta —hash del worker contra el
+del hub— **una vez, al principio**. `shuma-pregunta` selló bien. Once minutos después, `boveda` dijo:
+
+```
+INFO takana_build: caché: artefacto ya en el store hash=b3:3f1072cc… name=boveda
+```
+
+que es el hash de la receta ANTERIOR: un acierto de caché en cero segundos sobre el artefacto viejo,
+o sea el éxito falso que ese capítulo existe para evitar. La causa es la misma y el agujero es
+nuevo: **el latido (`rsync --delete` hub→worker, cada 30 min) revierte la receta EN EL MEDIO de la
+tanda**, y una guarda que corre al principio no puede ver lo que pasa diez minutos después.
+Comprobado: `recipes/boveda.toml` en el worker había vuelto al pin `b80f7567c`, con mtime del ciclo
+del latido.
+
+⇒ **la guarda va PEGADA a cada build, no una vez por tanda.** Y la cura de fondo es empujar la
+receta a `origin/main` ANTES de construir: el latido siembra desde el checkout del otro hub, así que
+mientras el commit no esté publicado, cada ciclo deshace lo que uno acaba de subir por `rsync`.
+
+ℹ Y un detalle que ahorra construcciones y que conviene tener medido: **los COMENTARIOS de una
+receta no mueven su hash** —se hashean los campos declarados, no los bytes del fichero—. Comprobado
+en los dos sentidos sobre `shuma-pregunta.toml`: con un comentario de más y sin él, el mismo
+`b3:cd4277c5`. Lo que sí lo mueve es el contenido de una fase, que es por qué el `.desktop` del
+§7.decies costó un build y corregir tres párrafos de comentario no costó ninguno.
+
+##### El llavero es el de SESIÓN, y en takana no hay quien lo cree
+
+Aun con un sembrador en la imagen quedaría una pregunta que conviene tener medida antes de
+escribirla mal: `pacha-llavero` usa `KEY_SPEC_SESSION_KEYRING` (-3). Ese llavero **lo crea el
+login**, y quien no lo tiene recibe uno propio en cuanto lo toca — o sea que dos procesos HERMANOS
+pueden no compartirlo.
+
+Lo medido, y dónde:
+
+- **en el worker sí se comparte, y se ve por qué**: `/etc/pam.d/login` y `/etc/pam.d/sshd` traen
+  `session optional pam_keyinit.so force revoke`, y dos procesos hermanos de la misma sesión ssh
+  caen en el MISMO anillo — `grep "_ses: 1$" /proc/keys` da uno solo con la clave adentro, y el
+  censo de `_ses` no crece al repetir;
+- **en takana no lo crea nadie**: el único `pam.d` que este repo escribe es el de
+  `scripts/mirada-usb.sh`, y es `pam_permit` en las cuatro líneas. Y más abajo todavía:
+  `recipes/shadow.toml` compila con `--without-libpam`, así que **el `login` de consola no pasa por
+  PAM** y `pam_keyinit` no tendría dónde correr aunque se configurara. El módulo SÍ está en la
+  imagen (`lib64/security/pam_keyinit.so`, dentro de `linux-pam`, que está en las cuatro).
+
+⇒ de ahí se SIGUE —y esto es inferencia, no medición: ver el aviso de abajo— que el día que haya
+sembrador el camino bueno sea el de la misma rama de procesos (desbloquear en la consola y lanzar el
+compositor desde ahí, que hereda), y que el de «desbloquear en una terminal de adentro» no alcance,
+porque la app del lanzador es hermana y no hija. **Es lo primero que habrá que medir** cuando exista
+el sembrador, y se mide con el guardián de metal, que sí tiene dónde correr.
+
+⚠ **Lo que NO se pudo medir, dicho como tal:** la herencia hermano/hijo no se probó con una sonda
+directa. `add_key` da `EPERM` en la jaula qorpa y `keyctl` da `ENOSYS` en el LXC del worker (seccomp
+de contenedor, las dos), así que el control de la sonda no encendía. Lo de arriba es el censo de
+`/proc/keys` más la semántica documentada del kernel — no una medición de la herencia.
+
+##### Entonces: ¿se quedan las dos recetas en las imágenes?
+
+**Sí, y el motivo cambió.** La respuesta era NO cuando la app no podía pintar (§7.septies): eso era
+una bóveda que niega todo *sin un error*. Hoy la app pinta, dice en su ventana que no hay identidad
+desbloqueada, y —con el arreglo de arriba— el navegador contesta `locked:true`, que es la verdad.
+Son 43 M que muestran un estado explicable en vez de uno silencioso. Si mañana se prefiere sacarlas
+hasta que exista el sembrador, son dos líneas por perfil; queda dicho para que sea una decisión y no
+un olvido.
+
+**La unidad que queda**, con sus dos formas posibles y sin elegir de paso: un **sembrador de
+identidad en la imagen** — o subir el pin de `agora-cli` a uno que tenga `identity unlock` (y es un
+pin compartido por árbol de fuentes con `arje-zero` y `dominium-cli`), o traer `churay-welcome` al
+corpus, que es el camino de diseño y es otra receta llimphi GUI como ésta. Y con cualquiera de los
+dos, la pregunta del llavero de sesión de acá arriba.
 
 ## 8. Plan, por unidades de trabajo
 
@@ -3445,7 +3612,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). **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). **Y ENTRA A LAS IMÁGENES el 2026-09-21** (§7.decies): hasta ese día las dos estaban `sealed` con `perfiles: []` —o sea selladas y en ninguna imagen, la lección de `foot` que ese mismo fichero repetía quince veces—, así que la función seguía apagada de fábrica aunque anduviera. Se declaran en los cuatro perfiles de escritorio (~43 M por imagen) y la receta aprende a instalar `boveda.desktop`, porque estar en la imagen no es poder abrirla: sin entrada en `/usr/share/applications` a la app sólo se llega escribiendo su nombre en una terminal. El guardián de coherencia pasa de CINCO lugares a SEIS —el sexto es `targets.toml`— con su tercer control negativo. **Abierto**: quién la levanta con la sesión, y de dónde sale la raíz de las claves (decisión 2 del §7.sexies) | 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). **Y ENTRA A LAS IMÁGENES el 2026-09-21** (§7.decies): hasta ese día las dos estaban `sealed` con `perfiles: []` —o sea selladas y en ninguna imagen, la lección de `foot` que ese mismo fichero repetía quince veces—, así que la función seguía apagada de fábrica aunque anduviera. Se declaran en los cuatro perfiles de escritorio (~43 M por imagen) y la receta aprende a instalar `boveda.desktop`, porque estar en la imagen no es poder abrirla: sin entrada en `/usr/share/applications` a la app sólo se llega escribiendo su nombre en una terminal. El guardián de coherencia pasa de CINCO lugares a SEIS —el sexto es `targets.toml`— con su tercer control negativo. **Y la decisión 2 CONTESTADA el 2026-09-21** (§7.undecies), con la respuesta incómoda: **nadie puede abrirla**, porque ninguna imagen trae un binario capaz de sembrar `SEED_IDENTIDAD` — `agora-cli` está en `perfiles: []` y además pineado al 2026-06-18, donde el verbo `unlock` todavía no existe; `churay-welcome`, que es el camino de diseño, no tiene receta. Y al medirlo apareció lo que ningún guardián de ficheros veía: cuando la bóveda no abre, la app **igual le levantaba el socket al navegador** sobre una bóveda TEMPORAL ⇒ `vault.status` contestaba `locked:false` y un `vault.save` consentido guardaba en `/tmp/boveda-sin-abrir-<pid>`, que muere con el proceso. Arreglado en tawasuyu (`7917fbb96`) con test en los dos sentidos, y el pin de las dos recetas sube a `6f0408d40`, que trae además el `Cargo.lock` cerrado — con una vuelta nueva: la «operación mínima» del §7.octies, corrida en una jaula con el registro de cargo incompleto, devuelve 0 y BORRA dos miembros del workspace, y su diff se ve más mínimo que el correcto. **Queda**: un sembrador de identidad en la imagen (subir el pin de `agora-cli` o traer `churay-welcome`), y el llavero de SESIÓN, que en takana no lo crea nadie (`shadow` va `--without-libpam`) | 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/recipes/boveda.toml b/recipes/boveda.toml
index 343f6d37..c521af0a 100644
--- a/recipes/boveda.toml
+++ b/recipes/boveda.toml
@@ -37,21 +37,30 @@ version = "0.1.0"
 license = "MPL-2.0"
 
 [source]
-# ⚠ El pin subió otra vez el 2026-09-18, a `b80f7567c` (el arreglo es `cf3540460`; el commit de
-# arriba es el que además publica el `Cargo.lock` cerrado, sin el cual `cargo vendor --locked`
-# no corre): el dueño de la bóveda atendía de a UN
-# cliente y con el navegador abierto eso la deja MUDA (Gecko lanza un host por extensión, ocho, y
-# todos se conectan al arrancar). SDD 26 §7.octies. Las dos recetas se mueven juntas para no pagar
-# un tercer vendoreo de 2,4 G: comparten árbol por `<repo>-<sha>`.
-# ⚠ EL PIN NO ES EL DE SUS HERMANAS, Y ES A PROPÓSITO (2026-09-18). Las demás recetas del monorepo
-# están en `23a292863`; ésta apunta a `eed3120b6`, que es el commit donde llimphi aprende a pasarle
-# el **display handle** a wgpu en el camino de escritorio. Con el pin viejo este binario sella,
+# ⚠ El pin subió el 2026-09-21 a `6f0408d40`: **una bóveda que NO abrió le levantaba el socket al
+# navegador igual**, sobre la temporal `/tmp/boveda-sin-abrir-<pid>` ⇒ `vault.status` contestaba
+# `locked:false` y un `vault.save` consentido se perdía con el proceso. Es el caso NORMAL en las
+# cuatro imágenes, donde nada puede sembrar la identidad (SDD 26 §7.undecies). Arreglado con test
+# de regresión en los dos sentidos (el arreglo es `7917fbb96`; éste trae además el `Cargo.lock`
+# cerrado de VERDAD, sin el cual `cargo vendor --locked` muere con «cannot update the lock file»).
+# ⚠ Y ojo con cómo se cierra ese lock: la operación mínima documentada (`cargo metadata` sin
+# `--locked`) corrida en una jaula con el registro INCOMPLETO devuelve 0 y deja un lock al que le
+# faltan dos miembros del workspace — y su diff se ve MÁS mínimo que el correcto (-15 líneas contra
+# +1). Se cierra donde el registro está completo; acá, en el worker.
+# El pin anterior fue `b80f7567c` (2026-09-18, §7.octies: el dueño atendía de a UN cliente y con el
+# navegador abierto la bóveda quedaba MUDA). Las dos recetas se mueven juntas para no pagar un
+# tercer vendoreo de 2,4 G: comparten árbol por `<repo>-<sha>`.
+# ⚠ EL PIN NO ES EL DE SUS HERMANAS, Y ES A PROPÓSITO (desde el 2026-09-18). Las demás recetas del
+# monorepo están en `23a292863`, que es ANTERIOR a `eed3120b6` — el commit donde llimphi aprende a
+# pasarle el **display handle** a wgpu en el camino de escritorio—, y sin eso este binario sella,
 # arranca y **no abre ventana**: wgpu cae a la plataforma EGL surfaceless, la surface no tiene un
 # solo formato y la app panica con un «index out of bounds» que no nombra nada de esto (SDD 26
-# §7.septies). El precio es un árbol de fuentes propio — otro vendoreo de 2,4 G, porque el árbol se
-# comparte por `<repo>-<sha>`—; se paga hasta que las demás suban al mismo commit.
+# §7.septies). Por eso ésta y `boveda`/`shuma-pregunta` van siempre a un commit posterior; cuál es
+# hoy lo dice el bloque de arriba, que es el que se actualiza. El precio es un árbol de fuentes
+# propio —otro vendoreo de 2,4 G, porque el árbol se comparte por `<repo>-<sha>`—; se paga hasta
+# que las demás suban al mismo commit.
 repo   = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
-commit = "b80f7567c189f0bcba988efb7b0c5c2049f9aca6"
+commit = "6f0408d40d05d7961e811f9062ce5d5127987635"
 # tawasuyu COMMITEA su propio `vendor/` (smithay parcheado por `[patch.crates-io]` POR RUTA); el
 # `cargo vendor` de takana lo pisaría y el error hablaría de un crate cualquiera, no de esto.
 # No entra en `hash_inputs`.
@@ -105,12 +114,17 @@ install -Dm755 target/release/boveda /out/usr/bin/boveda
 # 2. El cuarto perfil (sway) lleva sólo `hicolor`, que por diseño no trae iconos: ahí el lanzador
 # cae al genérico, que es degradarse, no romperse.
 #
-# ⚠ Lo que esta entrada NO puede hacer, y conviene saberlo antes de buscarlo acá: emparejar la
-# VENTANA con el lanzador. `llimphi_ui::run` no llama nunca a `with_name`, así que winit 0.30.13
-# sólo manda `set_app_id` `if let Some(name) = attributes.platform_specific.name` ⇒ la ventana sale
-# **sin app_id**, y con el título `"llimphi"`, que es el default de `App::title` y `BovedaApp` no
-# sobrescribe. Por eso acá no hay `StartupWMClass`: no habría contra qué emparejarlo. Es de llimphi
-# —como el muro del §7.septies— y se arregla allá, no acá.
+# No lleva `StartupWMClass`, y la razón es que NO HACE FALTA — no que no se pueda. `llimphi_ui`
+# arma la ventana con `with_name`, que en Wayland es el `app_id` del xdg-toplevel: usa el
+# `App::app_id()` que la app declare y, si no declara ninguna —que es el caso de `BovedaApp`—, cae
+# al **nombre del ejecutable** (`app_id_del_ejecutable`, el piso que llimphi se puso cuando 163 de
+# sus 178 apps compartían el genérico del compositor). O sea `app_id = "boveda"`, que es
+# exactamente el basename de este `.desktop` ⇒ el emparejamiento estándar ya funciona.
+# Medido, no leído: el guardián de metal espera `'"app_id": *"boveda"'` en el árbol de sway y lo
+# encuentra a los 12-13 s (SDD 26 §7.novies).
+# ⚠ Lo que sí sigue mal es el TÍTULO: `App::title()` devuelve `"llimphi"` por defecto y `BovedaApp`
+# no lo sobrescribe, así que la barra y el conmutador de ventanas dicen «llimphi». Es de llimphi/la
+# app, no de esta entrada.
 mkdir -p /out/usr/share/applications
 cat > /out/usr/share/applications/boveda.desktop <<'DESKTOP'
 [Desktop Entry]
diff --git a/recipes/shuma-pregunta.toml b/recipes/shuma-pregunta.toml
index 4f59a491..c17ca6f9 100644
--- a/recipes/shuma-pregunta.toml
+++ b/recipes/shuma-pregunta.toml
@@ -31,21 +31,30 @@ version = "0.1.0"
 license = "MIT OR Apache-2.0"
 
 [source]
-# ⚠ El pin subió otra vez el 2026-09-18, a `b80f7567c` (el arreglo es `cf3540460`; el commit de
-# arriba es el que además publica el `Cargo.lock` cerrado, sin el cual `cargo vendor --locked`
-# no corre): el dueño de la bóveda atendía de a UN
-# cliente y con el navegador abierto eso la deja MUDA (Gecko lanza un host por extensión, ocho, y
-# todos se conectan al arrancar). SDD 26 §7.octies. Las dos recetas se mueven juntas para no pagar
-# un tercer vendoreo de 2,4 G: comparten árbol por `<repo>-<sha>`.
-# ⚠ EL PIN NO ES EL DE SUS HERMANAS, Y ES A PROPÓSITO (2026-09-18). Las demás recetas del monorepo
-# están en `23a292863`; ésta apunta a `eed3120b6`, que es el commit donde llimphi aprende a pasarle
-# el **display handle** a wgpu en el camino de escritorio. Con el pin viejo este binario sella,
+# ⚠ El pin subió el 2026-09-21 a `6f0408d40`: **una bóveda que NO abrió le levantaba el socket al
+# navegador igual**, sobre la temporal `/tmp/boveda-sin-abrir-<pid>` ⇒ `vault.status` contestaba
+# `locked:false` y un `vault.save` consentido se perdía con el proceso. Es el caso NORMAL en las
+# cuatro imágenes, donde nada puede sembrar la identidad (SDD 26 §7.undecies). Arreglado con test
+# de regresión en los dos sentidos (el arreglo es `7917fbb96`; éste trae además el `Cargo.lock`
+# cerrado de VERDAD, sin el cual `cargo vendor --locked` muere con «cannot update the lock file»).
+# ⚠ Y ojo con cómo se cierra ese lock: la operación mínima documentada (`cargo metadata` sin
+# `--locked`) corrida en una jaula con el registro INCOMPLETO devuelve 0 y deja un lock al que le
+# faltan dos miembros del workspace — y su diff se ve MÁS mínimo que el correcto (-15 líneas contra
+# +1). Se cierra donde el registro está completo; acá, en el worker.
+# El pin anterior fue `b80f7567c` (2026-09-18, §7.octies: el dueño atendía de a UN cliente y con el
+# navegador abierto la bóveda quedaba MUDA). Las dos recetas se mueven juntas para no pagar un
+# tercer vendoreo de 2,4 G: comparten árbol por `<repo>-<sha>`.
+# ⚠ EL PIN NO ES EL DE SUS HERMANAS, Y ES A PROPÓSITO (desde el 2026-09-18). Las demás recetas del
+# monorepo están en `23a292863`, que es ANTERIOR a `eed3120b6` — el commit donde llimphi aprende a
+# pasarle el **display handle** a wgpu en el camino de escritorio—, y sin eso este binario sella,
 # arranca y **no abre ventana**: wgpu cae a la plataforma EGL surfaceless, la surface no tiene un
 # solo formato y la app panica con un «index out of bounds» que no nombra nada de esto (SDD 26
-# §7.septies). El precio es un árbol de fuentes propio — otro vendoreo de 2,4 G, porque el árbol se
-# comparte por `<repo>-<sha>`—; se paga hasta que las demás suban al mismo commit.
+# §7.septies). Por eso ésta y `boveda`/`shuma-pregunta` van siempre a un commit posterior; cuál es
+# hoy lo dice el bloque de arriba, que es el que se actualiza. El precio es un árbol de fuentes
+# propio —otro vendoreo de 2,4 G, porque el árbol se comparte por `<repo>-<sha>`—; se paga hasta
+# que las demás suban al mismo commit.
 repo   = "https://git.tawasuyu.net/tawasuyu/tawasuyu.git"
-commit = "b80f7567c189f0bcba988efb7b0c5c2049f9aca6"
+commit = "6f0408d40d05d7961e811f9062ce5d5127987635"
 cargo_vendor_dir = ".hammer-cargo-vendor"
 
 [build]