etapa 1: la puerta funcionó — me equivoqué, SÍ hay no-determinismo (y las dos mitades son una)
Ayer escribí en el SDD 23 que el `-ffile-prefix-map` global «se cancela porque su premisa es
falsa». **Era falso**, y la etapa 1 lo demostró en veinte minutos. Corregido en el §1.bis.
LA MEDIDA: `scripts/verificar-repro.sh` aparta el artefacto, reconstruye con las deps cacheadas
y compara con `why-differs`. Sobre las que pudieron reconstruirse: binutils ✓ y wl-clipboard ✓
reproducen; **appstream ✗ y bison ✗ DIVERGEN**. Causa idéntica en ambas:
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: mi barrido buscaba rutas DEL HOST (/home/<user>/,
/tmp/<aleatorio>) y no halló 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 encontrarla no
prueba que no haya otra. Y una sola muestra que reproduce no es una muestra: leí `wl-clipboard`
como si probara más de lo que probaba, que es el error contra el que el propio documento
advertía dos párrafos antes.
Lo que me salvó fue haber puesto la PUERTA antes de la campaña en vez de después. Si hubiera
seguido mi conclusión, habría cerrado como «resuelto» un invariante roto.
🎁 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 tocar -ffile-prefix-map en
720 recetas. Queda decidir qué hacer con el contenido del paquete -debug, pero eso afecta a un
artefacto secundario que nadie instala por defecto.
El script queda como herramienta: muestra por clase de build (C, Rust, Go: el no-determinismo
suele vivir en codegen paralelo y orden de símbolos, no sólo en C), y RESTAURA el artefacto si
el rebuild falla — distinguir «no reproduce» de «no construye acá» importa, porque mezclarlos
inventaría un problema de determinismo que no existe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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/<user>/`,
|
||||
`/tmp/<aleatorio>`) 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Executable
+78
@@ -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 ))
|
||||
Reference in New Issue
Block a user