atuq: la bóveda le habla a un host que no sabe sus verbos — y eran CINCO lugares, no cuatro

Este guardián nació esta mañana diciendo que una extensión está enchufada en CUATRO sitios y que
ninguno da error si falta. Los cuatro estaban bien. El quinto no, y es el que hoy está roto.

El quinto lugar vive en otra receta y en otro repo: `recipes/puriy-costura.toml` pinea un commit de
tawasuyu, y es ESE commit el que decide si `vault.match` es un verbo o una cadena que nadie atiende.
Medido, con su control al lado, sobre el artefacto SELLADO y vigente (`hash --check` dice SELLADO
para `b3:1eb2b692`):

    strings del binario sellado → «vault» 0 veces   ·   «cas» 5 veces   ⇐ EL CONTROL

El cero solo no probaba nada —un grep que devuelve cero puede ser una ausencia o un sitio
equivocado, y las dos se ven igual—; lo que lo convierte en evidencia es el «cas» que TENÍA que dar
positivo en el mismo binario. Y del lado de la fuente sale lo mismo: la receta pinea `e19bb0e5`, que
es ANCESTRO de `bc6903f9e` —el commit que agregó los verbos—, así que el host sellado es anterior a
la bóveda por construcción.

Cómo se ve ese fallo desde la silla del usuario, que es por qué esto merece guardián: el manifiesto
está, el permiso está, la política instala, `connectNative` CONECTA, la extensión manda
`vault.match` y el host contesta `verbo desconocido`. `fondo.js` lee `r.ok !== true`, borra la
insignia y se calla. Un navegador sin bóveda, y todos los ficheros en orden.

El chequeo pregunta `git grep '"<verbo>" =>' <commit> -- '*.rs'` sobre el clon, sin checkout: el
árbol de tawasuyu es compartido y siempre tiene ficheros en vuelo de otras sesiones. Los verbos los
saca de `fondo.js` y no de una lista escrita acá, que se desincronizaría justo en el único sitio
donde eso no se ve.

Tres controles, porque el chequeo es un grep que espera cero:
- POSITIVO: `sct.observe` tiene que aparecer en el commit pineado. Si no aparece, lo roto es el
  guardián y lo dice así, en vez de acusar a la receta;
- `--negative-control-verbo`: le agrega un verbo inventado y exige que lo detecte;
- y si el clon no está, «no se pudo comprobar» NO es «está bien»: falla ruidosamente.

Lo que este commit NO hace: subir el pin. Eso es su propia unidad y tiene un muro medido delante —el
`Cargo.lock` de tawasuyu en `main` todavía describe a `puriy-costura` con sus once deps viejas, sin
`pacha-boveda` ni `pacha-cifrador`, y `pacha-boveda-daemon` no está en el lock en absoluto. Con eso,
`cargo vendor --locked` muere sin nombrar al culpable, que es exactamente la trampa que la propia
receta dejó escrita.
This commit is contained in:
Sergio
2026-09-15 18:59:02 +00:00
parent 98c9e4daa6
commit e7369efe82
+123 -5
View File
@@ -5,28 +5,38 @@
python3 scripts/test-atuq-boveda-coherente.py --negative-control
── QUÉ MIRA, Y POR QUÉ ESTE GUARDIÁN EXISTE APARTE ───────────────────────────────────────────
Una extensión de `atuq` está enchufada en CUATRO lugares distintos, y ninguno da error si falta:
Una extensión de `atuq` está enchufada en CINCO lugares distintos, y ninguno da error si falta:
manifest.json el id que la extensión declara
distribution/policies.json la política que la instala
native-messaging/*.json el permiso para hablarle al host
atuq.cfg las preferencias que la acompañan
recipes/puriy-costura.toml el COMMIT del host, que decide si sus verbos existen
Si falta la política, la extensión no se instala y el usuario ve un navegador sin la función. Si
falta el permiso del host, la extensión se instala y **se conecta a nada** —`connectNative` falla
en silencio y la insignia no aparece nunca—. Si faltan las preferencias, la bóveda y el gestor de
Gecko se pelean por el mismo campo. Ninguno de los tres se ve como un error: se ven como «no anda».
Gecko se pelean por el mismo campo. Y si el commit pineado del host es ANTERIOR a los verbos, la
extensión conecta, manda `vault.match` y recibe «verbo desconocido» —que `fondo.js` lee como «no hay
bóveda» y se calla—. Ninguno de los cuatro se ve como un error: se ven como «no anda».
Éste NO reemplaza al guardián de metal (servidor HTTP, navegador real, login real, el diálogo de
consentimiento a la vista). Lo precede: mide lo que se puede medir sin construir nada, en un
segundo, y ahorra descubrir en metal un renglón que faltaba en un JSON.
── LOS DOS CONTROLES NEGATIVOS, UNO POR AFIRMACIÓN ───────────────────────────────────────────
`--negative-control` saca el permiso del host de una copia en memoria y exige que esto lo detecte.
Sin él, un guardián que siempre dice que sí se vería idéntico a uno que funciona.
`--negative-control-verbo` le manda al quinto chequeo un verbo inventado y exige lo mismo. Sin
ellos, un guardián que siempre dice que sí se vería idéntico a uno que funciona.
Y el quinto chequeo trae además un control POSITIVO —`sct.observe`, que el host pineado tiene que
tener sí o sí—, porque es un `grep` que espera cero y un cero puede ser una ausencia o un sitio
equivocado: las dos se ven igual, y sólo las separa un patrón hermano que TENGA que dar positivo.
"""
import json
import os
import re
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -36,6 +46,98 @@ ID = "boveda@atuq.tawasuyu"
# Las preferencias de D7: de fábrica manda la bóveda, pero el usuario puede volver atrás.
PREFS = ["signon.rememberSignons", "signon.autofillForms"]
# ── 8. EL QUINTO LUGAR: ¿el host que la RECETA PINEA sabe los verbos que ella manda? ──────────
# Los siete de arriba miran `recipes/atuq/`, que es el lado del navegador. Pero una extensión no
# está enchufada en cuatro lugares sino en CINCO, y el quinto vive en otra receta y en otro repo:
# `recipes/puriy-costura.toml` pinea un commit de tawasuyu, y es ESE commit el que decide si
# `vault.match` es un verbo o una cadena que nadie atiende.
#
# Falla igual de callado que los otros —peor, incluso—: el manifiesto está, el permiso está, la
# política instala, `connectNative` CONECTA, la extensión manda `vault.match` y el host contesta
# `{"ok": false, "error": "verbo desconocido: vault.match"}`. `fondo.js` lee `r.ok !== true`, borra
# la insignia y se calla. Lo que el usuario ve es un navegador sin bóveda; lo que un guardián de
# ficheros ve es todo en orden.
#
# Y no es hipotético: así estaba el 2026-09-15 al escribir esto. La extensión se commiteó con los
# verbos ya escritos en tawasuyu (`bc6903f9e`) y la receta seguía pineada en `e19bb0e5`, que es su
# ANCESTRO. El artefacto sellado lo confirma desde el otro lado, con su control puesto:
# strings del binario sellado → «vault» 0 veces · «cas» 5 veces
#
# ── EL CONTROL POSITIVO NO ES DECORACIÓN ──────────────────────────────────────────────────────
# Este chequeo es un `grep` que espera CERO, y un cero puede ser una ausencia o un sitio
# equivocado: las dos se ven igual. Por eso se pregunta también por `sct.observe`, un verbo que el
# commit pineado TIENE que tener —`sct` es la unidad 6, sellada y medida en metal—. Si el control
# sale cero, lo que está roto es el guardián y se dice así, en vez de acusar a la receta.
VERBOS_CONTROL = "sct.observe"
TAWASUYU = os.environ.get("TAWASUYU", "/mnt/vvv/tawasuyu")
RECETA_HOST = os.path.join(ROOT, "recipes/puriy-costura.toml")
def verbos_que_manda_la_extension():
"""Los verbos salen del propio `fondo.js`, no de una lista escrita a mano acá.
Una lista paralela se desincroniza el día que alguien agrega un verbo y no toca este
fichero — y lo haría en el único sitio donde eso no puede verse."""
fondo = leer("extensions/boveda/fondo.js")
return sorted(set(re.findall(r'verb:\s*"([a-z]+\.[a-z_]+)"', fondo)))
def commit_pineado():
with open(RECETA_HOST, 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 atiende(commit, verbo):
"""¿Hay un brazo de `match` para este verbo en el commit pineado?
`git grep <commit>` lee del objeto, así que no hace falta hacer checkout ni tocar el árbol
compartido de tawasuyu, que siempre tiene ficheros en vuelo de otras sesiones."""
r = subprocess.run(
["git", "-C", TAWASUYU, "grep", "-qF", f'"{verbo}" =>', commit, "--", "*.rs"],
capture_output=True,
)
return r.returncode == 0
def revisar_el_host(verbo_extra=None):
verbos = verbos_que_manda_la_extension()
if not verbos:
mal("no se pudo leer ningún `verb:` de fondo.js: este chequeo no está midiendo nada")
return
if verbo_extra:
verbos = verbos + [verbo_extra]
commit = commit_pineado()
if not commit:
mal(f"{os.path.relpath(RECETA_HOST, ROOT)} no pinea ningún commit")
return
if not os.path.isdir(os.path.join(TAWASUYU, ".git")):
# Un ausente falla RUIDOSAMENTE: si el clon no está, esto no se puede comprobar, y
# «no se pudo comprobar» no es «está bien».
mal(f"no hay clon de tawasuyu en {TAWASUYU}: el quinto lugar queda SIN COMPROBAR "
f"(se cambia con TAWASUYU=/ruta)")
return
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 commit pineado {commit[:9]}: "
f"hace falta un `git fetch` antes de creerle a este guardián")
return
# El control primero: si éste falla, ningún cero de abajo significa nada.
if not atiende(commit, VERBOS_CONTROL):
mal(f"CONTROL ROTO: {VERBOS_CONTROL} tampoco aparece en {commit[:9]}. El grep está mirando "
f"el sitio equivocado y los ceros de abajo no prueban ninguna ausencia")
return
bien(f"control: {VERBOS_CONTROL} SÍ está en el host pineado ({commit[:9]}) ⇒ el grep mide")
for v in verbos:
if atiende(commit, v):
bien(f"el host pineado atiende {v}")
else:
mal(f"el host pineado ({commit[:9]}) NO atiende {v}: la extensión lo manda y recibe "
f"«verbo desconocido», que fondo.js lee como «no hay bóveda» y se calla")
fallas = []
@@ -53,7 +155,7 @@ def leer(p):
return f.read()
def revisar(permisos_host):
def revisar(permisos_host, verbo_extra=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"))
@@ -114,17 +216,27 @@ def revisar(permisos_host):
else:
bien("la dirección la pone el navegador, no la página")
# 8. Y el quinto lugar, que vive en otra receta y en otro repo.
revisar_el_host(verbo_extra)
def main():
control = "--negative-control" in sys.argv
control_verbo = "--negative-control-verbo" in sys.argv
host = json.loads(leer("native-messaging/puriy_costura.json"))
permisos = list(host.get("allowed_extensions", []))
verbo_extra = None
if control:
print("⚠ control negativo: se le saca el permiso del host")
permisos = [x for x in permisos if x != ID]
if control_verbo:
# Un verbo que ningún commit puede atender: si el quinto chequeo no lo ve faltar, no está
# mirando el host sino contestando que sí.
verbo_extra = "vault.este_verbo_no_existe"
print(f"⚠ control negativo: se le agrega el verbo inventado {verbo_extra}")
print(f"── la bóveda en atuq ──")
revisar(permisos)
revisar(permisos, verbo_extra)
if control:
if any("allowed_extensions" in f for f in fallas):
@@ -132,6 +244,12 @@ def main():
return 0
print("\n✗ el control negativo NO lo detectó: este guardián no mide nada")
return 1
if control_verbo:
if any(verbo_extra in f for f in fallas):
print("\n✓ el control negativo del verbo lo detectó")
return 0
print("\n✗ el control del verbo NO lo detectó: el quinto chequeo no mide nada")
return 1
if fallas:
print(f"\n{len(fallas)} problema(s)")
return 1