Los llamadores EJECUTABLES: scripts/ (incluida toda la granja), los runbooks y CLAUDE.md. Seguro porque la 3a ya garantiza que el worker emite los dos binarios, y porque en farm-lab-sync.sh el cargo build remoto precede a la invocación remota en el mismo script. Verificado: sintaxis de los 49 (bash -n / py_compile — ojo que why-differs-barrido.sh es Python con extensión .sh) y `takana hash` devuelve hash real sobre el store. NO se toca en esta etapa, a propósito: - La variable de entorno HAMMER=. Es interfaz de los scripts entre sí y hay llamadores que la fijan; renombrarla va con la etapa 4. - docs/evidencia/ y los HANDOFF: son REGISTRO de lo que se corrió ese día. Reescribir un comando dentro de una evidencia la falsifica. - docs/state/: es generado, se regenera solo. - Los ADR y los docs de diseño: texto, y `hammer` sigue funcionando. Van con la etapa 5, que es la de churn de texto.
101 lines
4.3 KiB
Bash
Executable File
101 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# test-guardianes-wasm.sh — le pone delante a los guardianes de la cadena wasm las formas conocidas
|
|
# de romperla y exige que las maten. Y corre un CONTROL con el sysroot intacto que tiene que PASAR.
|
|
#
|
|
# POR QUÉ EXISTE. Un guardián que nunca falló no se sabe si sirve, y uno que falla SIEMPRE se ve
|
|
# idéntico a uno que funciona: por eso el control no es un extra, es la mitad de la prueba. El
|
|
# método lo aportó la sesión hammer-f8 (su scripts/test-atuq-politica.py hace lo mismo con el cruce
|
|
# política↔XPI).
|
|
#
|
|
# NO CONSTRUYE NADA, NO TOMA EL LOCK Y NO ESCRIBE EN EL STORE: trabaja sobre copias temporales de
|
|
# artefactos ya sellados, así que se puede correr con la granja a pleno.
|
|
#
|
|
# ./scripts/test-guardianes-wasm.sh [--store DIR]
|
|
set -u
|
|
STORE="./store"
|
|
[ "${1:-}" = "--store" ] && STORE="$2"
|
|
LAB="./.dev-fs/alpine"
|
|
TMP="${TMPDIR:-/tmp}/guardianes-wasm.$$"
|
|
ok=0; mal=0
|
|
|
|
di() { printf '%s\n' "$*"; }
|
|
fin() { rm -rf "$TMP"; }
|
|
trap fin EXIT
|
|
|
|
# ⚠ EL ARTEFACTO SE RESUELVE POR EL HASH DE LA RECETA, NUNCA POR GLOB. La primera versión de este
|
|
# script hacía `ls -d "$STORE"/*-wasi-libcxx | head -1` y agarró el artefacto VIEJO —el de antes de
|
|
# que la receta creara el directorio-sonda— así que el CONTROL falló y los cuatro sabotajes
|
|
# "pasaron". Es la misma trampa que el cache-hit que congela regresiones: un artefacto anterior se
|
|
# hace pasar por el actual y la prueba mide otra cosa. Y es, además, la demostración de para qué
|
|
# sirve el control: sin él eso se habría leído como "4 en verde, guardianes ok".
|
|
art() {
|
|
h=$(./target/release/takana --store "$STORE" hash "recipes/$1.toml" 2>/dev/null | tail -1)
|
|
h=${h#b3:}
|
|
[ -n "$h" ] && [ -d "$STORE/$h-$1" ] && printf '%s' "$STORE/$h-$1"
|
|
}
|
|
libc=$(art wasi-libc)
|
|
cxx=$(art wasi-libcxx)
|
|
[ -n "$libc" ] && [ -n "$cxx" ] || { di "el store no tiene el artefacto VIGENTE de wasi-libc/wasi-libcxx (¿sin construir?) — nada que probar"; exit 77; }
|
|
di "artefactos vigentes: $(basename "$libc" | cut -c1-12)… / $(basename "$cxx" | cut -c1-12)…"
|
|
[ -d "$LAB" ] || { di "sin lab en $LAB"; exit 77; }
|
|
|
|
# Arma un sysroot fusionado como el que ve firefox, y le aplica el sabotaje que se le pida.
|
|
armar() {
|
|
rm -rf "$TMP/s"; mkdir -p "$TMP/s"
|
|
cp -a "$libc"/usr/share/wasi-sysroot/. "$TMP/s"/
|
|
cp -a "$cxx"/usr/share/wasi-sysroot/. "$TMP/s"/
|
|
case "${1:-}" in
|
|
sin-sonda) rm -rf "$TMP/s/include/c++/v1" ;;
|
|
sin-libcxx) rm -f "$TMP/s"/lib/*/libc++.a ;;
|
|
sin-libc) rm -f "$TMP/s"/lib/*/libc.a ;;
|
|
sin-cstring) find "$TMP/s/include" -name cstring -delete ;;
|
|
intacto) : ;;
|
|
esac
|
|
}
|
|
|
|
# La prueba EXACTA que hace el configure de firefox, dentro del lab.
|
|
probar_cxx() {
|
|
bwrap --overlay-src "$LAB" --tmp-overlay / --proc /proc --dev /dev --tmpfs /tmp \
|
|
--ro-bind "$TMP/s" /sysroot --unshare-all --die-with-parent \
|
|
/bin/sh -c 'echo "#include <cstring>" > /tmp/p.cpp
|
|
clang++ -std=gnu++20 --target=wasm32-wasip1 /tmp/p.cpp --sysroot=/sysroot -c -o /tmp/p.o' \
|
|
>/dev/null 2>&1
|
|
}
|
|
|
|
# --- EL CONTROL. Va PRIMERO a propósito: si esto no pasa, los demás resultados no significan nada,
|
|
# porque un guardián que mata todo se ve igual que uno que discrimina.
|
|
armar intacto
|
|
if probar_cxx; then
|
|
di "✓ CONTROL sysroot intacto -> compila (el guardián NO es un «mata siempre»)"; ok=$((ok+1))
|
|
else
|
|
di "✗ CONTROL sysroot intacto -> NO compila. El resto de esta prueba no vale nada."; mal=$((mal+1))
|
|
fi
|
|
|
|
# --- LOS SABOTAJES. Cada uno es una forma conocida de romper la cadena; todos tienen que matar.
|
|
for caso in sin-sonda sin-cstring; do
|
|
armar "$caso"
|
|
if probar_cxx; then
|
|
di "✗ SABOTAJE $caso -> compiló igual. El guardián NO cubre este caso."; mal=$((mal+1))
|
|
else
|
|
di "✓ SABOTAJE $caso -> muere, como debe"; ok=$((ok+1))
|
|
fi
|
|
done
|
|
|
|
# --- Los de presencia pura, que son los que el caso `sin-sonda` demostró insuficientes por sí solos.
|
|
for caso in sin-libcxx sin-libc; do
|
|
armar "$caso"
|
|
falta=0
|
|
for t in wasm32-wasip1 wasm32-wasip1-threads; do
|
|
[ -s "$TMP/s/lib/$t/libc++.a" ] && [ -s "$TMP/s/lib/$t/libc.a" ] || falta=1
|
|
done
|
|
if [ "$falta" = 1 ]; then
|
|
di "✓ SABOTAJE $caso -> el test de presencia lo caza"; ok=$((ok+1))
|
|
else
|
|
di "✗ SABOTAJE $caso -> el test de presencia NO lo caza"; mal=$((mal+1))
|
|
fi
|
|
done
|
|
|
|
di ""
|
|
di "resumen: $ok en verde, $mal en rojo"
|
|
[ "$mal" = 0 ] || exit 1
|