granja: versionar la flota permanente, y acotar el commit del cron
'.fleet' está gitignored con razón —los workers efímeros entran y salen, y el reaper lo reescribe en cada ciclo— pero eso tenía un coste que nadie había pagado: un hub recién clonado nacía con la flota VACÍA y la granja quedaba desconectada EN SILENCIO. La cosecha decía 'flota vacía' cada 30 min, los artefactos del worker no volvían, estado-granja.sh reportaba 'no hay worker vivo' con el worker compilando, y nada fallaba. Así se descubrió esto hoy, por casualidad. Se separa lo que debe sobrevivir a un clon (scripts/farm/flota-permanente, versionado) de lo que es estado de ejecución (.fleet). cosecha-cron siembra .fleet desde el fichero fijo al empezar cada ciclo, uniendo por nombre. Probado con la siembra extraída del script real: hub recién clonado (.fleet ausente) → queda dev.gioser.net ← el caso que rompía con un hworker-3 efímero ya dentro → conviven los dos, sin duplicar ejecutada dos veces más → idempotente, sigue 1 línea por worker comentarios del fichero versionado → 0 se cuelan Y de paso el commit del propio cron pasa a ir acotado por pathspec. Hacía 'git add <rutas>' + 'git commit' a secas, que es justo lo que la regla 2 de CLAUDE.md declara insuficiente: el índice es compartido y el commit se lleva el índice entero. Pesa más acá que en ningún sitio porque corre desatendido cada 30 min mientras hay agentes trabajando. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
2 archivos · +30 −1
diff --git a/scripts/farm/cosecha-cron.sh b/scripts/farm/cosecha-cron.sh
index b56f6db4..5c63489b 100755
--- a/scripts/farm/cosecha-cron.sh
+++ b/scripts/farm/cosecha-cron.sh
@@ -64,6 +64,16 @@ fi
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
REMOTE="${REMOTE:-/opt/hammer}"
FLEET="$ROOT/scripts/farm/.fleet"
+# ── SIEMBRA DE LA FLOTA FIJA ────────────────────────────────────────────────────────────────────
+# `.fleet` es estado de EJECUCIÓN (gitignored, lo reescribe el reaper cada ciclo); `flota-permanente`
+# es la parte versionada. Sin esto, un hub recién clonado nace con la flota vacía y la granja queda
+# desconectada EN SILENCIO: «flota vacía» cada 30 min, los artefactos del worker no vuelven, y nada
+# falla. Se unen por NOMBRE (el fijo gana) para que lo efímero de hcloud conviva sin duplicarse.
+FLEET_FIJA="$ROOT/scripts/farm/flota-permanente"
+if [ -s "$FLEET_FIJA" ]; then
+ { grep -vE '^[[:space:]]*(#|$)' "$FLEET_FIJA"; cat "$FLEET" 2>/dev/null; } \
+ | awk 'NF && !visto[$1]++' > "$FLEET.seed" && mv "$FLEET.seed" "$FLEET"
+fi
HAMMER="${HAMMER:-$ROOT/target/release/hammer}"
# Workers efímeros reciclan IPs ⇒ no fijar known_hosts (hub-and-spoke, worker sin secretos).
SSH="ssh -i $SSH_KEY -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o BatchMode=yes -o LogLevel=ERROR -o ConnectTimeout=15"
@@ -361,7 +371,11 @@ fi
if [ "${NO_COMMIT:-}" != "1" ]; then
git add docs/state/sonames.txt docs/state/build-state.json docs/state/build-state-kde.json docs/state/build-state-gnome.json docs/state/build-state-cosmic.json docs/state/build-state-wlr.json docs/state/drenaje.json docs/state/fuentes-vigia.json docs/state/kernel-contract.txt docs/state/static-audit.txt 2>/dev/null || true
if ! git diff --cached --quiet 2>/dev/null; then
- git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" 2>/dev/null \
+ # ⚠ ACOTADO POR PATHSPEC (`-- <rutas>`), no sólo por el `git add` de arriba: el índice es
+ # estado COMPARTIDO y `git commit` a secas commitea el índice ENTERO, así que lo que otro
+ # agente dejara en `git add` viajaría dentro del commit del cron. Es la regla 2 de CLAUDE.md,
+ # y acá pesa más que en ningún lado: esto corre desatendido cada 30 min mientras se trabaja.
+ git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" -- docs/state/sonames.txt docs/state/build-state.json docs/state/build-state-kde.json docs/state/build-state-gnome.json docs/state/build-state-cosmic.json docs/state/build-state-wlr.json docs/state/drenaje.json docs/state/fuentes-vigia.json docs/state/kernel-contract.txt docs/state/static-audit.txt 2>/dev/null \
&& { git push -q origin main 2>/dev/null && echo "==> estado commiteado+pusheado" \
|| echo "==> estado commiteado (push falló, reintenta próximo ciclo)"; }
else
diff --git a/scripts/farm/flota-permanente b/scripts/farm/flota-permanente
new file mode 100644
index 00000000..51b4f68b
--- /dev/null
+++ b/scripts/farm/flota-permanente
@@ -0,0 +1,15 @@
+# flota-permanente — los workers FIJOS de la granja, versionados.
+#
+# POR QUÉ EXISTE (2026-09-08). `scripts/farm/.fleet` está en `.gitignore` y con razón: los workers
+# efímeros de hcloud entran y salen, y el reaper lo REESCRIBE en cada ciclo. Pero eso tenía un coste
+# que nadie había pagado todavía: **un hub recién clonado nacía con la flota VACÍA y la granja
+# quedaba desconectada EN SILENCIO** — `cosecha-cron` decía «flota vacía» cada 30 min, los
+# artefactos del worker no volvían nunca, `estado-granja.sh` reportaba «no hay worker vivo» con el
+# worker compilando, y NADA fallaba. Así se descubrió, por casualidad.
+#
+# Este fichero es la parte que SÍ debe sobrevivir a un clon: los workers permanentes. `cosecha-cron`
+# siembra `.fleet` desde acá al empezar cada ciclo (unión sin duplicar por nombre), así que lo
+# efímero sigue viviendo sólo en `.fleet` y lo fijo se repone solo.
+#
+# Formato: <nombre> <ip> · líneas en blanco y `#` se ignoran.
+dev.gioser.net 154.197.1.13