Files
takana/scripts/farm/harvest-harkaq.sh
T
Sergio aacee245d6 takana: el worker pasa a /opt/takana y takana-farm.service
Renombrado el directorio del worker y la unit, con las tres units del repo
(farm, deadman service y timer). Todas las rutas /opt/hammer del hub pasan a
/opt/takana: eran defaults REMOTE= y HAMMER_DIR=, mas los systemctl/journalctl
que nombraban la unit sin el sufijo .service, que el primer sed no casaba.

Orden, para que no quedara partido: el hub siembra por rsync a una ruta fija,
asi que se saco el cron ANTES de mover. Si se movia el worker con el hub
apuntando a la ruta vieja, la siguiente cosecha recreaba /opt/hammer y quedaban
dos arboles.

Verificado de punta a punta:
- el worker cerro un ciclo de build REAL desde /opt/takana (dunst sellado, 1/1)
- una cosecha completa del hub contra la ruta nueva: siembra, manifiesto, los
  nueve grafos, static-audit (691 estaticos, MIENTEN 0) y estado pusheado
- la unit vieja quedo deshabilitada y borrada; /opt tiene una sola entrada

NO se tocan dos referencias a /opt/hammer que siguen siendo CORRECTAS:
docs/23-plan-rehasheo.md describe rutas EMBEBIDAS en artefactos ya construidos
—que literalmente dicen /opt/hammer/work en sus secciones .debug— y el HANDOFF
de la noche de KDE es registro. Cambiarlas haria que los documentos mientan.
2026-09-09 20:20:16 +00:00

140 lines
7.2 KiB
Bash
Executable File

#!/bin/sh
# harvest-harkaq.sh — COSECHA-ONLY de la campaña harkaq (modo desatendido, para cron en el hub).
# Determinista, sin IA. Hermano de harvest-go.sh y con la misma disciplina: no elige, no diagnostica,
# no compila en el hub; sólo convierte en commit lo que el worker YA midió.
#
# Uso (cron): cd /home/sergio/hammer && scripts/farm/harvest-harkaq.sh root@<ip> >>work/harvest-harkaq.log 2>&1
#
# POR QUÉ EXISTE ASÍ: la lista de "recetas a las que les falta declarar make" NO se puede sacar de
# un regex. Se intentó y falló dos veces en la misma tarde: `\bmake\b` colaba `cargo-make` (el guión
# es límite de palabra) y la versión estrecha perdía LAS CINCO recetas donde harkaq había medido la
# deuda de verdad (`compile = "make …"`: el carácter previo es una comilla). El número bailó 69→45→99
# según el retoque. La fuente de verdad es la MEDICIÓN, no la conjetura — que es exactamente para lo
# que existe harkaq. Este script sólo aplica lo que el kernel denunció.
#
# Ciclo (idempotente, seguro de re-correr):
# 1. git pull --rebase (otro agente pushea al mismo main)
# 2. baja los veredictos del worker
# 3. por cada veredicto: harkaq-suggest contra el store COMPLETO del hub (el del worker es parcial
# y marcaría como irreducible lo que sí es declarable — el worker MIDE, el hub CLASIFICA)
# 4. si la deuda es DECLARABLE ⇒ añade la dep a la receta. Si es IRREDUCIBLE ⇒ NO la toca: va a
# tandas/needs-review-harkaq/ con el motivo. Declarar algo que el store no provee rompería el
# build en vez de arreglarlo.
# 5. commit + push con `git add` EXPLÍCITO (nunca -A: respeta al otro agente)
set -u
HUB="$(cd "$(dirname "$0")/../.." && pwd)"
# Ruta absoluta a MÍ MISMO capturada ANTES del `cd`: se usa para re-ejecutarse bajo el lock, y un
# `$0` relativo dejaría de resolver en cuanto cambiamos de directorio.
YO="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
cd "$HUB"
WORKER="${1:?uso: harvest-harkaq.sh root@<ip>}"
# LOCK: dos cosechas a la vez editarían recetas y harían `git commit` en paralelo = árbol
# corrupto. El harvest es lento (re-indexa el store por veredicto), así que un ciclo puede pisar
# al siguiente. flock con -n: si ya hay una corriendo, esta sale limpia en vez de solaparse.
LOCK="$HUB/work/.harvest-harkaq.lock"
# ⚠ `flock -o` y re-exec, NO `exec 9>` (medido 2026-09-08): el lock lo sostiene el fd y los hijos
# lo heredan, así que un `ssh` o un `rsync` fugado de esta cosecha lo retendría DESPUÉS de que el
# script termine y todas las cosechas siguientes saldrían por «otra cosecha en curso» — mudas y
# para siempre. Con `-o` el fd se cierra antes de ejecutar y sólo el proceso `flock` lo sostiene.
# `-E 77` separa «estaba ocupado» de «el trabajo falló». Ver la regla 1 de CLAUDE.md.
if [ -z "${HARVEST_HARKAQ_CON_LOCK:-}" ]; then
export HARVEST_HARKAQ_CON_LOCK=1
rc=0; flock -n -E 77 -o "$LOCK" "$YO" "$@" || rc=$?
if [ "$rc" = 77 ]; then
echo "$(date -u +%FT%TZ) otra cosecha en curso, salto este ciclo"
exit 0
fi
exit "$rc"
fi
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
REMOTE="${REMOTE:-/opt/takana}"
SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes"
REVIEW="tandas/needs-review-harkaq"
mkdir -p "$REVIEW" work
echo "═══ $(date -u +%FT%TZ) cosecha harkaq desde $WORKER"
git pull --rebase -q 2>/dev/null || echo " (pull falló; sigo con lo local)"
VERD=work/harkaq-verdicts
rm -rf "$VERD"; mkdir -p "$VERD"
rsync -az -e "$SSH" "$WORKER:$REMOTE/work/harkaq-campana/" "$VERD/" 2>/dev/null || {
echo " sin veredictos que cosechar (¿el worker sigue midiendo?)"; exit 0; }
tocadas=""
for v in "$VERD"/*.verdicts; do
[ -f "$v" ] || continue
n=$(basename "$v" .verdicts)
[ -f "recipes/$n.toml" ] || continue
deps=""; irr=0
while IFS= read -r j; do
[ -n "$j" ] || continue
echo "$j" > /tmp/.hkv.json
s=$(scripts/harkaq/harkaq-suggest.py /tmp/.hkv.json "$HOME/.cache/harkaq/base.policy" \
"$HUB/store" "$HUB/recipes" 2>/dev/null) || true
echo "$s" | grep -q IRREDUCIBLE && irr=1
# "→ declarar dep: X" (el store lo provee y sabe cuál artefacto es).
d=$(echo "$s" | sed -n 's/.*→ declarar dep: \([a-zA-Z0-9._-]*\).*/\1/p')
deps="$deps $d"
done < "$v"
deps=$(echo "$deps" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' ')
[ -n "$deps" ] || continue
if [ "$irr" = 1 ]; then
# NO se toca: declarar algo que el store no provee rompe el build en vez de arreglarlo.
cp "$v" "$REVIEW/$n.verdicts"
echo " ?? $n — deuda irreducible ⇒ a needs-review (deps declarables vistas:$deps)"
continue
fi
if python3 scripts/farm/harkaq-add-deps.py "recipes/$n.toml" $deps; then
echo " ++ $n — declaradas:$deps"
tocadas="$tocadas recipes/$n.toml"
fi
done
# Recoger también las recetas editadas por un ciclo ANTERIOR que no llegó a commitear (el proceso
# murió, SIGPIPE, la máquina se apagó). Sin esto quedan huérfanas para siempre: al re-correr,
# add-deps dice "ya estaban declaradas", `tocadas` sale vacío y nadie las commitea nunca. Pasó en
# el primer ciclo real: 13 recetas editadas y sin committear. Se filtra por `recipes/*.toml` y por
# que el diff SÓLO toque la línea `build = [...]`, para no arrastrar trabajo de otro agente.
for f in $(git diff --name-only -- 'recipes/*.toml' 2>/dev/null); do
case " $tocadas " in *" $f "*) continue ;; esac
# ¿el diff toca SÓLO `[deps]`/`build =`? Se ignoran las líneas en blanco añadidas/quitadas
# (`^[+-]\s*$`): add-deps mete un `\n` tras el bloque y ese `+` vacío hacía que el filtro
# rechazara recetas legítimas (flex, gawk…) como "de otro agente". Y el mensaje usa comillas
# SIMPLES: con dobles, el `[deps].build` entre backticks se ejecutaba como comando.
if git diff -U0 -- "$f" | grep -E '^[-+]' | grep -vE '^(---|\+\+\+)' \
| grep -vE '^[-+]\s*$' | grep -qvE '^[-+]\s*(build\s*=|\[deps\])'; then
echo ' ·· '"$f"' — el diff toca algo que no es [deps].build: NO lo toco (¿otro agente?)'
else
tocadas="$tocadas $f"
echo " ↻ $f — pendiente de un ciclo anterior, se commitea ahora"
fi
done
if [ -n "$tocadas" ]; then
# `git add` EXPLÍCITO por rutas: el repo lo comparte otro agente (cola gráfica/Go).
# shellcheck disable=SC2086
git add $tocadas
git commit -q -m "harkaq: declarar deps medidas por el kernel (cosecha automática)
Cosecha desatendida de la campaña harkaq. Cada dep de acá la denunció el audit de
Landlock en un build real: el kernel vio al build usarla sin declararla, y el
store del hub confirma qué artefacto la provee (deuda DECLARABLE, §4.5).
Recetas:$(echo "$tocadas" | tr ' ' '\n' | grep -v '^$' | sed 's|recipes/||;s|\.toml||' | tr '\n' ' ')
No se tocó ninguna receta con deuda IRREDUCIBLE: declarar algo que el store no
provee rompería el build en vez de arreglarlo. Esas van a $REVIEW.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>" && git push -q 2>/dev/null || {
git pull --rebase -q && git push -q; }
echo " ✓ commit+push de $(echo "$tocadas" | wc -w) receta(s)"
else
echo " nada que declarar en este ciclo"
fi