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 604Rust 56Shell 36Markdown 281227 Definiciones

corpus: driver de reconstruccion — en serie, reanudable y por objetivo

Hace falta cada vez que cambie la imagen del lab, porque desde 58d3161 el
toolchain entra en hash_inputs y eso mueve el hash de TODO el corpus. Es el
precio explicito de cambiar de compilador; antes se pagaba sin enterarse.

EN SERIE bajo el flock compartido (ADR 0012): dos builds que compartan una
dep se pisan el arbol de fuentes y queda roto para siempre.

REANUDABLE sin estado propio: pregunta `hammer hash --check` (~2 ms) en vez
de llevar un fichero de progreso. Matarlo y relanzarlo continua donde iba.

POR OBJETIVO, no alfabetico: hay 1186 recetas y solo 168 alcanzan una imagen.
Ordenado por targets.toml primero, cada perfil que cierra es utilizable ya;
por orden alfabetico, tras un dia de maquina no habria ni una imagen.
Dos correcciones en el camino: usar los nodos de build-state marcaba 1161 de
1161 como prioritarias (ese grafo ES el corpus entero, no las clausuras), y
`glob('recipes/**/*.toml')` se dejaba 25 ficheros fuera — os.walk da 1186,
que es lo que cuenta `find`.

La purga de work/sources va ENTRE recetas, con nada corriendo. Purgar con un
build en vuelo le arranca los ficheros al compilador: paso el 2026-08-10 y se
llevo un kernel a medias.

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

05e8ba853b1013fef5f1e7a0927743fa282022d1 · por Sergio · (hace 2 meses) · padre 0de4bf08a · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

1 archivos · +138 −0

diff --git a/scripts/reconstruir-corpus.sh b/scripts/reconstruir-corpus.sh
new file mode 100755
index 00000000..511b260c
--- /dev/null
+++ b/scripts/reconstruir-corpus.sh
@@ -0,0 +1,138 @@
+#!/usr/bin/env bash
+# reconstruir-corpus.sh — reconstruye TODO lo que esté en deuda, en serie y de forma reanudable.
+#
+# ── CUÁNDO SE USA ───────────────────────────────────────────────────────────────────────────────
+# Cuando algo mueve el `ArtifactHash` de todo el corpus a la vez. Hoy eso es UNA cosa: cambiar la
+# imagen del lab (`scripts/lab-image.sh --crear` + pinear el sha nuevo), porque desde el 2026-08-10
+# el toolchain entra en `hash_inputs`. No es un accidente que sea caro: ése es el precio explícito
+# de cambiar de compilador, y antes se pagaba sin enterarse (dos labs sellaban bytes distintos en
+# la misma dirección).
+#
+# ── POR QUÉ EN SERIE, Y NO ES NEGOCIABLE ────────────────────────────────────────────────────────
+# `hammer build` comparte `work/sources/<dep>-<sha>`. Dos builds que compartan una dep se pisan el
+# árbol y queda ROTO PARA SIEMPRE (ADR 0012, sin decidir; ~93 de 205 recetas KDE murieron así al
+# invalidar libdrm). Todo el run va bajo UN `flock work/.farm-build.lock`, el mismo que toman los
+# scripts de la granja, así que también nos serializa con ellos y con cualquier otro agente.
+#
+# ── REANUDABLE POR CONSTRUCCIÓN ─────────────────────────────────────────────────────────────────
+# No lleva estado propio: la pregunta "¿esto ya está?" se la hace al store con `hammer hash --check`
+# (~2 ms, sin construir). Matarlo y relanzarlo continúa donde iba. Un run entero sobre un corpus ya
+# construido son unos segundos de puros --check.
+#
+# ── EL DISCO ES EL LÍMITE REAL, NO LA CPU ───────────────────────────────────────────────────────
+# Cada sellado añade un artefacto y cada build deja su árbol en `work/sources/`. Entre receta y
+# receta —con NADA corriendo, que es cuando es trivialmente seguro— se purgan los árboles si el
+# disco baja del umbral. Purgar CON un build en vuelo le arranca los ficheros al compilador: pasó
+# el 2026-08-10 y se llevó por delante un kernel a medias.
+#
+# Uso:   scripts/reconstruir-corpus.sh [--seco]
+# Env:   MIN_LIBRE_GB (def 25) umbral de purga · ABORT_GB (def 10) aborta antes de llenar el disco
+set -uo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+HAMMER="${HAMMER:-$ROOT/target/release/hammer}"
+STORE="${STORE:-./store}"
+LOCK="$ROOT/work/.farm-build.lock"
+LOG="${LOG:-$ROOT/work/reconstruccion.log}"
+MIN_LIBRE_GB="${MIN_LIBRE_GB:-25}"
+ABORT_GB="${ABORT_GB:-10}"
+SECO=0; [ "${1:-}" = "--seco" ] && SECO=1
+
+libre_gb() { df -BG --output=avail "$ROOT" 2>/dev/null | tail -1 | tr -dc 0-9; }
+ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
+
+# EL ORDEN IMPORTA, aunque las deps se resuelvan solas. `hammer build` construye recursivamente lo
+# que le falta, así que cualquier orden termina — pero no cualquier orden es útil a mitad de camino.
+# Hay 1186 ficheros de receta y sólo ~440 alcanzan alguna imagen; el resto son catálogo y colas de
+# staging. Construyendo por orden alfabético, tras un día de máquina no habría NI UNA imagen
+# completa. Se ordena por objetivo: primero las clausuras de los perfiles de `targets.toml`
+# (base, cli, escritorio-*), después todo lo demás. Así cada perfil que cierra es utilizable ya.
+ORDEN="$ROOT/work/reconstruccion-orden.txt"
+python3 - "$ORDEN" <<'PY'
+import json, os, sys, subprocess
+# Las raíces salen de `targets.toml` vía su expansor oficial (scripts/targets.py), NO de los nodos
+# de build-state: ese grafo es el corpus ENTERO, así que usarlo marcaba 1161 de 1161 como
+# prioritarias y el orden no ordenaba nada. `targets.py` da lo que una imagen pide por nombre.
+PERFILES = ["base", "cli", "escritorio-mirada", "escritorio-sway",
+            "escritorio-cosmic", "escritorio-kde", "escritorio-gnome"]
+prioritarios, orden_perfil = set(), []
+for p in PERFILES:
+    r = subprocess.run(["python3", "scripts/targets.py", p], capture_output=True, text=True)
+    if r.returncode != 0:
+        continue
+    nombres = [l.strip() for l in r.stdout.split() if l.strip()]
+    orden_perfil.append((p, len(nombres)))
+    prioritarios.update(nombres)
+# La CLAUSURA (las libs de las que cuelgan las raíces) se saca del grafo siguiendo las aristas.
+d = json.load(open("docs/state/build-state.json"))
+nodos = d.get("nodes", {})
+pila, vistos = list(prioritarios), set()
+while pila:
+    x = pila.pop()
+    if x in vistos:
+        continue
+    vistos.add(x)
+    pila += nodos.get(x, {}).get("deps", [])
+prioritarios = vistos
+# os.walk y no glob: `recipes/**/*.toml` se dejaba 25 ficheros fuera y no se nota mirando el total.
+todas = []
+for dirpath, _, files in os.walk("recipes"):
+    todas += [os.path.join(dirpath, f) for f in files if f.endswith(".toml")]
+todas.sort()
+# El nombre del artefacto es el campo `name`, no el del fichero (17 recetas difieren), pero para
+# ORDENAR alcanza el basename: equivocarse acá sólo cambia la prioridad, nunca la corrección.
+pri = [f for f in todas if os.path.basename(f)[:-5] in prioritarios]
+resto = [f for f in todas if os.path.basename(f)[:-5] not in prioritarios]
+with open(sys.argv[1], "w") as fh:
+    fh.write("\n".join(pri + resto) + "\n")
+print(f"   orden: {len(pri)} de perfil primero, {len(resto)} de catálogo después")
+PY
+mapfile -t RECETAS < "$ORDEN"
+echo "== $(ts) reconstrucción: ${#RECETAS[@]} recetas · libre $(libre_gb) G" | tee -a "$LOG"
+
+if [ "$SECO" = "1" ]; then
+  pend=0
+  for f in "${RECETAS[@]}"; do
+    "$HAMMER" --store "$STORE" hash "$f" --check >/dev/null 2>&1 || pend=$((pend+1))
+  done
+  echo "   en deuda: $pend de ${#RECETAS[@]}" | tee -a "$LOG"
+  exit 0
+fi
+
+exec 9>"$LOCK"
+echo "== esperando el lock de build…" | tee -a "$LOG"
+flock 9
+echo "== $(ts) lock tomado" | tee -a "$LOG"
+
+ok=0; falla=0; cache=0; i=0
+for f in "${RECETAS[@]}"; do
+  i=$((i+1))
+  n="$(basename "$f" .toml)"
+
+  if "$HAMMER" --store "$STORE" hash "$f" --check >/dev/null 2>&1; then
+    cache=$((cache+1)); continue
+  fi
+
+  l=$(libre_gb)
+  if [ "${l:-0}" -lt "$ABORT_GB" ]; then
+    echo "!! $(ts) ABORTO: quedan ${l} G (< ${ABORT_GB}). Sellado $ok, fallo $falla." | tee -a "$LOG"
+    exit 1
+  fi
+  if [ "${l:-0}" -lt "$MIN_LIBRE_GB" ]; then
+    # Seguro por construcción: acá NO hay ningún build corriendo (somos el único, y en serie).
+    echo "   [$(ts)] libre ${l} G < ${MIN_LIBRE_GB} ⇒ purgo work/sources" | tee -a "$LOG"
+    rm -rf work/sources/* 2>/dev/null
+    echo "   libre tras purgar: $(libre_gb) G" | tee -a "$LOG"
+  fi
+
+  t0=$(date +%s)
+  if "$HAMMER" --store "$STORE" build "$f" >>"$ROOT/work/reconstruccion-detalle.log" 2>&1; then
+    ok=$((ok+1)); estado=OK
+  else
+    falla=$((falla+1)); estado=FALLA
+  fi
+  printf '%s [%4d/%4d] %-6s %-40s %4ds  libre %sG\n' \
+    "$(ts)" "$i" "${#RECETAS[@]}" "$estado" "$n" "$(( $(date +%s) - t0 ))" "$(libre_gb)" | tee -a "$LOG"
+done
+
+echo "== $(ts) FIN · sellado $ok · fallo $falla · ya-estaba $cache · libre $(libre_gb) G" | tee -a "$LOG"