tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

why-differs-barrido: no llamar "no-reproducción" a lo que no está probado

El artefacto guarda su recipe.toml pero NO los hashes de sus deps, y el ArtifactHash sí los
incluye ⇒ dos sellados con el mismo recipe.toml pueden diferir LEGÍTIMAMENTE porque cambió una
dependencia. La primera versión gritaba "NO-REPRODUCCIÓN REAL" sobre 128 paquetes; eso era un
superconjunto, no una prueba.

Ahora separa por la CAUSA, que sí discrimina: si TODO lo que difiere son metadatos que un cambio
de dep no explica (MTIME de gzip, cabecera ar, ruta de build, secciones ELF informativas), es
no-determinismo probable; si difiere .text/.data o hay ficheros de más, es indistinguible de un
cambio de dep sin reconstruir. 128 → 92 probables + 36 no concluyentes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

a8bb83add5fe005262e255297b3b261a70075ad9 · ruwaq sergio · (3 killa ñawpaq) · tayta 6ad078d03 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

1 qillqakuna · +47 −11

diff --git a/scripts/why-differs-barrido.sh b/scripts/why-differs-barrido.sh
index 72182151..4f5d96ea 100755
--- a/scripts/why-differs-barrido.sh
+++ b/scripts/why-differs-barrido.sh
@@ -6,9 +6,22 @@ Comparar dos sellados del mismo nombre responde una pregunta que hasta ahora nad
 **¿por qué difieren?** Y sobre todo separa los dos casos que se confunden a ojo:
 
   · `.hammer/recipe.toml` DIFIERE  ⇒ son recetas distintas. Que el artefacto cambie es lo esperado.
-  · `.hammer/recipe.toml` IDÉNTICO ⇒ **misma receta, distinto artefacto = NO-REPRODUCCIÓN REAL.**
-    Eso es un bug de reproducibilidad, y el informe ya dice la causa (gzip mtime, cabecera ar,
-    sección ELF, ruta de build embebida…).
+  · `.hammer/recipe.toml` IDÉNTICO ⇒ misma receta, distinto artefacto: **sospechoso**.
+
+CUIDADO CON EL SEGUNDO CASO (y por qué este script no grita "no-reproducción"): el artefacto sólo
+guarda su `recipe.toml`, NO los hashes de sus dependencias — pero el ArtifactHash SÍ los incluye.
+Dos sellados con el mismo `recipe.toml` pueden diferir **legítimamente** porque una dep cambió
+(glib nuevo ⇒ gtk4 se re-sella con contenido distinto). "Mismo recipe.toml, artefacto distinto" es
+un SUPERCONJUNTO de las no-reproducciones, no una prueba.
+
+Lo que sí se puede afirmar mirando la CAUSA, y por eso el informe separa dos grupos:
+
+  · **no-determinismo probable** — todas las divergencias son metadatos que no dependen de las
+    deps: MTIME de gzip, cabecera `ar`, ruta de build embebida, o secciones ELF puramente
+    informativas (`.debug_*`, `.comment`, `.note.gnu.build-id`, tablas de símbolos). Una dep
+    distinta no explica que cambie SÓLO eso.
+  · **puede ser una dep** — difiere `.text`/`.data`/`.rodata` o hay ficheros presentes/ausentes.
+    Indistinguible de "la dependencia cambió" sin reconstruir. Reconstruir es la única prueba.
 
 Es el "multiplicador de granja" del SDD 17 §1.2: debuggear una no-reproducción deja de ser artesanal.
 
@@ -44,6 +57,21 @@ def sellados_por_nombre():
     return {n: sorted(v, key=lambda p: p.stat().st_mtime) for n, v in m.items() if len(v) >= 2}
 
 
+# Secciones ELF que son metadatos: su contenido no cambia porque una dependencia cambie.
+SECCIONES_META = (".debug", ".zdebug", ".comment", ".note", ".symtab", ".strtab", ".shstrtab")
+# Causas que, por sí solas, no las explica un cambio de dependencia.
+CAUSAS_META = {"gzip_mtime", "ar_cabecera", "ruta_de_build"}
+
+
+def solo_metadatos(d):
+    """¿Esta divergencia es explicable SÓLO por no-determinismo del build?"""
+    if d["causa"] in CAUSAS_META:
+        return True
+    if d["causa"] != "elf_secciones":
+        return False
+    return all(s.split(" ")[0].startswith(SECCIONES_META) for s in d["secciones"])
+
+
 def main():
     if not HAMMER.exists():
         sys.exit(f"falta {HAMMER} (cargo build --release --bin hammer)")
@@ -55,7 +83,8 @@ def main():
     print(f"== barrido why-differs: {len(nombres)} paquetes con ≥2 sellados\n")
 
     causas = collections.Counter()
-    no_reproducen = []      # misma receta, distinto artefacto ⇒ bug real
+    no_determinismo = []    # misma receta y sólo metadatos ⇒ el build no es determinista
+    quiza_dep = []          # misma receta pero difiere código ⇒ pudo cambiar una dependencia
     recetas_distintas = 0
     errores = []
 
@@ -79,13 +108,16 @@ def main():
             causas[d["causa"]] += 1
         if receta_cambio:
             recetas_distintas += 1
+        elif all(solo_metadatos(d) for d in divs):
+            no_determinismo.append((nombre, a.name[:12], b.name[:12], divs))
         else:
-            no_reproducen.append((nombre, a.name[:12], b.name[:12], divs))
+            quiza_dep.append((nombre, a.name[:12], b.name[:12], divs))
         if i % 50 == 0:
             print(f"   … {i}/{len(nombres)}")
 
     print(f"\n── recetas distintas (divergencia esperada): {recetas_distintas}")
-    print(f"── MISMA receta, artefacto distinto (NO REPRODUCE): {len(no_reproducen)}")
+    print(f"── misma receta, SÓLO metadatos difieren (no-determinismo probable): {len(no_determinismo)}")
+    print(f"── misma receta, difiere código/ficheros (puede ser una dep — no concluyente): {len(quiza_dep)}")
     if errores:
         print(f"── no analizados: {len(errores)} ({errores[:3]}…)")
 
@@ -93,9 +125,10 @@ def main():
     for c, n in causas.most_common():
         print(f"   {c:22s} {n}")
 
-    if no_reproducen:
-        print("\n══ NO-REPRODUCCIONES (misma receta ⇒ el artefacto debería ser idéntico):")
-        for nombre, ha, hb, divs in no_reproducen:
+    if no_determinismo:
+        print("\n══ NO-DETERMINISMO PROBABLE (misma receta; sólo difieren metadatos que las deps")
+        print("   no explican — reconstruir para confirmar):")
+        for nombre, ha, hb, divs in no_determinismo:
             print(f"\n   {nombre}  [{ha}… vs {hb}…]  {len(divs)} divergencia(s)")
             for d in divs[:4]:
                 print(f"     · {d['ruta']}: {d['explicacion'].splitlines()[0][:150]}")
@@ -106,8 +139,11 @@ def main():
         pathlib.Path(SALIDA_JSON).write_text(json.dumps({
             "causas": dict(causas),
             "recetas_distintas": recetas_distintas,
-            "no_reproducen": [
-                {"nombre": n, "a": a, "b": b, "divergencias": d} for n, a, b, d in no_reproducen
+            "no_determinismo": [
+                {"nombre": n, "a": a, "b": b, "divergencias": d} for n, a, b, d in no_determinismo
+            ],
+            "quiza_dep": [
+                {"nombre": n, "a": a, "b": b, "divergencias": d} for n, a, b, d in quiza_dep
             ],
         }, indent=2, ensure_ascii=False))
         print(f"\n   informe → {SALIDA_JSON}")