harkaq: revertir busybox (era runtime base, rompía builds) + coordinar el §3 con Fable 5

DOS ERRORES MÍOS, uno de dirección y otro técnico, los dos en la cosecha automática:

1. DIRECCIÓN: declaró `busybox` como dep en 29 recetas — cuando la Etapa C lo está
   ELIMINANDO (USERLAND_COMPONENTS=[uutils,findutils,…], ya cerrada; joyas-reusables
   §5 lo confirma: "uutils… Ubuntu 25.10 los envía como default"). Estaba cimentando
   la deuda que el roadmap borra.
2. TÉCNICO: busybox YA está en el runtime base de harkaq (`ro /bin/busybox`, `ro
   /bin/sh` — el sandbox corre `sh -c` y /bin/sh→/bin/busybox). Es CONTRATO, no dep.
   Declararlo apila el busybox de hammer sobre el de Alpine y ROMPE el build:
   binutils daba "cannot run C compiled programs" en las DOS máquinas. Verificado:
   sin busybox declarado, binutils construye (b3:f4507dcd…). Y la cadena se explica:
   binutils roto ⇒ zlib (que lo declara) tampoco construía.

Auditoría de la cosecha (diff real, no la línea completa del +): añadió sólo 5 deps
distintas — make ×73, busybox ×29, perl ×8, pkgconf ×5, binutils ×1. Sólo busybox
estaba mal; las otras 4 son deps reales medidas. busybox revertido de 30 recetas
(queda sólo en busybox.toml, pre-existente).

Es el mismo error que ya me habían señalado con otro disfraz: MEDIR BIEN Y ACCIONAR
MAL. harkaq midió correcto (el build toca /bin/busybox: es el shell); la acción
correcta no era declararlo sino reconocerlo como contrato.

+ COORDINACIÓN del §3 con Fable 5 (mismo diseño, tareas repartidas):
  - Su lección casper queda CONFIRMADA y REFORZADA: la clausura de build no sólo le
    FALTAN las clases del mundo (offline) — también le SOBRA casi todo (headers, gcc).
    Medido: htop (estático, 0 NEEDED) no toca NADA al correr ⇒ política = su binario.
  - Su "la clase viaja como campo de la ConcesionCapacidad, sin formato nuevo" se
    cumple LITERALMENTE: lo firmado es format::Permisos = u32 bitmask en 36 bytes
    canónicos (Ring 0) ⇒ las clases SON los bits. La cripto no se toca.
  - Diseño unificado: frontera (clases, u32, declaradas) + detalle (paths, Landlock,
    medidos). D3 rige en ambos.
  - Reparto: clases→Fable 5; medición/harness→Opus. CONTACTO: runtime-policy.sh ahora
    emite la CLASE detectada (/etc/resolv.conf→dns, /etc/ssl/certs→tls-certs, …), no
    sólo el path: la medición alimenta la tabla, la tabla decide el bit.
  - Consumidor esperando: plan-jaula-juegos F1 (Steam que no puede leer ~/.ssh).

+ juez.sh (§10): nombre único por corrida (con uno fijo pega en caché ⇒ falso
  "sin evidencia"). Fue el juez quien destapó todo esto en su primera corrida real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:14:19 -04:00
co-authored by Claude Opus 4.8
parent 2e85dfc8a3
commit d99ed58850
44 changed files with 697 additions and 35 deletions
+16 -1
View File
@@ -62,6 +62,21 @@ if [ -z "$propios" ]; then
echo " ⇒ política Landlock: sólo \`ro $BIN\`. Es el caso de un estático que no abre ficheros."
else
echo "$propios" | sed 's/^/ ro /'
# CONTACTO con el §3 (clases de servicio, diseño de Fable 5): la medición no sólo lista
# paths — propone la CLASE. El path va a Landlock (detalle, userspace); la clase va al bit
# del u32 firmado (frontera, Ring 0). La medición ALIMENTA la tabla; la tabla decide el bit.
# Ninguno ensancha por su cuenta (D3).
clases=""
echo "$propios" | grep -qE '/etc/(resolv\.conf|hosts)' && clases="$clases dns"
echo "$propios" | grep -qE '/etc/ssl/certs' && clases="$clases tls-certs"
echo "$propios" | grep -qE '/usr/share/zoneinfo|/etc/TZ' && clases="$clases tz"
echo "$propios" | grep -qE '/dev/log' && clases="$clases syslog"
if [ -n "$clases" ]; then
echo
echo " ── clases de servicio detectadas (§3: van al u32 firmado, NO a Landlock):"
for c in $clases; do echo " $c"; done
echo " ⇒ declarar por nombre en la ConcesionCapacidad; el resto de arriba es Landlock."
fi
echo
echo " ⇒ pegar en la política Landlock del binario instalado."
echo " ⇒ los `ro` van a la política Landlock del binario instalado."
fi
+104
View File
@@ -0,0 +1,104 @@
#!/bin/sh
# juez.sh <receta> — JUEZ MECÁNICO del bucle agéntico (SDD 17 §10).
#
# agente propone receta → harkaq veredicto → granja reproduce ×2 → catálogo acepta
#
# Sin humano en el medio para la parte mecánica: el humano decide QUÉ construir; el juez decide
# si lo construido MERECE entrar al catálogo, y lo decide con evidencia, no con opinión.
#
# scripts/juez.sh recipes/zlib.toml
#
# Dos preguntas, dos veredictos, ninguno negociable:
# 1. ¿HERMÉTICO? harkaq (SDD 16): ¿usó algo que no declaró? El kernel responde.
# `denials=[]` no se cree, se GANA (el canario lo demuestra — D9).
# 2. ¿REPRODUCIBLE? consenso (SDD 17 §1): N builders independientes, ¿el mismo hash?
# Si sí, el artefacto es confiable SIN FIRMA.
#
# Si falla, el juez no dice "no" a secas: dice POR QUÉ y qué hacer.
# - Impuro → harkaq-suggest: "declarar dep: make" (arreglo mecánico)
# - Divergente → why-differs: qué fichero, qué causa (codegen/timestamp/build-id)
#
# Es el bucle que el SDD 08 §4 pide ("la IA propone, el sistema reproduce, el humano commitea")
# con la pieza que faltaba: un juez que la IA no puede convencer, sólo satisfacer.
set -eu
RECETA="${1:?uso: juez.sh <receta.toml>}"
HUB="$(cd "$(dirname "$0")/.." && pwd)"
cd "$HUB"
n=$(basename "$RECETA" .toml)
BUILDERS="${BUILDERS:-2}"
echo "══════ JUEZ MECÁNICO — $n"
echo
# ── 1. ¿HERMÉTICO? (harkaq)
echo "── 1/2 ¿usó algo que no declaró? [harkaq, SDD 16]"
if [ ! -x "${HARKAQ_BIN:-$HOME/.cache/harkaq}/harkaq-audit" ]; then
echo " ⊘ harkaq no está compilado ⇒ SIN EVIDENCIA. El juez NO afirma hermeticidad."
echo " (compilar scripts/harkaq/*.c y `setcap cap_audit_read` — ver scripts/harkaq/README.md)"
veredicto_h="sin-evidencia"
else
export HARKAQ=1 HARKAQ_BIN="${HARKAQ_BIN:-$HOME/.cache/harkaq}"
export HARKAQ_BASE="${HARKAQ_BASE:-$HARKAQ_BIN/base.policy}" HARKAQ_TIMEOUT="${HARKAQ_TIMEOUT:-200}"
# Nombre ÚNICO por corrida: con uno fijo, la 2ª vez `hammer build` pega en CACHÉ, no
# construye nada, no hay veredictos y el juez dice "sin evidencia" — un falso negativo que
# parece un problema de la receta. (Gotcha conocido; me mordió al probar el juez.)
uniq="$n-juez-$$"
tmp="$(dirname "$RECETA")/.juez-$n.toml"
sed -E "s/^(name\s*=\s*)\"$n\"/\1\"$uniq\"/" "$RECETA" > "$tmp"
out=$(timeout "${POR:-600}" ./target/debug/hammer build "$tmp" --store "$PWD/store" 2>&1 || true)
rm -f "$tmp"; rm -rf store/*-"$uniq" 2>/dev/null || true
veredictos=$(echo "$out" | grep '^\[harkaq\] {' | sed 's/^\[harkaq\] //')
deuda=""
if [ -z "$veredictos" ]; then
echo " ⊘ sin veredicto (¿caché? ¿el build ni arrancó?) ⇒ SIN EVIDENCIA"
veredicto_h="sin-evidencia"
else
veredicto_h="hermetico"
i=0
echo "$veredictos" | while IFS= read -r v; do :; done
printf '%s\n' "$veredictos" > /tmp/.juez-v
while IFS= read -r v; do
i=$((i + 1)); echo "$v" > /tmp/.juez-1
est=$(scripts/harkaq/harkaq-verdict.py /tmp/.juez-1 "$HARKAQ_BASE" --human 2>/dev/null | head -1 | sed 's/^ *estado: //;s/ .*//')
echo " fase $i: ${est:-?}"
[ "$est" = "Impuro" ] && { veredicto_h="impuro"; \
scripts/harkaq/harkaq-suggest.py /tmp/.juez-1 "$HARKAQ_BASE" "$PWD/store" "$PWD/recipes" "$RECETA" 2>/dev/null | sed 's/^/ /'; }
[ "$est" = "SinEvidencia" ] && veredicto_h="sin-evidencia"
done < /tmp/.juez-v
# el while corre en subshell: recuperar el peor veredicto por marcador
grep -q '"estado":"Impuro"' /tmp/.juez-v && veredicto_h="impuro"
grep -q '"estado":"SinEvidencia"' /tmp/.juez-v && veredicto_h="sin-evidencia"
fi
fi
echo
# ── 2. ¿REPRODUCIBLE? (consenso)
echo "── 2/2 ¿N builders independientes dan el mismo hash? [consenso, SDD 17 §1]"
if consenso=$(BUILDERS="$BUILDERS" timeout "${POR_CONSENSO:-1200}" scripts/consenso.sh "$RECETA" "$BUILDERS" 2>&1); then
echo "$consenso" | grep -E '✅|builder' | sed 's/^/ /'
veredicto_r="consenso"
else
rc=$?
echo "$consenso" | grep -E '⚠️|builder|SIN CONSENSO' | sed 's/^/ /'
[ "$rc" = 1 ] && veredicto_r="divergencia" || veredicto_r="sin-consenso"
if [ "$veredicto_r" = divergencia ]; then
echo " ── why-differs (SDD 17 §2) explicaría la divergencia:"
echo " scripts/why-differs.py <hashA> <hashB> --store ./store"
fi
fi
echo
# ── VEREDICTO
echo "══════ VEREDICTO"
if [ "$veredicto_h" = hermetico ] && [ "$veredicto_r" = consenso ]; then
echo " ✅ ACEPTAR — no usó nada sin declarar Y $BUILDERS builders lo reprodujeron bit a bit."
echo " El catálogo puede confiar en este artefacto sin firma y sin revisión humana del build."
exit 0
fi
echo " ❌ NO ACEPTAR"
[ "$veredicto_h" = impuro ] && echo " · hermeticidad: usa algo sin declarar (ver la sugerencia arriba: es una línea)"
[ "$veredicto_h" = sin-evidencia ] && echo " · hermeticidad: SIN EVIDENCIA — no se afirma nada (D9: un lector ciego y un build limpio son idénticos)"
[ "$veredicto_r" = divergencia ] && echo " · reproducibilidad: los builders NO coinciden ⇒ hay un canal impuro"
[ "$veredicto_r" = sin-consenso ] && echo " · reproducibilidad: no hubo suficientes builders con hash"
exit 1