#!/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.
#
# ── ⚠ QUÉ MIDE ESTE SCRIPT, EXACTAMENTE (leer antes de sacar conclusiones) ─────────────────────
# Compara el artefacto GUARDADO en el store contra una reconstrucción de hoy. Eso NO es lo mismo que
# medir no-determinismo, y confundirlos costó dos conclusiones equivocadas en el SDD 23:
#   · si el artefacto guardado es viejo, se construyó con OTRO estado del lab ⇒ una diferencia es
#     DERIVA («el mundo se movió»), no no-determinismo;
#   · el no-determinismo es: mismas entradas, MISMO lab, salidas distintas.
# ⇒ Para medir NO-DETERMINISMO hay que correr el script DOS VECES: la primera pone el artefacto al
#   día con el lab actual, y es la SEGUNDA la que contesta la pregunta. Medido: anew, gron y age
#   divergían en la primera corrida y REPRODUCEN en la segunda.
#
# ── 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 ))
