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.
163 lines
8.0 KiB
Python
Executable File
163 lines
8.0 KiB
Python
Executable File
#!/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())
|