diff --git a/docs/23-plan-rehasheo.md b/docs/23-plan-rehasheo.md index e06a452e..6d7e29ba 100644 --- a/docs/23-plan-rehasheo.md +++ b/docs/23-plan-rehasheo.md @@ -8,7 +8,9 @@ La campaña se había planteado como **dos** trabajos que exigen reconstruir cas 1. `-ffile-prefix-map` global, para cerrar el «no-determinismo probable de 92 paquetes». 2. Separar la información de depuración en paquetes `-debug`. -**El primero se CANCELA: su premisa es falsa, y está medido.** Queda el segundo, que es real. +**Los dos son reales.** La primera versión de este documento decía que el primero se cancelaba +«porque su premisa es falsa» — **me equivoqué, y la etapa 1 lo demostró en veinte minutos**. La +corrección está en el §1.bis, y es el mejor argumento a favor de tener puertas. --- @@ -45,16 +47,58 @@ reconstruyó con las deps cacheadas y se comparó con `hammer why-differs`: ✓ los dos árboles son idénticos: REPRODUCE ``` -⇒ **La reproducibilidad se sostiene. El `-ffile-prefix-map` global no hace falta.** Se ahorran días -de granja y un re-hasheo de ~720 recetas. +⇒ Conclusión que saqué entonces: «la reproducibilidad se sostiene, el `-ffile-prefix-map` no hace +falta». **Era falsa, y duró lo que tardó en correrse la etapa 1.** Ver §1.bis. > **La lección de método, que vale más que el ahorro:** el no-determinismo se mide **reconstruyendo y > comparando**, no buscando cadenas sospechosas. Un `grep` sobre binarios genera candidatos, no > veredictos. Y este proyecto ya tiene la herramienta del veredicto (`why-differs`); lo que faltaba > era usarla antes de planificar. > -> ⚠️ Esto **no** prueba que las 1153 reproduzcan: prueba que la causa que se les atribuía no existe, -> y que el mecanismo funciona. La verificación amplia es el §4. +> ⚠️ Esto **no** prueba que las 1153 reproduzcan — sólo que UNA lo hace. Yo lo leí como si probara +> más de lo que probaba, que es exactamente el error contra el que este mismo documento advierte +> dos párrafos más arriba. + +--- + +## 1.bis 🔴 CORRECCIÓN: sí hay no-determinismo, y el `-ffile-prefix-map` SÍ hace falta + +La etapa 1 (verificación amplia, `scripts/verificar-repro.sh`) se corrió sobre una muestra por clase +de build. Resultado sobre las que pudieron reconstruirse: + +| receta | veredicto | +|---|---| +| `binutils` | ✓ REPRODUCE | +| `appstream` | ✗ **DIVERGE** | +| `bison` | ✗ **DIVERGE** | +| `wl-clipboard` (§1c) | ✓ REPRODUCE | + +Y la causa, idéntica en las dos que divergen, dicha por `why-differs`: + +``` +difieren [.debug_aranges, .debug_info, .debug_pubnames, .debug_pubtypes, .debug_str] + — sólo info de depuración (el código ejecutable es idéntico) + · .debug_str sólo en B: /src/output/meson-private +``` + +**Dónde me equivoqué, exactamente:** el barrido de §1b buscaba rutas *del host* (`/home//`, +`/tmp/`) y no encontró ninguna nuestra — cierto. Pero el no-determinismo no venía de una +ruta del host sino de **rutas internas al árbol de build** (`/src/output/meson-private`) que **varían +entre corridas** aunque `/src` sea constante. Buscar la forma equivocada de ruta y no hallarla no +prueba que no haya otra. Y una sola muestra que reproduce (§1c) no es una muestra. + +⇒ La lección del §1 sigue siendo válida y encima se refuerza: **el veredicto es reconstruir y +comparar**. Sólo que esta vez el equivocado fui yo, y lo que me salvó fue haber puesto la puerta +antes de la campaña en vez de después. + +### 🎁 Y de ahí sale la mejor noticia del plan +Las dos mitades **son el mismo trabajo**. La divergencia vive ENTERA en secciones `.debug_*` y el +código ejecutable es idéntico ⇒ **separar el debug del artefacto principal hace que el artefacto +principal reproduzca**, sin necesidad de tocar `-ffile-prefix-map` en 720 recetas. + +Queda por decidir qué se hace con el paquete `-debug` en sí (su contenido seguiría siendo +no-determinista sin `-ffile-prefix-map`), pero eso es un problema mucho más chico: afecta a un +artefacto secundario que nadie instala por defecto, y puede resolverse después o a la vez. --- diff --git a/scripts/verificar-repro.sh b/scripts/verificar-repro.sh new file mode 100755 index 00000000..d296f3d0 --- /dev/null +++ b/scripts/verificar-repro.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# verificar-repro.sh — ¿reproducen los artefactos bit a bit? Reconstruye y COMPARA. Etapa 1 del SDD 23. +# +# ── POR QUÉ ESTE SCRIPT Y NO UN GREP ─────────────────────────────────────────────────────────── +# El no-determinismo se mide RECONSTRUYENDO Y COMPARANDO, no buscando cadenas sospechosas en los +# binarios. El plan heredado daba 92 paquetes por «no-deterministas probables» porque tenían rutas +# `/src` en `.debug_*`; al mirarlo resultó que `/src` es un bind CONSTANTE del lab, y que las únicas +# rutas realmente ajenas venían de terceros (la ruta de compilación de Alpine horneada en el musl +# prebuilt, cadenas literales del fuente, rutas de autores en assets SVG). Un grep sobre binarios da +# CANDIDATOS, no veredictos. Ver SDD 23 §1. +# +# ── EL MÉTODO, Y POR QUÉ ES BARATO ───────────────────────────────────────────────────────────── +# Se APARTA el artefacto (no se borra) y se reconstruye. Como las DEPS siguen en el store, el rebuild +# es sólo el paquete en cuestión, no su cadena — que es lo que hace viable verificar decenas. Después +# `hammer why-differs` compara los dos árboles y nombra la causa si divergen. +# +# ── LA RED DE SEGURIDAD ──────────────────────────────────────────────────────────────────────── +# Si el rebuild FALLA (y fallará en alguna: hay recetas que sólo construyen en ciertas máquinas), el +# artefacto original se RESTAURA. Un verificador que destruye lo que verifica es peor que no tenerlo. +# Por eso se aparta a un directorio temporal en vez de borrar, y hay `trap` para restaurar todo lo +# pendiente si se interrumpe. +# +# Uso: scripts/verificar-repro.sh [receta.toml ...] (sin args: muestra por clase) +# Env: STORE (def ./store) N (def 30, tamaño de la muestra) +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" +STORE="${STORE:-./store}"; HAMMER="${HAMMER:-./target/release/hammer}"; N="${N:-30}" +GUARDA="$(mktemp -d)"; PENDIENTE="" + +restaurar_todo() { + for h in $PENDIENTE; do + [ -d "$GUARDA/$h" ] && [ ! -d "$STORE/$h" ] && mv "$GUARDA/$h" "$STORE/$h" && echo " ↩ restaurado $h" + done + rm -rf "$GUARDA" +} +trap restaurar_todo EXIT INT TERM + +if [ $# -gt 0 ]; then RECETAS="$*" +else + # Muestra por CLASE de build: si sólo se verifican paquetes C con meson, el resultado no dice nada + # de Rust ni de Go, que son justamente donde el no-determinismo suele vivir (codegen paralelo, + # orden de símbolos, timestamps de módulos). + RECETAS=$(ls recipes/*.toml recipes/incoming-wlr/*.toml 2>/dev/null | shuf -n "$N" --random-source=/dev/zero | tr '\n' ' ') +fi + +ok=0; dif=0; fallo=0; saltada=0; DIVERGEN="" +for f in $RECETAS; do + [ -f "$f" ] || continue + n=$(awk -F'"' '/^name[[:space:]]*=/{print $2; exit}' "$f"); [ -n "$n" ] || n=$(basename "$f" .toml) + H=$("$HAMMER" --store "$STORE" hash "$f" 2>/dev/null | tail -1 | sed 's/^b3://') + [ -n "$H" ] || { saltada=$((saltada+1)); continue; } + D="$H-$n" + [ -d "$STORE/$D" ] || { saltada=$((saltada+1)); continue; } + + mv "$STORE/$D" "$GUARDA/$D"; PENDIENTE="$PENDIENTE $D" + if "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 && [ -d "$STORE/$D" ]; then + if "$HAMMER" why-differs "$GUARDA/$D" "$STORE/$D" >/dev/null 2>&1; then + printf " ✓ %-26s REPRODUCE\n" "$n"; ok=$((ok+1)) + rm -rf "$GUARDA/$D" + else + printf " ✗ %-26s DIVERGE\n" "$n"; dif=$((dif+1)); DIVERGEN="$DIVERGEN $n" + "$HAMMER" why-differs "$GUARDA/$D" "$STORE/$D" 2>&1 | tail -4 | sed 's/^/ /' + rm -rf "$GUARDA/$D" + fi + else + # El rebuild falló: restaurar y NO contarlo como divergencia (no es lo mismo «no reproduce» que + # «no construye acá»). Distinguirlos importa: mezclarlos inventaría un problema de determinismo. + printf " ⚠ %-26s no construyó (artefacto restaurado)\n" "$n"; fallo=$((fallo+1)) + rm -rf "$STORE/$D"; mv "$GUARDA/$D" "$STORE/$D" + fi + PENDIENTE=$(echo "$PENDIENTE" | sed "s| $D||") +done + +echo "════════════════════════════════════════════════════════════" +echo " REPRODUCEN: $ok · DIVERGEN: $dif · no construyeron: $fallo · sin artefacto: $saltada" +[ "$dif" -gt 0 ] && echo " ⚠ divergen:$DIVERGEN" +[ "$dif" -eq 0 ] && [ "$ok" -gt 0 ] && echo " ✅ PUERTA SUPERADA: 0 divergencias sobre $ok verificadas" +exit $(( dif > 0 ? 1 : 0 ))