#!/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 '"" =>' -- '*.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())