diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md index de1a734e..e1706ad3 100644 --- a/docs/26-atuq-envoltorio-gecko.md +++ b/docs/26-atuq-envoltorio-gecko.md @@ -3595,6 +3595,179 @@ pin compartido por árbol de fuentes con `arje-zero` y `dominium-cli`), o traer 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. +#### 7.duodecies El sembrador entra a las imágenes — y de paso: `agora-cli` cifraba la seed de TODO EL MUNDO con una palabra pública (2026-09-21) + +El §7.undecies cerró con una unidad y dos formas posibles: **un sembrador de identidad en la +imagen**, o subiendo el pin de `agora-cli` a uno que tenga `identity unlock`, o trayendo +`churay-welcome` al corpus. Ésta es esa unidad, y trajo un hallazgo que no estaba en ninguna de +las dos formas. + +##### Cuál de las dos, con lo medido y no con la intuición + +| | `agora-cli` | `churay-welcome-llimphi` | +|---|---|---| +| ¿existe el binario? | sí, receta sellada desde la etapa G | **sí** — `src/main.rs` sin `[[bin]]`, o sea que cargo lo descubre con el nombre del paquete | +| ¿siembra `SEED_IDENTIDAD`? | sí, `identity unlock` | sí, `Efecto::GenerarSeedYDesbloquear` por el runner | +| ¿pide la frase? | por terminal | en un campo de la ventana — **es el camino de diseño** | +| ¿qué MÁS decide? | nada | backend de IA, dotfiles, fondo de pantalla, chasqui, `first_run_done` | + +⇒ **entra `agora-cli`**, y el motivo no es que sea mejor: es que el wizard trae consigo la +experiencia de primer arranque entera, y eso es una decisión de producto que no se toma de paso +dentro de una unidad del navegador. `agora-cli` hace UNA cosa, no impone ningún flujo, y es la que +el propio tawasuyu nombra como «la que siembra el login» (comentario de `pacha-secretos`). Cuando +`churay-welcome` tenga receta, esto se revisa — y queda dicho acá para que sea una revisión y no un +descubrimiento. + +##### ⚠⚠ Y antes de poder declararlo: sin `AGORA_PASSPHRASE`, la frase era una constante pública + +Esto apareció leyendo el fuente para otra cosa, y es lo que convierte la unidad en obligatoria en +vez de conveniente. `Sesion::abrir()` resolvía la frase del keystore así: + +```rust +let passphrase = std::env::var("AGORA_PASSPHRASE").unwrap_or_else(|_| { + eprintln!("agora-cli: usando passphrase de desarrollo \"agora-dev\". …"); + "agora-dev".to_string() // ← y el comando sigue, y termina en ✓ +}); +``` + +La cadena que eso toca, medida en el fuente y no supuesta: + +``` +la frase → Argon2id → ChaCha20-Poly1305 que cifra la seed en el keystore (agora-keystore) +la seed → SEED_IDENTIDAD en el llavero → la clave con la que `boveda` descifra su base +``` + +O sea: **en una máquina de trabajo es una comodidad; en una imagen de escritorio es la bóveda de +todo el mundo cerrada con una palabra que está escrita en el fuente**. Y otra vez la forma de fallo +de este capítulo entero: no falla nada, el aviso va a stderr —donde nadie mira cuando el comando +dice ✓— y lo que se ve es un gestor de contraseñas que anda. + +**Arreglado en tawasuyu** (`fd08dc03a`), con la decisión separada de su ejecución para poder +probarla sin una terminal delante: + +``` +variable > terminal (se pregunta, sin eco) > desarrollo (sólo si no hay a quién preguntarle) +``` + +- **perezosa**: `identity list` no descifra nada, así que no pregunta; y una vez por proceso, así + que un comando que abre y después guarda no pregunta dos veces; +- **doble al crear** (`identity new`, `wawa forjar-clave`): en la génesis no hay nada cifrado + contra qué verificar, así que un error de tipeo no se nota hoy — se nota el día que la bóveda no + abre, y para entonces la seed no se recupera; +- **la de desarrollo se queda** para el caso sin terminal (cron, pipes, CI), porque quitarla + rompería a todos los llamadores no interactivos que ya existen. Lo que cambió es que ahora es + una rama elegida y no el default de todos. + +Cuatro tests sobre `elegir_origen()`, y probado AL REVÉS, que es lo único que dice que el test +mide: con el brazo `Preguntar` borrado a propósito, falla con `left: Desarrollo / right: +Preguntar`, que es exactamente el comportamiento viejo. + +##### ⚠ El muro del `Cargo.lock`, por CUARTA vez — y esta vez la causa era otra + +`cargo vendor --locked` sobre el HEAD publicado murió con el «cannot update the lock file» de +siempre (§7.quinquies.bis, §7.octies, §7.undecies). Pero la causa **no** era la de las veces +anteriores: no había nada mal en el lock que yo había tocado —mi cambio agrega una sola arista, +`rpassword`, que ya era dependencia de workspace y por lo tanto ya estaba en el lock con su +checksum—. Lo que faltaba era `shuma-taller`, un miembro que otro agente había metido en un +`Cargo.toml` sin que el lock lo acompañara. + +Conviene tenerlo escrito así porque cambia el diagnóstico: **este muro no lo levanta quien toca el +lock, lo levanta cualquiera que toque un `Cargo.toml` del monorepo.** Va a volver. + +El cierre se hizo donde el §7.undecies dijo —**donde el registro de cargo está completo**—, y acá +salió gratis: el árbol ya estaba bajado en el worker por el build que acababa de fallar, así que +`cargo metadata` corrió ahí mismo. **+1 línea, cero checksums movidos**, que es la forma que tiene +el arreglo bueno; en la jaula, la misma operación devuelve 0 y borra miembros del workspace con un +diff que se ve más mínimo. + +##### ⚠ Y el `Cargo.lock` del árbol compartido traía otra vez el malo + +Al ir a commitear, `Cargo.lock` estaba en estado `MM`: el ÍNDICE con una versión sin +`llimphi-widget-panel` —la arista que el §7.undecies había agregado— y el ÁRBOL con una a la que +le faltaban `cuentas-allichay` y `allichay`, o sea **el lock que sale de cerrarlo en una jaula con +el registro incompleto, otra vez**. Ninguno de los dos se tocó: el commit se armó con `git +commit-tree` sobre el lock de HEAD, sin pasar por el índice ni por el árbol. Es la regla 2 del +CLAUDE.md llevada un paso más allá — cuando el fichero que necesitás es justo el que el otro agente +tiene a medias, el pathspec no alcanza y hay que salirse del índice. + +##### Lo que quedó, y cómo se comprobó + +**El pin**, con el rodeo del lock dentro: `9967b02c` (2026-06-18) → `fd08dc03a` (el arreglo) → +`da5fb8968` (el lock cerrado). Sellado en el worker con la guarda de receta **pegada al build** +—que es la lección del §7.undecies: el latido revierte la receta EN EL MEDIO de una tanda— ⇒ +`agora-cli` `b3:46529e14`, **1,9 M**, ELF estático. + +⚠ **Y un pin distinto es un árbol de fuentes distinto.** No coincide con el de +`boveda`/`shuma-pregunta` (`6f0408d40`) ni con el de sus hermanas de monorepo (`9967b02c`: +`dominium-cli`, `cosmos-cli`, los `arje-*`), así que paga un vendoreo propio de ~2,4 G. Se paga a +propósito: mover `boveda` para que coincidan cuesta dos builds caros de llimphi GUI y arrastra 28 +commits de upstream sin relación con esto. El día que el pin de `boveda` suba, que suba a éste. + +**Mirado por dentro, que es la regla 3** — el arreglo está en el COMMIT no dice que esté en el +BINARIO (§7.quinquies): + +``` +frase NUEVA para el keystore de … 1 ← la doble pregunta de la génesis +repetila / las dos frases no coinciden 1 / 1 +sin AGORA_PASSPHRASE y sin terminal 1 ← la rama de desarrollo, ahora acotada +id:default 1 ← el valor de SEED_IDENTIDAD: SÍ siembra +agora-dev 1 ← control: la frase vieja sigue, para el caso sin tty +``` + +**Y probado como artefacto, de punta a punta, con su control** (en el worker, con el binario que +va a la imagen): + +``` +identity new --name probador ⇒ nueva identidad creada · id dcedffd4… +unlock ⇒ ✓ identidad dcedffd4… desbloqueada y cacheada en la sesión +/proc/keys ⇒ user pacha:id:default: 32 ← la seed, sus 32 bytes +AGORA_PASSPHRASE=la-mala unlock ⇒ keystore: autenticación fallida ← y NO re-siembra +``` + +⚠ **Y de paso, una corrección al §7.undecies: el verbo NO es `agora-cli identity unlock`.** Es +`agora-cli unlock`, subcomando de primer nivel; `identity` no tiene `unlock`. Lo decía este +documento, lo decía la receta, y hasta el mensaje de error del propio binario dice «crea una con +`agora-cli identity new `» cuando el nombre va por `--name`. Nada de eso falla en un +guardián: se descubre la primera vez que alguien lo escribe. + +##### El séptimo lugar del guardián de coherencia + +`test-atuq-boveda-coherente.py` pasa de SEIS lugares a SIETE. El séptimo pregunta lo que queda +debajo del sexto —el sexto dice si el DUEÑO está en la imagen; éste, si hay con qué abrirla— y lo +hace en los dos planos, porque fallan distinto: + +| plano | qué exige | cómo se ve cuando falla | +|---|---|---| +| `recipes/agora-cli.toml` | que el COMMIT pineado llame a `guardar(pacha_llavero::SEED_IDENTIDAD` | `unknown subcommand`, o un binario que hace otra cosa | +| `docs/state/targets.toml` | que algún perfil de escritorio lo declare | sellado ≠ instalado: `locked:true` para siempre | + +Con su control positivo (`SEED_IDENTIDAD: &str`, que cualquier commit del monorepo tiene que +traer) y su control negativo propio (`--negative-control-sembrador`, que lo saca de una copia en +memoria). **El control positivo pagó solo**: corrido contra el pin VIEJO no contesta «no siembra» +sino «CONTROL ROTO: `SEED_IDENTIDAD: &str` tampoco aparece en 9967b02c» — que es la verdad exacta +(ese commit es anterior a la constante) y no la acusación equivocada que un `grep` sin control +habría hecho. Los cuatro controles negativos, en verde. + +##### Lo que sigue abierto, y separado de lo que quedó medido + +1. **la herencia del llavero, TODAVÍA sin medir**, y con una corrección sobre cómo NO medirla: el + §7.undecies dedujo del censo de `/proc/keys` que en el worker los hermanos comparten anillo. + Acá se vio que **`/proc/keys` leído como root lista claves que el proceso no necesariamente + puede usar** —una segunda sesión ssh «ve» la clave de la primera—, así que ese fichero prueba + que la clave EXISTE, no quién puede recuperarla. Lo que sí quedó medido y no lo estaba: + `add_key` **funciona** en el LXC del worker (lo hace `agora-cli unlock`, arriba); lo que da + `ENOSYS` ahí es `keyctl`. La pregunta que importa —si la app lanzada desde el menú, que es + HERMANA de la terminal, ve lo que la terminal sembró— sigue pidiendo el guardián de metal y un + lector, no un censo; +2. **`pacha` y `pacha-secretos` tienen el mismo hueco, una capa al lado**, y esto es medido: + los dos están declarados **sólo en `perfil.servidor`** y los dos sólo LEEN `SEED_IDENTIDAD`, así + que el Secret Service de esa imagen corre en «modo memoria» —los secretos no persisten— sin + sembrador que lo evite. Es menos silencioso que la bóveda, porque `pacha-secretos` publica + `persistente` por D-Bus justamente porque «desde afuera es indistinguible»; pero es la misma + unidad sin hacer. **No se declara `agora-cli` en `servidor` de paso**: es otro perfil, otro + dueño y otra decisión; +3. **el wizard**, que es el camino de diseño y hoy no tiene receta. + ## 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 @@ -3622,7 +3795,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. **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-`, 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 ✅ | +| 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-`, 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. **Y EL SEMBRADOR ENTRA el 2026-09-21** (§7.duodecies), que es lo que esa respuesta dejaba pendiente: de las dos formas posibles se elige `agora-cli` —el wizard `churay-welcome-llimphi` SÍ tiene binario, pero trae consigo backend de IA, dotfiles, fondo y chasqui, o sea la experiencia de primer arranque entera, y eso no se decide dentro de una unidad del navegador—. Y antes de poder declararlo apareció lo que lo volvía imposible: **sin `AGORA_PASSPHRASE`, `Sesion::abrir()` caía en la frase de desarrollo `"agora-dev"`** con un aviso por stderr y un ✓ en pantalla ⇒ en una imagen de escritorio, la seed de identidad de todo el mundo —que es la RAÍZ con la que la bóveda se descifra— cifrada con una palabra que está escrita en el fuente. Arreglado en tawasuyu (`fd08dc03a`): variable > terminal (se pregunta, sin eco, y DOS veces en la génesis) > desarrollo sólo si no hay a quién preguntarle, con la decisión en una función pura, cuatro tests y el control al revés. Pin `9967b02c` → `da5fb8968` (con el muro del `Cargo.lock` por cuarta vez, y esta vez la arista que faltaba era de OTRO agente: `shuma-taller`) ⇒ `b3:46529e14`, 1,9 M, declarado en los cuatro perfiles, **medido como artefacto**: `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys`, y con la frase equivocada falla y no re-siembra. El guardián de coherencia pasa de SEIS lugares a SIETE, con su cuarto control negativo. **Queda**: la herencia del llavero de SESIÓN entre procesos HERMANOS (sin medir — y `/proc/keys` como root no la mide, sólo dice que la clave existe), y `pacha`/`pacha-secretos` en `perfil.servidor` con el mismo hueco sin sembrador | 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/docs/state/targets.toml b/docs/state/targets.toml index 2d32146a..9e2ece50 100644 --- a/docs/state/targets.toml +++ b/docs/state/targets.toml @@ -352,6 +352,39 @@ paquetes = [ # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes. "boveda", "shuma-pregunta", + # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21) + # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se + # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que + # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo + # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de + # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además + # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las + # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo: + # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y + # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual + # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa. + # `agora-cli` el sembrador: `identity new --name ` crea la identidad y `unlock` la + # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado + # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene + # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá. + # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va + # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la + # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta + # «autenticación fallida» y NO re-siembra. + # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide + # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA, + # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque + # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como + # «la que siembra el login» (comentario de `pacha-secretos`). + # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN + # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow` + # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no + # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos + # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le + # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos + # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de + # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker. + "agora-cli", # ── CAPTURA Y STREAMING ────────────────────────────────────────────────────────────────────── # `obs-studio` (2026-09-04). A diferencia de mpv, ésta vive en ESTA COLA y no en el corpus, y no # es preferencia: su frontend es Qt6 y las 13 recetas Qt viven sólo en `incoming-kde`. Un qtbase @@ -657,6 +690,39 @@ paquetes = [ # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes. "boveda", "shuma-pregunta", + # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21) + # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se + # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que + # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo + # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de + # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además + # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las + # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo: + # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y + # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual + # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa. + # `agora-cli` el sembrador: `identity new --name ` crea la identidad y `unlock` la + # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado + # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene + # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá. + # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va + # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la + # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta + # «autenticación fallida» y NO re-siembra. + # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide + # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA, + # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque + # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como + # «la que siembra el login» (comentario de `pacha-secretos`). + # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN + # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow` + # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no + # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos + # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le + # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos + # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de + # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker. + "agora-cli", # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ───────────────────────────────────────────────── # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de @@ -1056,6 +1122,39 @@ paquetes = [ # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes. "boveda", "shuma-pregunta", + # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21) + # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se + # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que + # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo + # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de + # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además + # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las + # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo: + # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y + # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual + # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa. + # `agora-cli` el sembrador: `identity new --name ` crea la identidad y `unlock` la + # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado + # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene + # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá. + # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va + # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la + # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta + # «autenticación fallida» y NO re-siembra. + # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide + # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA, + # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque + # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como + # «la que siembra el login» (comentario de `pacha-secretos`). + # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN + # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow` + # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no + # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos + # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le + # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos + # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de + # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker. + "agora-cli", # ── VISOR DE IMÁGENES, TAMBIÉN EN LAS CUATRO ───────────────────────────────────────────────── # `swayimg` y no `imv`, que era lo que proponía `docs/plan-apps-usuario-final.md`: imv dibuja con # OpenGL de función fija (`glBegin`/`glOrtho`) y esta distro NO tiene proveedor de GL de @@ -1318,6 +1417,39 @@ paquetes = [ # y el control de contestar que NO (§7.novies)—. Por eso entran hoy y no antes. "boveda", "shuma-pregunta", + # ── …Y QUIÉN SIEMBRA LA IDENTIDAD CON LA QUE ESA BÓVEDA SE ABRE (SDD 26 §7.duodecies — 2026-09-21) + # Declarar al dueño y al diálogo dejó la función ANDANDO y todavía IMPOSIBLE de usar, y esto se + # midió el mismo día. `boveda` abre su base con una clave derivada de la seed de identidad, que + # `pacha-boveda-llimphi` saca del llavero de SESIÓN bajo `pacha_llavero::SEED_IDENTIDAD`. En todo + # tawasuyu esa clave la ESCRIBEN dos binarios —`agora-cli unlock` y el wizard de + # bienvenida `churay-welcome-llimphi`—: el primero estaba sellado con `perfiles: []` y además + # pineado al 2026-06-18, donde el verbo todavía no existe; el segundo no tiene receta. ⇒ en las + # cuatro imágenes `abrir()` daba `Err(boveda-cerrada)` SIEMPRE, y no por falta de desbloqueo: + # por falta de CON QUÉ. Es la misma forma de fallo del renglón de arriba una capa más abajo, y + # por eso los dos bloques viven juntos: la bóveda sin dueño y el dueño sin identidad se ven igual + # desde el navegador — `{"ok":true,"locked":true}`, que es una respuesta exitosa. + # `agora-cli` el sembrador: `identity new --name ` crea la identidad y `unlock` la + # deja en el llavero de la sesión. **1,9 M** medidos sobre el artefacto sellado + # (`b3:46529e14`), contra los ~43 M de las dos de arriba. Es CLI: no tiene + # lanzador ni lo necesita, así que no hay `.desktop` que comprobar acá. + # Medido contra el ARTEFACTO, no contra el commit (regla 3): en el worker, con el binario que va + # a la imagen, `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys` — la + # seed, sus 32 bytes. Y con el control negativo puesto: con la frase equivocada contesta + # «autenticación fallida» y NO re-siembra. + # ⚠ NO es el camino de diseño y conviene que quede escrito: el de diseño es el wizard, que pide + # la frase en un campo en vez de en una terminal. Pero ese wizard decide además backend de IA, + # dotfiles, fondo de pantalla y chasqui — traerlo es decidir la experiencia de primer arranque + # entera, y eso no se hace de paso. Ésta hace UNA cosa y es la que el propio tawasuyu nombra como + # «la que siembra el login» (comentario de `pacha-secretos`). + # ⚠ Y queda un eslabón SIN MEDIR, dicho como tal: el llavero es el de SESIÓN + # (`KEY_SPEC_SESSION_KEYRING`), que lo crea el login, y en takana no lo crea nadie —`shadow` + # compila `--without-libpam`, así que el `login` de consola no pasa por PAM y `pam_keyinit` no + # corre, aunque el módulo viaje en la imagen dentro de `linux-pam`—. De ahí se SIGUE que dos + # procesos hermanos puedan no compartir anillo, y entonces desbloquear en una terminal no le + # serviría a la app lanzada desde el menú; el camino bueno sería la misma rama de procesos + # (desbloquear en consola y lanzar el compositor desde ahí). Eso se mide con el guardián de + # metal, no desde acá: `add_key` da EPERM en la jaula y `keyctl` da ENOSYS en el LXC del worker. + "agora-cli", # `dunst` es LA OTRA MITAD, y sólo hace falta acá: KDE la atiende con plasma-workspace, GNOME con # gnome-shell y COSMIC con cosmic-notifications; sway no tenía a NADIE escuchando # `org.freedesktop.Notifications`, así que una página que pedía notificar mandaba el mensaje al bus diff --git a/recipes/agora-cli.toml b/recipes/agora-cli.toml index c2c8d1a6..92740e56 100644 --- a/recipes/agora-cli.toml +++ b/recipes/agora-cli.toml @@ -11,7 +11,7 @@ # # `boveda` abre su base con una clave derivada de la seed de identidad # └─ que `pacha-boveda-llimphi` saca del llavero de SESIÓN, clave `pacha_llavero::SEED_IDENTIDAD` -# └─ que en TODO tawasuyu escriben dos binarios: `agora-cli identity unlock` +# └─ que en TODO tawasuyu escriben dos binarios: `agora-cli unlock` # y el onboarding `churay-welcome-llimphi` # └─ `agora-cli` estaba en ninguna imagen · `churay-welcome` no tiene receta # diff --git a/scripts/test-atuq-boveda-coherente.py b/scripts/test-atuq-boveda-coherente.py index 8bf7f18a..1e1dd6bf 100755 --- a/scripts/test-atuq-boveda-coherente.py +++ b/scripts/test-atuq-boveda-coherente.py @@ -4,9 +4,10 @@ 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 + python3 scripts/test-atuq-boveda-coherente.py --negative-control-sembrador ── QUÉ MIRA, Y POR QUÉ ESTE GUARDIÁN EXISTE APARTE ─────────────────────────────────────────── -Una extensión de `atuq` está enchufada en SEIS lugares distintos, y ninguno da error si falta: +Una extensión de `atuq` está enchufada en SIETE 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 @@ -14,6 +15,7 @@ Una extensión de `atuq` está enchufada en SEIS lugares distintos, y ninguno da 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 + recipes/agora-cli.toml quién puede SEMBRAR la identidad con la que la base se abre 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 @@ -23,17 +25,21 @@ extensión conecta, manda `vault.match` y recibe «verbo desconocido» —que `f 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». +está sellado y en ninguna imagen. Y si la imagen no trae un sembrador de identidad, el dueño SÍ +está, abre su ventana, dice por qué no hay bóveda… y el navegador vuelve a ver `locked:true` — +correcto y para siempre, porque nadie tiene con qué desbloquearla (§7.undecies). +Ninguno de los seis 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 TRES CONTROLES NEGATIVOS, UNO POR AFIRMACIÓN ────────────────────────────────────────── +── LOS CUATRO 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. `--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 +memoria, y exige que el sexto lo vea. `--negative-control-sembrador` hace lo mismo con `agora-cli` +y exige que el séptimo 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 @@ -209,6 +215,97 @@ def revisar_los_perfiles(sin_esta=None): bien(f"{perfil} declara el dueño y el diálogo ({RAIZ_CONTROL} de control)") +# ── 10. EL SÉPTIMO LUGAR: ¿hay en la imagen alguien capaz de SEMBRAR la identidad? ──────────── +# El sexto lugar pregunta si el DUEÑO de la base está en la imagen. Éste pregunta lo que queda +# debajo y es lo que costó el §7.undecies: el dueño abre la bóveda con una clave derivada de la +# seed de identidad, que saca del llavero de sesión (`pacha_llavero::SEED_IDENTIDAD`). Si nadie +# la sembró, `abrir()` da `Err(boveda-cerrada)` SIEMPRE — no «todavía no la desbloqueaste»: nunca. +# +# Y el 2026-09-21, en las cuatro imágenes, no había con qué: en todo tawasuyu sólo DOS binarios +# escriben esa clave —`agora-cli identity unlock` y el onboarding `churay-welcome-runner`—, y +# `agora-cli` estaba sellado con `perfiles: []` (en ninguna imagen) y además pineado a un commit +# de junio donde el verbo todavía no existía, mientras que `churay-welcome` no tiene receta. +# +# Se comprueba en los dos planos, porque fallan distinto y los dos en silencio: +# a) el COMMIT que la receta pinea sabe sembrar (si no: «unknown subcommand», o peor, un binario +# que hace otra cosa con el mismo nombre); +# b) algún perfil de escritorio lo DECLARA (si no: sellado ≠ instalado, la lección de `foot`). +SEMBRADORES = {"agora-cli": os.path.join(ROOT, "recipes/agora-cli.toml")} +# Lo que un sembrador TIENE que hacer, leído del commit pineado y no de la memoria: guardar la +# seed bajo la constante canónica. Un `identity unlock` que no llame a esto no siembra nada. +SIEMBRA = "guardar(pacha_llavero::SEED_IDENTIDAD" +# El control POSITIVO del grep, por lo mismo de siempre: este chequeo espera ENCONTRAR, y un +# «no encontré» puede ser una ausencia o un patrón equivocado. La constante se define en +# `pacha-llavero` y tiene que estar en cualquier commit del monorepo. +SIEMBRA_CONTROL = 'SEED_IDENTIDAD: &str' + + +def pin_de(receta): + with open(receta, encoding="utf-8") as f: + m = re.search(r'^commit\s*=\s*"([0-9a-f]{7,40})"', f.read(), re.M) + return m.group(1) if m else None + + +def hay_en(commit, patron): + r = subprocess.run( + ["git", "-C", TAWASUYU, "grep", "-qF", patron, commit, "--", "*.rs"], + capture_output=True, + ) + return r.returncode == 0 + + +def revisar_el_sembrador(sin_sembrador=None): + if not os.path.isdir(os.path.join(TAWASUYU, ".git")): + mal(f"no hay clon de tawasuyu en {TAWASUYU}: el séptimo lugar queda SIN COMPROBAR " + f"(se cambia con TAWASUYU=/ruta)") + return + + # a) el commit pineado sabe sembrar + for nombre, receta in SEMBRADORES.items(): + if not os.path.exists(receta): + mal(f"no existe {os.path.relpath(receta, ROOT)}: {nombre} no está en el corpus") + continue + commit = pin_de(receta) + if not commit: + mal(f"{os.path.relpath(receta, ROOT)} no pinea ningún commit") + continue + if subprocess.run(["git", "-C", TAWASUYU, "cat-file", "-e", f"{commit}^{{commit}}"], + capture_output=True).returncode != 0: + mal(f"el clon de tawasuyu no conoce el pin {commit[:9]} de {nombre}: " + f"hace falta un `git fetch` antes de creerle a este guardián") + continue + if not hay_en(commit, SIEMBRA_CONTROL): + mal(f"CONTROL ROTO: {SIEMBRA_CONTROL!r} tampoco aparece en {commit[:9]}. El grep mira " + f"el sitio equivocado y su «no está» no prueba nada") + continue + if hay_en(commit, SIEMBRA): + bien(f"{nombre} pineado ({commit[:9]}) SÍ siembra {SIEMBRA[:-1]}…)") + else: + mal(f"{nombre} pineado ({commit[:9]}) NO siembra la seed de identidad: la bóveda " + f"contesta `boveda-cerrada` siempre y nadie tiene con qué abrirla") + + # b) alguien lo declara en cada perfil de escritorio + try: + with open(TARGETS, "rb") as f: + perfiles = tomllib.load(f)["perfil"] + except Exception as e: + mal(f"no se pudo leer {os.path.relpath(TARGETS, ROOT)} ({e})") + return + for perfil in PERFILES_ESCRITORIO: + raices = list(perfiles.get(perfil, {}).get("paquetes", [])) + if sin_sembrador: + raices = [r for r in raices if r != sin_sembrador] + if RAIZ_CONTROL not in raices: + mal(f"CONTROL ROTO: {perfil} no declara {RAIZ_CONTROL}, así que lo de abajo no mide") + continue + presentes = [n for n in SEMBRADORES if n in raices] + if presentes: + bien(f"{perfil} trae un sembrador de identidad ({', '.join(presentes)})") + else: + mal(f"{perfil} NO trae ningún sembrador de identidad ({'/'.join(SEMBRADORES)}): la " + f"bóveda está declarada y NADIE puede abrirla — `locked:true` para siempre") + + fallas = [] @@ -226,7 +323,7 @@ def leer(p): return f.read() -def revisar(permisos_host, verbo_extra=None, sin_perfil=None): +def revisar(permisos_host, verbo_extra=None, sin_perfil=None, sin_sembrador=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")) @@ -293,11 +390,15 @@ def revisar(permisos_host, verbo_extra=None, sin_perfil=None): # 9. Y el sexto, que vive en el manifiesto de objetivo: quién la pone en la imagen. revisar_los_perfiles(sin_perfil) + # 10. Y el séptimo: quién puede sembrar la identidad con la que esa bóveda se abre. + revisar_el_sembrador(sin_sembrador) + def main(): control = "--negative-control" in sys.argv control_verbo = "--negative-control-verbo" in sys.argv control_perfil = "--negative-control-perfil" in sys.argv + control_sembrador = "--negative-control-sembrador" in sys.argv host = json.loads(leer("native-messaging/puriy_costura.json")) permisos = list(host.get("allowed_extensions", [])) verbo_extra = None @@ -315,9 +416,15 @@ def main(): # 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") + sin_sembrador = None + if control_sembrador: + # El sembrador, sacado de la misma copia en memoria: si el séptimo chequeo no lo ve + # faltar, está contestando que sí en vez de mirar quién puede abrir la bóveda. + sin_sembrador = "agora-cli" + print(f"⚠ control negativo: se saca {sin_sembrador} de los perfiles de escritorio") print(f"── la bóveda en atuq ──") - revisar(permisos, verbo_extra, sin_perfil) + revisar(permisos, verbo_extra, sin_perfil, sin_sembrador) if control: if any("allowed_extensions" in f for f in fallas): @@ -337,6 +444,12 @@ def main(): return 0 print("\n✗ el control del perfil NO lo detectó: el sexto chequeo no mide nada") return 1 + if control_sembrador: + if any("sembrador de identidad" in f and "NO trae" in f for f in fallas): + print("\n✓ el control negativo del sembrador lo detectó") + return 0 + print("\n✗ el control del sembrador NO lo detectó: el séptimo chequeo no mide nada") + return 1 if fallas: print(f"\n✗ {len(fallas)} problema(s)") return 1