atuq §7.duodecies: el sembrador entra a las cuatro imágenes — y cifraba la seed de todos con una palabra pública

El §7.undecies dejó la bóveda declarada y a NADIE capaz de abrirla: ninguna
imagen traía un binario que sembrara `pacha_llavero::SEED_IDENTIDAD`. Esta es
esa unidad.

De las dos formas posibles entra `agora-cli`, y el motivo no es que sea mejor:
el wizard `churay-welcome-llimphi` SÍ tiene binario (medido: `src/main.rs` sin
`[[bin]]`, o sea que cargo lo descubre), pero decide además backend de IA,
dotfiles, fondo de pantalla y chasqui — la experiencia de primer arranque
entera, que 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. La cadena que eso toca:
frase → Argon2id → ChaCha20-Poly1305 que cifra la seed → la clave con la que
`boveda` descifra su base. O sea, en una imagen de escritorio, la bóveda de
todo el mundo cerrada con una palabra escrita en el fuente, y nada que falle.

Arreglado en tawasuyu (`fd08dc03a`): variable > terminal (se pregunta, sin eco,
y DOS veces en la génesis, donde un error de tipeo no se nota hasta que la seed
ya no se recupera) > desarrollo sólo si no hay a quién preguntarle. La decisión
vive en una función pura con cuatro tests, probada AL REVÉS: con el brazo
`Preguntar` borrado falla con `left: Desarrollo / right: Preguntar`.

Pin `9967b02c` → `da5fb8968` ⇒ `b3:46529e14`, 1,9 M, sellado en el worker con
la guarda PEGADA al build. Mirado por dentro (regla 3) y probado como
artefacto, con control negativo: `identity new` + `unlock` deja
`user pacha:id:default: 32` en `/proc/keys`, y con la frase equivocada contesta
«autenticación fallida» y NO re-siembra.

El muro del `Cargo.lock` por cuarta vez, con la causa cambiada: esta vez no la
puso quien tocó el lock sino otro agente que metió `shuma-taller` en un
`Cargo.toml`. Cerrado en el worker, donde el registro está completo: +1 línea.
Y el lock del árbol compartido traía otra vez el malo (índice y árbol con dos
versiones distintas, las dos rotas), así que el commit se armó con
`commit-tree` sin pasar por el índice.

Corrección al §7.undecies: el verbo es `agora-cli unlock`, no
`agora-cli identity unlock`.

El guardián de coherencia pasa de SEIS lugares a SIETE, con su cuarto control
negativo; los cuatro, en verde.

Queda: la herencia del llavero de SESIÓN entre procesos hermanos (sin medir —
y `/proc/keys` como root no la mide), y `pacha`/`pacha-secretos` en
`perfil.servidor` con el mismo hueco.
This commit is contained in:
Sergio
2026-09-21 16:40:46 +00:00
parent 0e0d8ddf0c
commit c7097e4a91
4 changed files with 426 additions and 8 deletions
File diff suppressed because one or more lines are too long
+132
View File
@@ -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 <nombre>` 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 <nombre>` 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 <nombre>` 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 <nombre>` 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
+1 -1
View File
@@ -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
#
+119 -6
View File
@@ -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