repro: «la 2ª reconstrucción no construyó» NO es no-determinismo — mi propio bug, y lo pagó el libro

Corriendo una tanda con `MAX_SECONDS=45` se llenó el disco a mitad, los rebuilds empezaron a morir
por ENOSPC, y la rama que yo mismo había escrito para distinguir DERIVA de NO-DETERMINISMO metió
«falló» y «difiere» en la misma condición ⇒ **21 recetas sanas quedaron anotadas como
NO-DETERMINISMO** en un libro que pretende ser autoritativo. Un registro falso y persistente es peor
que no tener registro: el propósito del libro es que nadie tenga que volver a mirar, así que una
entrada equivocada se cree para siempre.

Es la trampa de siempre —un disco lleno se lee como receta rota, y la pista es que mueren varias
seguidas— y el script ORIGINAL ya la evitaba: «no es lo mismo «no reproduce» que «no construye
acá». Distinguirlos importa: mezclarlos inventaría un problema de determinismo». Lo rompí al añadir
la segunda reconstrucción. Restituido: si la 2ª no construye, se restaura el artefacto, se cuenta
como fallo y **no se anota nada** — sin veredicto es un estado legítimo.

Reparado además el daño: las 21 entradas falsas purgadas del libro (quedan 27 verificaciones
buenas), y `angle-grinder` repuesto desde `store/.divergen/`, donde lo había dejado la clasificación
errónea. Eso sí funcionó como debía: el ejemplar estaba guardado y entero, no perdido.

Y queda anotado en el propio script el límite del filtro por tiempo que provocó todo: el sidecar de
`store/.times/` lo escribe el WORKER, así que acota el build de esa receta EN LA MÁQUINA QUE LO
MIDIÓ. Una receta Rust de 40 s allá con la caché de cargo caliente son muchos minutos acá en frío
—`angle-grinder` y `amp` entraron por el filtro y se comieron el disco— y encima el número no cuenta
la CASCADA de deps que falten en el store local, que es la misma sorpresa que dio `gjs`. Con disco
justo conviene pasar las recetas a mano.
This commit is contained in:
Sergio
2026-09-05 20:05:33 +00:00
parent 118ba2e2c3
commit d7d9cf8fd2
2 changed files with 73 additions and 2 deletions
+18
View File
@@ -17,3 +17,21 @@ npth e5682fca87a9f591d533855b0056a07b5724304fb53402c940524d885908d980 2026-09-05
poppler-render-check 0d29dc69792df003726252fbc381a0133df7654daafccf547f8d5a82e526c1fe 2026-09-05 reproduce
when 5b4048a23584aaaca41a7d8c0e50a92c469b1b1710f5d10b2dd7abd908fe57b6 2026-09-05 reproduce (tras poner al día un artefacto derivado)
doas 28bc42c24008f73d9541f407562cd4ed81e3788b95ab72fa00b5369ad606064a 2026-09-05 reproduce
zstd 1ceb6215a5a3061ac385e036da642341da61a585abe62a1c904bd0c8c2f4d5fb 2026-09-05 reproduce
actionlint 12e0640aa73729d170d992eb442bcca6b9d7b891a074d2d8b9f6583710717be3 2026-09-05 reproduce
addlicense e918fed592bc9eac2989262f030dba05e4dbdb32ab9ff715abe1f05bf9e688d3 2026-09-05 reproduce
adwaita-cursors 22ae0ff6369e6f15cb6810ddd979d95ebe38307e8da668c1210d9434ef9b5b05 2026-09-05 reproduce
adwaita-hello 09cf170c6a780bd09f46347c61aa48a16eccda81e27aee42184938242b026918 2026-09-05 reproduce
age b8ae28a4e360b61aa53667af7c6261951085285e62c2201f615173731ca36716 2026-09-05 reproduce
agora-cli e05c41f1d4e46974195fbfb76a3efb591217e6733be8e17b007ba5d9cc3a153d 2026-09-05 reproduce
air 3761c6a516ab0bbcae825625e10c0fbd8015a15f25080e3b7dc36bfb7597d59f 2026-09-05 reproduce
ali 9017cf46ef5dc33c726a2fe4adf91c7463bee749ce605a94757eeea51f260c06 2026-09-05 reproduce
zoxide 83359c57babdd2e051b7c53bd63694d319969cd9bcb5a27922707655f7b572af 2026-09-05 reproduce
alsa-lib 73fb200aae06b0743035dc9f961121fc94295e80a12d39dc358920d698b7bfe0 2026-09-05 reproduce
alterx 415465a247da8489bfec54eac84d8bb2234e822423ba717974cbcbedfeb8905d 2026-09-05 reproduce
amass 385b4a140f8e8fa04282494f01e7523625aff42ae90f6eb38cd0230e6914aa56 2026-09-05 reproduce
amfora 10560630b5d8302979b7844a22d2e30696a046c834968bd713efa7fcbc2b993a 2026-09-05 reproduce
amp 7b800350e79dda56eb1c3fca511700761cd91b2cd49ebf58f453fb4a01a62944 2026-09-05 reproduce (tras poner al día un artefacto derivado)
anew 68ef0da2c17b474bee65caec2f61e4b24ccd5ea8e2d8357540ce572482bb5782 2026-09-05 reproduce
npth e5682fca87a9f591d533855b0056a07b5724304fb53402c940524d885908d980 2026-09-05 reproduce
musl-fts b9681a5205197d51bb777efde1a271e9b189bf35d74091834c5f1aa43d82f16e 2026-09-05 reproduce
1 # repro-verificado.tsv — qué artefactos se comprobaron que REPRODUCEN bit a bit, y cuándo.
17 poppler-render-check
18 when
19 doas
20 zstd
21 actionlint
22 addlicense
23 adwaita-cursors
24 adwaita-hello
25 age
26 agora-cli
27 air
28 ali
29 zoxide
30 alsa-lib
31 alterx
32 amass
33 amfora
34 amp
35 anew
36 npth
37 musl-fts
+55 -2
View File
@@ -143,6 +143,47 @@ 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).
# ── `MAX_SECONDS`: sortear sólo entre builds BARATOS ─────────────────────────────────────────
# Elegir muestra «por artefacto chico» es una trampa: el artefacto de `nodejs` pesa 0,2 MB y su
# build son horas de V8. Lo que hace cara una verificación es el TIEMPO DE CONSTRUIR, y ese dato
# ya existe — `scripts/farm/build-timed.sh` deja un sidecar por build en `store/.times/` y llega
# con la cosecha. Con `MAX_SECONDS=60` la muestra sale de ahí.
#
# Las recetas SIN medición quedan fuera cuando se pone el tope: no se puede prometer que algo es
# barato sin haberlo medido nunca. El modo de ampliar esa lista no es relajar el filtro sino
# correr builds instrumentados en el worker, que es donde el tiempo se mide.
#
# ⚠ DOS LÍMITES DEL NÚMERO, medidos el 2026-09-05 con `MAX_SECONDS=45` en el hub:
# · El sidecar lo escribe el WORKER. Una receta Rust que allá tarda 40 s con la caché de cargo
# caliente puede tardar muchos minutos acá en frío — `angle-grinder` y `amp` entraron por el
# filtro y se comieron el disco. El tope acota el build DE ESA RECETA en LA MÁQUINA QUE LO
# MIDIÓ, no acá.
# · Y no cuenta la CASCADA: `hammer build` reconstruye las deps que falten en el store local, y
# eso no está en el tiempo del sidecar. Es la misma sorpresa que dio `gjs`, cuyo grafo decía
# «una receta» y se puso a rehacer spidermonkey entero.
# ⇒ con disco justo, mejor pasar las recetas a mano que confiar en el tope.
if [ -n "${MAX_SECONDS:-}" ]; then
TODOS=$(python3 - "$MAX_SECONDS" <<'PY'
import glob, json, os, sys, re
from statistics import median
cap = float(sys.argv[1])
m = {}
for f in glob.glob("store/.times/*.json"):
try: d = json.load(open(f))
except Exception: continue
if d.get("name") and isinstance(d.get("seconds"), (int, float)):
m.setdefault(d["name"], []).append(d["seconds"])
baratas = {k for k, v in m.items() if median(v) < cap}
for ruta in glob.glob("recipes/*.toml") + glob.glob("recipes/incoming-wlr/*.toml"):
txt = open(ruta, encoding="utf8", errors="replace").read(4000)
mm = re.search(r'^name\s*=\s*"([^"]+)"', txt, re.M)
if mm and mm.group(1) in baratas:
print(ruta)
PY
)
[ -n "$TODOS" ] || { echo "── no hay recetas medidas por debajo de ${MAX_SECONDS}s (¿falta store/.times?)"; exit 0; }
fi
# Se sortea entre lo que NO está en el libro, para que corridas sucesivas ACUMULEN cobertura en
# vez de repetir las mismas. Con `TODO=1` se sortea entre todas (para re-verificar a propósito).
TODOS=$(ls recipes/*.toml recipes/incoming-wlr/*.toml 2>/dev/null)
@@ -187,8 +228,20 @@ for f in $RECETAS; do
# que estaba fuera de fecha era el guardado ⇒ deriva. Si no coinciden, es no-determinismo de
# verdad. Sólo se paga el rebuild extra cuando ya hubo una diferencia.
mv "$STORE/$D" "$GUARDA/$D.r1"; PENDIENTE="$PENDIENTE $D.r1"
if "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 && [ -d "$STORE/$D" ] \
&& "$HAMMER" why-differs "$GUARDA/$D.r1" "$STORE/$D" >/dev/null 2>&1; then
# ⚠ «la segunda reconstrucción FALLÓ» NO es «difiere». Meterlas en la misma rama fue un error
# mío del 2026-09-05 y lo pagó el libro: se llenó el disco a mitad de una tanda, los rebuilds
# empezaron a morir por ENOSPC, y quedaron anotadas 21 recetas sanas como NO-DETERMINISMO —
# un registro falso y persistente, que es peor que no tener registro. Es la misma trampa de
# siempre: un disco lleno se lee como receta rota, y la pista es que mueren varias seguidas.
# El script original ya distinguía «no reproduce» de «no construye acá»; esto lo restituye.
if ! "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 || [ ! -d "$STORE/$D" ]; then
printf " ⚠ %-26s la 2ª reconstrucción no construyó ⇒ SIN VEREDICTO (no lo anoto)\n" "$n"
fallo=$((fallo+1))
rm -rf "$STORE/$D"; mv "$GUARDA/$D" "$STORE/$D"; rm -rf "$GUARDA/$D.r1"
PENDIENTE=$(echo "$PENDIENTE" | sed "s| $D.r1||;s| $D||")
continue
fi
if "$HAMMER" why-differs "$GUARDA/$D.r1" "$STORE/$D" >/dev/null 2>&1; then
printf " ~ %-26s DERIVA (el guardado era viejo; dos reconstrucciones coinciden)\n" "$n"
deriva=$((deriva+1))
# Se anota igual: las dos reconstrucciones coincidieron entre sí, que es justo lo que