vigía: 46 herramientas selladas que NO SE PUEDEN INVOCAR — cuatro verdes sobre algo inerte
Hay una familia de recetas que no son programas: son SUBCOMANDOS. `protoc-gen-go` es un plugin que
ejecuta `protoc`; `cargo-audit` existe para escribirse `cargo audit`; `kubectl-tree` lo despacha
`kubectl`. Están selladas, tienen contenido, resuelven sus sonames, el grafo las cuenta y
`verificar-repro` dice que reproducen. **Cuatro indicadores en verde sobre binarios que no se pueden
usar**, porque el driver que los invoca no está en el catálogo.
Ninguna métrica existente puede verlo: la arista «me ejecuta aquél» NO es una dep de build, así que
no existe en el grafo. Lo descubrí por accidente — `protoc-gen-go` llevaba meses sellado y el
catálogo no tenía `protoc`; lo delató ir a escribir la receta de un servicio gRPC y preguntarme con
qué se generan los stubs. Eso es exactamente un punto ciego, y acá va su guardián.
Lo que mide hoy:
✗ falta `cargo` ⇒ 44 recetas INERTES (cargo-audit, cargo-nextest, cargo-deny, …)
✗ falta `kubectl` ⇒ 2 recetas INERTES (kubectl-neat, kubectl-tree)
TOTAL: 46. No es deuda de BUILD —construyen y reproducen— es deuda de CATÁLOGO.
Dos decisiones de diseño que son la diferencia entre un vigía que se lee y uno que se ignora:
· **Comprueba el BINARIO, no el nombre de la receta.** El driver de `protoc-gen-*` es `protoc`, que
lo publica la receta `protobuf`. Preguntar «¿existe recipes/protoc.toml?» habría seguido diciendo
«no» DESPUÉS de cerrarlo, y un guardián que grita cuando ya está arreglado se empieza a ignorar.
· **La tabla de drivers es explícita, no una heurística sobre guiones.** `git-cliff` es subcomando de
git; `gettext-tiny` no es subcomando de `gettext`. Adivinar por el guión da falsos positivos, y un
vigía con falsos positivos no se lee.
Y trae `--autoprueba` con los dos controles, porque un guardián que nunca falló no se sabe si sirve:
NEGATIVO (escondo `git`, que sí está ⇒ tiene que cantar `git-`) y POSITIVO (sin tocar nada, no debe
cantar sobre los drivers presentes). Los dos pasan.
⚠ Una trampa medida escribiéndolo, dentro del propio script: `glob('store/*-go')` casa por SUFIJO y
matchea `…-protoc-gen-go`, o sea que contestaba que `go` estaba sellado cuando no lo estaba. El
nombre de receta se saca con una regex ANCLADA sobre el basename.
This commit is contained in:
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
# vigia-subcomandos.py — ¿hay herramientas SELLADAS que no sirven porque falta su DRIVER?
|
||||
#
|
||||
# ══ EL PUNTO CIEGO QUE ESTE VIGÍA CUBRE ════════════════════════════════════════════════════════
|
||||
# `build-state.py` contesta «¿está sellado?», `vigia-sonames.py` contesta «¿algún `.so` que alguien
|
||||
# pide no lo provee nadie?» y `vigia-imagen.py` contesta «¿la imagen se puede USAR?». Ninguno
|
||||
# contesta ésta: **¿este binario sellado sirve para algo por sí solo?**
|
||||
#
|
||||
# Hay una familia entera de herramientas que NO son programas: son SUBCOMANDOS. `protoc-gen-go` es un
|
||||
# plugin que `protoc` ejecuta; `cargo-audit` existe para escribirse `cargo audit`; `kubectl-tree` lo
|
||||
# despacha `kubectl`. Sellados, no vacíos, sin NEEDED colgante, reproducibles — y **inertes**, porque
|
||||
# el driver que los invoca no está en el catálogo.
|
||||
#
|
||||
# ⚠ **Ningún indicador existente puede verlo.** El artefacto tiene contenido (regla 3 ✓), resuelve
|
||||
# sus sonames (vigia-sonames ✓), el grafo lo cuenta como `sealed` (✓) y `verificar-repro` dice que
|
||||
# REPRODUCE (✓). Cuatro verdes sobre algo que no se puede usar. La arista «me ejecuta aquél» no es
|
||||
# una dep de build, así que no existe en el grafo: se descubre sólo cuando alguien va a usarlo.
|
||||
#
|
||||
# ══ CÓMO LO ENCONTRÉ (2026-09-11), que es la razón de que este fichero exista ═══════════════════
|
||||
# `recipes/protoc-gen-go.toml` llevaba meses sellado y el catálogo NO tenía `protoc`. Un plugin de
|
||||
# protoc sin protoc no hace absolutamente nada. No lo delató ninguna métrica: lo delató ir a escribir
|
||||
# la receta de un servicio gRPC y preguntarse con qué se generan los stubs.
|
||||
#
|
||||
# ══ QUÉ MIDE, Y POR QUÉ ASÍ ════════════════════════════════════════════════════════════════════
|
||||
# Para cada prefijo de la tabla DRIVERS, junta las recetas que empiezan con él y comprueba que el
|
||||
# **BINARIO** del driver exista dentro de algún artefacto sellado.
|
||||
#
|
||||
# · Se comprueba el BINARIO y no el nombre de la receta a propósito: el driver de `protoc-gen-*` es
|
||||
# `protoc`, que lo publica la receta `protobuf`. Preguntar «¿existe recipes/protoc.toml?» habría
|
||||
# contestado «no» incluso después de cerrarlo, y un vigía que grita cuando ya está arreglado se
|
||||
# empieza a ignorar — que es cómo se muere un guardián ([[guardianes-el-ruido-los-mata]]).
|
||||
# · La tabla es EXPLÍCITA y no una heurística sobre guiones. `git-cliff` es subcomando de git, pero
|
||||
# `gettext-tiny` no es subcomando de `gettext`. Adivinar por el guión da falsos positivos, y un
|
||||
# vigía con falsos positivos no se lee.
|
||||
#
|
||||
# ══ AUTOPRUEBA ═════════════════════════════════════════════════════════════════════════════════
|
||||
# `--autoprueba` corre los dos controles que hacen falta para creerle a un guardián: uno NEGATIVO
|
||||
# (esconder un driver que sí está ⇒ TIENE que saltar) y uno POSITIVO (sin tocar nada, los drivers
|
||||
# presentes NO deben aparecer). Un guardián que nunca falló no se sabe si sirve.
|
||||
#
|
||||
# Uso: scripts/vigia-subcomandos.py [--autoprueba]
|
||||
# Sale: 0 si no hay inertes · 1 si los hay · 2 si la autoprueba falla
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# prefijo de receta → (binario del driver, quién se supone que lo publica, para el mensaje)
|
||||
DRIVERS = {
|
||||
"cargo-": ("cargo", "el toolchain de Rust"),
|
||||
"kubectl-": ("kubectl", "una receta kubectl"),
|
||||
"git-": ("git", "recipes/git.toml"),
|
||||
"helm-": ("helm", "recipes/helm.toml"),
|
||||
"protoc-gen-": ("protoc", "recipes/protobuf.toml"),
|
||||
"docker-": ("docker", "una receta docker"),
|
||||
}
|
||||
|
||||
|
||||
def artefactos():
|
||||
"""Todos los directorios de artefacto del store, por nombre de receta.
|
||||
|
||||
⚠ El nombre se saca con una EXPRESIÓN ANCLADA, no con `glob('store/*-<n>')`: ese glob hace
|
||||
coincidencia por sufijo, así que `*-go` casa con `…-protoc-gen-go` y contesta que `go` está
|
||||
sellado cuando no lo está. Me pasó midiendo esto mismo."""
|
||||
out = {}
|
||||
for d in glob.glob(os.path.join(RAIZ, "store", "*")):
|
||||
m = re.match(r"^[0-9a-f]{64}-(.+)$", os.path.basename(d))
|
||||
if m and os.path.isdir(d):
|
||||
out.setdefault(m.group(1), []).append(d)
|
||||
return out
|
||||
|
||||
|
||||
def provee_binario(arts, nombre_bin, ocultar=()):
|
||||
"""¿Algún artefacto sellado trae un ejecutable llamado `nombre_bin`?"""
|
||||
for receta, dirs in arts.items():
|
||||
if receta in ocultar:
|
||||
continue
|
||||
for d in dirs:
|
||||
for sub in ("bin", "sbin", "usr/bin", "usr/sbin", "usr/local/bin"):
|
||||
p = os.path.join(d, sub, nombre_bin)
|
||||
if os.path.exists(p):
|
||||
return receta
|
||||
return None
|
||||
|
||||
|
||||
def barrido(arts, ocultar=()):
|
||||
"""→ lista de (prefijo, binario, quien_deberia, [recetas inertes])."""
|
||||
recetas = set()
|
||||
for patron in ("recipes/*.toml", "recipes/incoming*/*.toml"):
|
||||
for f in glob.glob(os.path.join(RAIZ, patron)):
|
||||
recetas.add(os.path.basename(f)[:-5])
|
||||
hallazgos = []
|
||||
for prefijo, (binario, quien) in sorted(DRIVERS.items()):
|
||||
subs = sorted(r for r in recetas if r.startswith(prefijo) and r != prefijo.rstrip("-"))
|
||||
if not subs:
|
||||
continue
|
||||
if provee_binario(arts, binario, ocultar) is None:
|
||||
hallazgos.append((prefijo, binario, quien, subs))
|
||||
return hallazgos
|
||||
|
||||
|
||||
def autoprueba():
|
||||
"""Control NEGATIVO + control POSITIVO. Sin los dos, no se sabe si el vigía mide algo."""
|
||||
arts = artefactos()
|
||||
ok = True
|
||||
|
||||
# NEGATIVO: escondemos `git`, que SÍ está y tiene subcomandos ⇒ el vigía tiene que verlo.
|
||||
quien_git = provee_binario(arts, "git")
|
||||
if quien_git is None:
|
||||
print(" ⚠ control negativo NO APLICABLE: `git` no está sellado en esta máquina")
|
||||
else:
|
||||
roto = [p for p, _b, _q, _s in barrido(arts, ocultar={quien_git})]
|
||||
if "git-" in roto:
|
||||
print(f" ✓ control NEGATIVO: escondiendo `{quien_git}`, el vigía canta `git-` ")
|
||||
else:
|
||||
print(f" ✗ control NEGATIVO FALLÓ: escondí `{quien_git}` y el vigía no dijo nada")
|
||||
ok = False
|
||||
|
||||
# POSITIVO: sin tocar nada, los drivers que SÍ están no deben aparecer. Un vigía que grita
|
||||
# sobre todo es igual de inútil que uno que nunca grita.
|
||||
presentes = [p for p, (b, _q) in DRIVERS.items() if provee_binario(arts, b)]
|
||||
ruidosos = [p for p, _b, _q, _s in barrido(arts) if p in presentes]
|
||||
if ruidosos:
|
||||
print(f" ✗ control POSITIVO FALLÓ: canta sobre drivers presentes: {ruidosos}")
|
||||
ok = False
|
||||
else:
|
||||
print(f" ✓ control POSITIVO: no canta sobre los {len(presentes)} drivers presentes "
|
||||
f"({', '.join(p.rstrip('-') for p in presentes)})")
|
||||
return ok
|
||||
|
||||
|
||||
def main():
|
||||
if "--autoprueba" in sys.argv:
|
||||
print("══ AUTOPRUEBA DEL VIGÍA ══")
|
||||
sys.exit(0 if autoprueba() else 2)
|
||||
|
||||
arts = artefactos()
|
||||
hallazgos = barrido(arts)
|
||||
print("══ SUBCOMANDOS SIN DRIVER ══")
|
||||
if not hallazgos:
|
||||
print(" ✓ 0 herramientas inertes: todos los drivers de la tabla están en el catálogo")
|
||||
sys.exit(0)
|
||||
total = 0
|
||||
for prefijo, binario, quien, subs in hallazgos:
|
||||
total += len(subs)
|
||||
print(f"\n ✗ falta el binario `{binario}` ({quien})")
|
||||
print(f" ⇒ {len(subs)} recetas selladas quedan INERTES:")
|
||||
for i in range(0, len(subs), 6):
|
||||
print(" " + " ".join(subs[i:i + 6]))
|
||||
print(f"\n TOTAL: {total} herramientas selladas que no se pueden invocar.")
|
||||
print(" No es deuda de BUILD (construyen y reproducen): es deuda de CATÁLOGO.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user