atuq: vigía de las dos puntas del cable — y el extractor de verbos que no veía la mitad

El chequeo del commit anterior miraba una extensión. El cable tiene la misma forma para las diez, y
este vigía lo recorre entero: qué verbos manda cada extensión y si el commit que `recipes/
puriy-costura.toml` pinea los atiende. Corre en un segundo y no construye nada.

Medido hoy, 13 verbos sobre 8 extensiones: sólo los TRES de la bóveda están sin dueño. O sea que
esto no se podía deducir mirando cuándo se tocó la receta por última vez — las otras nueve
extensiones conviven bien con el mismo pin viejo.

⚠ Y lo que costó más que el vigía: **el extractor de verbos no veía la mitad de lo que hay**. Estaba
buscando `verb: "…"`, que es como lo escribe la bóveda. Pero la extensión de IA arma el mensaje con
`postMessage({ id, verb: verbo, … })` —el verbo llega por VARIABLE—, así que ese patrón encontraba
cero verbos en `ia` y la declaraba sana sin haber mirado nada: cero verbos encontrados y cero verbos
faltando son el mismo cero con dos causas. Ahora recoge toda cadena con forma `ns.verbo` y descarta
las que son nombres de fichero, que es lo único que se les parece. Con eso aparecen `ai.ask` y
`archive.ask`, que antes no estaban en ninguna cuenta.

De ahí sale el tercer control, que no es negativo ni positivo sino de COBERTURA: una extensión de la
que no se extrae ningún verbo se REPORTA. Y las dos que de verdad no le hablan al host —`inicio` y
`proxy`, que viven enteras dentro del navegador— están nombradas en el código, para que «no manda
verbos» no se pueda confundir con «el extractor se quedó mudo».

Los otros tres controles: `sct.observe` tiene que aparecer en el commit pineado (si no, lo roto es
el vigía y lo dice); `--negative-control` agrega un verbo inventado a cada extensión y exige que lo
vea; y si el clon de tawasuyu no está, o no conoce el commit pineado, esto FALLA — «no se pudo
comprobar» no es «está bien». Los cuatro probados, incluida la rama del commit desconocido.

Pregunta por `git grep '"<verbo>" =>' <commit> -- '*.rs'` leyendo del objeto: sin checkout, porque
el árbol de tawasuyu es compartido y siempre tiene ficheros en vuelo de otras sesiones.
This commit is contained in:
Sergio
2026-09-15 19:04:50 +00:00
parent 2566bdd3c8
commit c30905e2b9
2 changed files with 179 additions and 4 deletions
+17 -4
View File
@@ -73,13 +73,26 @@ TAWASUYU = os.environ.get("TAWASUYU", "/mnt/vvv/tawasuyu")
RECETA_HOST = os.path.join(ROOT, "recipes/puriy-costura.toml")
# Lo que parece un verbo y no lo es: los ficheros que el manifiesto nombra.
NO_ES_VERBO = (".js", ".html", ".css", ".png", ".svg", ".json", ".txt", ".xpi", ".mjs")
def verbos_que_manda_la_extension():
"""Los verbos salen del propio `fondo.js`, no de una lista escrita a mano acá.
"""Los verbos salen del propio código de la extensión, 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)))
fichero — y lo haría en el único sitio donde eso no puede verse.
⚠ Y NO se busca `verb: "..."`, aunque la bóveda lo escriba así. Medido sobre las diez
extensiones: la de IA arma el mensaje con `postMessage({ id, verb: verbo, ... })`, o sea que
el verbo llega por variable y ese patrón no engancha NADA. Cero verbos encontrados y cero
verbos faltando se ven idénticos, y el guardián habría dicho que sí sin mirar nada. Por eso se
recogen todas las cadenas con forma de verbo y se descartan las que son nombres de fichero."""
fuentes = [f for f in sorted(os.listdir(os.path.join(ATUQ, "extensions/boveda")))
if f.endswith(".js")]
texto = "".join(leer(f"extensions/boveda/{f}") for f in fuentes)
crudos = set(re.findall(r'"([a-z][a-z0-9_]*\.[a-z][a-z0-9_]*)"', texto))
return sorted(v for v in crudos if not v.endswith(NO_ES_VERBO))
def commit_pineado():
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""¿El host que la receta PINEA atiende los verbos que mandan las DIEZ extensiones? (SDD 26 §7)
python3 scripts/vigia-atuq-verbos.py
python3 scripts/vigia-atuq-verbos.py --negative-control
── EL FALLO QUE ESTO CAZA, Y POR QUÉ NO SE VE DE NINGUNA OTRA FORMA ──────────────────────────
Cada función de `atuq` que no es CSS es un VERBO del host nativo `puriy-costura`. El host vive en
tawasuyu y entra al corpus por `recipes/puriy-costura.toml`, que lo pinea por commit. O sea que hay
un cable con DOS puntas en dos repos distintos: la extensión manda `vault.match`, y que eso sea un
verbo o una cadena que nadie atiende lo decide un `commit =` de un TOML.
Cuando las dos puntas no coinciden **no falla nada**. El manifiesto está, la política instala,
`connectNative` CONECTA —el host existe y contesta—, y lo que devuelve es:
{"ok": false, "error": "verbo desconocido: vault.match"}
El fondo de la extensión lee `r.ok !== true`, borra la insignia y se calla. Lo que el usuario ve es
un navegador sin esa función; lo que ve cualquier guardián de ficheros es todo en orden. Y la
distancia entre las dos puntas crece sola: escribir la extensión y subir el pin son dos unidades de
trabajo distintas, y la primera se commitea sin la segunda sin que nada proteste.
Medido el 2026-09-15, que es por qué este vigía existe: la extensión `boveda` se commiteó por la
mañana con sus tres verbos ya escritos en tawasuyu (`bc6903f9e`) y la receta seguía pineada en
`e19bb0e5`, que es su ANCESTRO. Las otras nueve extensiones estaban bien — o sea que esto no se
podía deducir mirando «cuándo se tocó la receta por última vez».
── CÓMO PREGUNTA, Y POR QUÉ ASÍ ──────────────────────────────────────────────────────────────
`git grep '"<verbo>" =>' <commit> -- '*.rs'` sobre el clon de tawasuyu, LEYENDO DEL OBJETO: sin
checkout y sin tocar el árbol, que es compartido y siempre tiene ficheros en vuelo de otras
sesiones. El brazo de `match` es el sitio donde el verbo deja de ser texto y pasa a tener dueño.
── LOS CONTROLES, PORQUE ESTO ES UN GREP QUE ESPERA CERO ─────────────────────────────────────
Un `grep` que devuelve cero puede ser una ausencia o un sitio equivocado, y las dos se ven igual.
Tres controles, uno por forma de mentir:
POSITIVO `sct.observe` TIENE que aparecer en el commit pineado. Si no aparece, lo roto es
este vigía y lo dice así, en vez de acusar a la receta.
COBERTURA una extensión de la que no se extrae NINGÚN verbo se reporta, no se saltea: cero
verbos encontrados y cero verbos faltando se ven idénticos.
--negative-control le agrega un verbo inventado a cada extensión y exige que los detecte.
Y si el clon de tawasuyu no está, esto NO pasa: «no se pudo comprobar» no es «está bien».
── EL EXTRACTOR NO BUSCA `verb: ""`, Y ESO ESTÁ MEDIDO ──────────────────────────────────────
La bóveda escribe `postMessage({ verb: "vault.match", … })`, pero la extensión de IA arma el
mensaje con `postMessage({ id, verb: verbo, … })` —el verbo llega por variable— y la de archivo
hace lo mismo. Un extractor que busque `verb:` seguido de comilla encuentra CERO en esas dos y las
declara sanas sin haber mirado nada. Por eso se recogen todas las cadenas con forma `ns.verbo` y se
descartan las que son nombres de fichero, que es lo único que se les parece.
"""
import os
import re
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
EXTS = os.path.join(ROOT, "recipes/atuq/extensions")
RECETA_HOST = os.path.join(ROOT, "recipes/puriy-costura.toml")
TAWASUYU = os.environ.get("TAWASUYU", "/mnt/vvv/tawasuyu")
CONTROL_POSITIVO = "sct.observe"
NO_ES_VERBO = (".js", ".html", ".css", ".png", ".svg", ".json", ".txt", ".xpi", ".mjs")
# Extensiones que a propósito no le hablan al host: viven enteras dentro del navegador. Están
# NOMBRADAS y no descartadas por silencio, para que «no manda verbos» se distinga de «el extractor
# no encontró nada», que es el mismo cero con dos causas.
SIN_HOST = {"inicio", "proxy"}
def verbos_de(ext):
d = os.path.join(EXTS, ext)
texto = ""
for f in sorted(os.listdir(d)):
if f.endswith(".js"):
with open(os.path.join(d, f), encoding="utf-8") as fh:
texto += fh.read()
crudos = set(re.findall(r'"([a-z][a-z0-9_]*\.[a-z][a-z0-9_]*)"', texto))
return sorted(v for v in crudos if not v.endswith(NO_ES_VERBO))
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):
return subprocess.run(
["git", "-C", TAWASUYU, "grep", "-qF", f'"{verbo}" =>', commit, "--", "*.rs"],
capture_output=True,
).returncode == 0
def main():
control = "--negative-control" in sys.argv
inventado = "host.este_verbo_no_existe" if control else None
fallas = []
commit = commit_pineado()
if not commit:
print(f"{os.path.relpath(RECETA_HOST, ROOT)} no pinea ningún commit")
return 1
if not os.path.isdir(os.path.join(TAWASUYU, ".git")):
print(f"✗ no hay clon de tawasuyu en {TAWASUYU}: esto queda SIN COMPROBAR, que no es "
f"lo mismo que estar bien (se cambia con TAWASUYU=/ruta)")
return 1
if subprocess.run(["git", "-C", TAWASUYU, "cat-file", "-e", f"{commit}^{{commit}}"],
capture_output=True).returncode != 0:
print(f"✗ el clon no conoce el commit pineado {commit[:9]}: hace falta un `git fetch` "
f"antes de creerle a este vigía")
return 1
print(f"── los verbos de atuq contra el host pineado ──")
print(f" receta : {os.path.relpath(RECETA_HOST, ROOT)}")
print(f" commit : {commit[:9]} (clon: {TAWASUYU})\n")
if not atiende(commit, CONTROL_POSITIVO):
print(f"✗ CONTROL ROTO: {CONTROL_POSITIVO} tampoco aparece en {commit[:9]}. El grep mira "
f"el sitio equivocado y ningún cero de este vigía prueba una ausencia.")
return 1
print(f" ✓ control positivo: {CONTROL_POSITIVO} está en {commit[:9]} ⇒ el grep mide\n")
if control:
print(f"⚠ control negativo: a cada extensión se le agrega {inventado}\n")
for ext in sorted(os.listdir(EXTS)):
if not os.path.isdir(os.path.join(EXTS, ext)):
continue
verbos = verbos_de(ext)
if not verbos:
if ext in SIN_HOST:
print(f" {ext:<10} — no le habla al host, a propósito")
else:
print(f" {ext:<10} ✗ no se le extrajo NINGÚN verbo y debería tener: el extractor "
f"no está midiendo esta extensión")
fallas.append(f"{ext}: sin verbos extraídos")
continue
if inventado:
verbos = verbos + [inventado]
for v in verbos:
if atiende(commit, v):
print(f" {ext:<10}{v}")
else:
print(f" {ext:<10}{v} — el host pineado NO lo atiende: contesta «verbo "
f"desconocido» y la extensión lo lee como «no hay función»")
fallas.append(f"{ext}: {v}")
if control:
if any(inventado in f for f in fallas):
print(f"\n✓ el control negativo lo detectó")
return 0
print(f"\n✗ el control negativo NO lo detectó: este vigía no mide nada")
return 1
if fallas:
print(f"\n{len(fallas)} verbo(s) sin dueño en el host pineado")
return 1
print(f"\n✓ las dos puntas del cable coinciden")
return 0
if __name__ == "__main__":
raise SystemExit(main())