harkaq: el clasificador nunca propone runtime base como declarable (fix de raíz de busybox)

El bug que declaró `busybox` en 29 recetas y rompió binutils: harkaq-suggest veía
`/bin/busybox` denegado, encontraba `recipes/busybox.toml` en el store y decía
"declarar dep: busybox" — sin mirar que la política YA lo concede (`ro /bin/busybox`:
el sandbox corre `sh -c` y /bin/sh→busybox ⇒ es CONTRATO, no dep).

Fix: harkaq-suggest ahora lee los paths CONCEDIDOS de la política (`ro`/`rw`/`list`),
no sólo los `# expect`. Si un path está concedido ⇒ categoría RUNTIME BASE: no se
declara, aunque el store lo provea.

Las 4 clasificaciones verificadas tras el cambio:
  /bin/busybox              → RUNTIME BASE (ya concedido; NO declarar)   ← el fix
  /usr/bin/make             → DECLARABLE (declarar dep: make)
  /usr/lib/libstdc++.so     → IRREDUCIBLE (nadie lo provee)
  /usr/bin/gcc en broot     → DEUDA DE COMPILADOR (compiler=gcc)
  /usr/bin/gcc en zlib      → sin deuda (sonda esperada; zlib compila con zig)

Con esto puesto, busybox JAMÁS se habría declarado. Cierra el TODO que dejó el
revert: la campaña automática ya no puede cimentar contrato como dependencia.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:27:57 -04:00
co-authored by Claude Opus 4.8
parent d99ed58850
commit 83d40af35c
+20 -1
View File
@@ -101,11 +101,22 @@ def main():
usa_gcc = receta_toml and compiler_de(receta_toml) == "gcc"
esperadas = set()
# RUNTIME BASE: lo que la política ya CONCEDE (`ro`/`rw`/`list`). Si un path está acá, NO es
# deuda declarable aunque el store lo provea — es contrato del sandbox.
#
# Este bug costó caro: `/bin/busybox` está concedido (el sandbox corre `sh -c` y /bin/sh→
# busybox), pero como `recipes/busybox.toml` existe, el clasificador propuso "declarar dep:
# busybox" y la cosecha lo declaró en 29 recetas. Declararlo apila el busybox de hammer sobre
# el de Alpine y ROMPE el build (binutils: "cannot run C compiled programs"). Además va contra
# la Etapa C, que reemplaza busybox por uutils. Medir bien y accionar mal.
concedidos = set()
with open(politica) as f:
for l in f:
l = l.strip()
if l.startswith("# expect "):
esperadas.add(l[len("# expect "):].strip())
elif l[:3] in ("ro ", "rw ") or l.startswith("list "):
concedidos.add(l.split(None, 1)[1].strip())
# Si la receta compila con gcc, los frontends NO se perdonan: son la deuda de compilador.
if usa_gcc:
esperadas -= FRONTENDS_GCC
@@ -117,11 +128,15 @@ def main():
idx = indice_del_store(store)
recetas = catalogo(recipes_dir)
declarable, irreducible, compilador = {}, [], []
declarable, irreducible, compilador, base = {}, [], [], []
for p in deuda:
if usa_gcc and p in FRONTENDS_GCC:
compilador.append(p)
continue
# ¿Ya concedido por la política? ⇒ runtime base, NO se declara (ver arriba: busybox).
if p in concedidos or any(p.startswith(c.rstrip("/") + "/") for c in concedidos if c != "/"):
base.append(p)
continue
provs = idx.get(p)
if provs:
# El store es autoritativo: sabe qué ficheros aporta cada artefacto.
@@ -144,6 +159,10 @@ def main():
else:
irreducible.append(p)
if base:
print("RUNTIME BASE — ya concedido por la política; NO declarar (es contrato del sandbox):")
for p in base:
print(f" {p}")
if compilador:
print("DEUDA DE COMPILADOR (matar-gcc) — la receta usa el gcc de Alpine (compiler=gcc):")
for p in compilador: