Una tanda volvió a llenar el libro de veredictos falsos: 47 recetas sanas anotadas como
NO-DETERMINISMO. Esta vez la cadena empezó antes de donde yo había mirado.
El `mv` que APARTA el artefacto falló (el directorio de apartado no estaba) y yo nunca comprobaba que
hubiera funcionado. A partir de ahí todo lo que sigue es basura: el artefacto se queda en su sitio,
`hammer why-differs` compara contra una ruta que no existe y devuelve `Error: store: no existe…`, y
mi código leía cualquier salida no-cero como «difieren». Un error de comparación disfrazado de
veredicto, 47 veces.
Es la MISMA lección que arreglé hace un rato para el build, en un segundo sitio que no miré:
**no poder medir no es un resultado negativo**. Dos guardas nuevas:
· el apartado se comprueba (`mv || SIN VEREDICTO`) y la receta se salta ruidosamente;
· `why-differs` se lee, no sólo su exit code: si dice `Error:`/`no existe`, es SIN VEREDICTO, se
restaura el artefacto y NO se anota nada.
Probadas las dos: con el apartadero sin permiso de escritura sale «no pude apartar el artefacto ⇒
SIN VEREDICTO», el artefacto queda intacto y el libro no crece.
Comprobado además que la tanda mala no perdió NADA: como el `mv` fallaba, los 47 artefactos nunca
salieron del store (`faltan: 0`). El daño fue sólo el registro, y está purgado — el libro queda con
51 verificaciones buenas.
La regla, ya por triplicado hoy: cuando un guardián no puede establecer algo, el estado es *sin
veredicto*, y eso no se escribe. Un libro que anota lo que no midió se cree igual que uno que sí.
18 recetas C verificadas de una, en unos nueve minutos, 15 REPRODUCEN y 3 eran DERIVA (ya al día).
Cero no-determinismos. Y `work/sources` se quedó en 4 KB toda la tanda gracias a la poda por
veredicto: antes una tanda así llevaba el disco del 95% al 98%.
Lo que hace sostenible a esta y no a la anterior es la SELECCIÓN. El filtro `MAX_SECONDS` sobre los
tiempos del worker no transfiere a Rust —`zola` se comió 40 minutos y no dio un solo veredicto— pero
en C sí: sin caché de cargo de por medio, un build de 40 s allá son 40 s acá. Lista explícita de
clase `c`, no muestreo ciego.
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.
`verificar-repro.sh` sorteaba una muestra y se olvidaba. Sin registro no había forma de contestar la
pregunta que importa —¿qué fracción del corpus se comprobó alguna vez que reproduce?— y encima las
mismas recetas salían sorteadas una y otra vez mientras otras no se miraban jamás. La primera medida
con el libro puesto: **2 de 1157**. El invariante central del proyecto no se había verificado de
forma acumulativa prácticamente en nada, y eso no se veía porque cada corrida daba «✅ PUERTA
SUPERADA» sobre su propia muestra.
`docs/state/repro-verificado.tsv` va con la misma clave auto-invalidante que el libro de fuzz:
(receta, **ArtifactHash**). El hash resume la receta MÁS su cierre de deps, así que una entrada deja
de casar en cuanto algo aguas arriba cambia. Un «ya lo verifiqué» que sobreviviera a un re-hash sería
una mentira; éste no puede serlo.
Con eso el muestreo pasa a sortear entre lo NO verificado, así que corridas sucesivas ACUMULAN
cobertura en vez de repetir (con `TODO=1` se sortea entre todas, para re-verificar a propósito), y
`--coverage` da el número sin construir nada.
Verificadas en esta tanda: itstool, markupsafe, musl-obstack, hicolor-icon-theme, musl-fts, npth,
poppler-render-check, doas, libffi y packaging REPRODUCEN; `when` derivaba y quedó al día. Cero
no-determinismos. Van 11 de 1157.
⚠ Y una trampa que casi me come, anotada acá porque el próximo la va a pisar: elegí candidatos «por
artefacto chico» y la lista empezó con **nodejs**, cuyo artefacto pesa 0,2 MB y cuyo build son horas
de V8. El tamaño del artefacto NO dice nada del costo de construirlo. Para elegir muestra barata hay
que mirar tiempos de build, no bytes de salida.