docs(servidor): el SDD 28 afirmaba que una pieza estaba puesta, y por eso el cron falló 18 días
Dos correcciones y una lección de método. §5.9 daba por puesto `/usr/local/bin/takana` y **nunca existió**. El cron del aviso de novedades falló CADA HORA durante dos semanas y media con `!! falta /usr/local/bin/takana (cargo build --release)`. La otra mitad de la frase también era falsa: el binario de la caja **sí conoce `outdated`** (comprobado, imprime su ayuda). El envoltorio apunta ahora a `/usr/bin/takana` y el aviso contesta «al día contra https://repo.gioser.net (2 paquete(s) comprobado(s))». Y la lección, que vale más que el arreglo: el guardián que debía delatarlo escribía su queja en `/var/log/takana-actualizaciones.log` — **una línea por hora, 18 días, en un fichero que nadie abre**. Un guardián cuyo único canal es un log sin lector no es un guardián, es un registro. Es la misma forma de fallo que el latido («NO pude publicar» cada 30 min durante 12 días) y que el «al día» vacío que este mismo §5.9 ya describía. Al enchufar un aviso hay que decidir **dónde grita cuando ÉL mismo está roto**, no sólo qué dice cuando funciona. Y entra el §6.50 con el arreglo del latido: por qué publicaba contra el árbol, por qué el espejo nunca se empujó desde acá, y la pieza nueva de la caja (`/usr/local/bin/credencial-espejo-github`) con las alternativas que se descartaron y por qué — las tres dejan el token EN REPOSO en una caja con root compartido. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Semantic delta
No definition changed: this change is text, documentation or data.
1 files · +58 −1
diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md
index f1808fb3..9f10c514 100644
--- a/docs/28-servidor-de-produccion.md
+++ b/docs/28-servidor-de-produccion.md
@@ -683,9 +683,18 @@ Tres piezas que faltaban en la caja y ahora están:
| | |
|---|---|
| `/var/lib/hammer/trust/release.ed25519.pub` | el almacén de confianza **por defecto** de la CLI, que no existía ⇒ `install`/`outdated` verifican la firma **sin pasar `--trust`** |
-| `/usr/local/bin/takana` | build release con el arreglo del §5.5. ⚠ El `PATH` de la caja **no incluye `/usr/local/bin`**, así que un `takana` a secas sigue siendo el del store (que no conoce `outdated`). El cron usa rutas absolutas. Ponerlo canónico es reconstruir la receta `takana` e hidratarla — su propia unidad de trabajo |
+| ~~`/usr/local/bin/takana`~~ → **`/usr/bin/takana`** | ⚠ **CORREGIDO el 2026-10-07: `/usr/local/bin/takana` NUNCA EXISTIÓ**, y este párrafo lo daba por puesto. El cron falló **cada hora durante dos semanas y media** con `!! falta /usr/local/bin/takana (cargo build --release)`, una línea por hora en un log que nadie lee. Y la otra mitad de la frase también era falsa: el binario de la caja **SÍ conoce `outdated`** (comprobado: `/usr/bin/takana outdated --help` imprime su ayuda). El envoltorio apunta ahora a `/usr/bin/takana` y el aviso contesta: *«al día contra https://repo.gioser.net (2 paquete(s) comprobado(s))»*. Sigue siendo cierto que el `PATH` de la caja no incluye `/usr/local/bin` y que el cron usa rutas absolutas |
| `/var/log/takana-actualizaciones.log` | una línea por hora |
+⚠ **Lección de la corrección de arriba, que vale más que la corrección.** Este documento afirmaba
+que una pieza estaba «en la caja» cuando no estaba, y el aviso que debía delatarlo escribía su
+queja en `/var/log/takana-actualizaciones.log`: **una línea por hora, durante 18 días, en un
+fichero que nadie abre**. Un guardián cuyo único canal es un log sin lector no es un guardián —
+es un registro. Es la misma forma de fallo que el latido («NO pude publicar» cada 30 min, 12 días)
+y la que este documento ya describe tres párrafos más abajo sobre el «al día» vacío. Al enchufar un
+aviso hay que decidir **dónde grita cuando ÉL mismo está roto**, no sólo qué dice cuando funciona.
+
+
⚠ **Y la primera corrida real destapó un defecto del propio aviso**: con la DB de instalados vacía
decía **«al día»**. Es verdad y no dice nada — peor, se lee como «comprobado y correcto» cuando lo
cierto es que *no había nada que comprobar*. Un aviso que suena igual cuando vigila que cuando no
@@ -3830,6 +3839,54 @@ que hace la persona**, grabada en un fichero de respuestas.
---
+### 6.50 🫀 El latido latía y no publicaba — 12 días, y el espejo nunca existió *(2026-10-07)*
+
+El latido vive acá desde el §6.42. El 2026-10-07 se descubrió que llevaba **12 días publicando
+nada**: 580 commits locales atascados en `/opt/takana` y un «NO pude publicar el estado — queda
+local» cada 30 minutos en `work/cosecha-cron.log`. Nadie lo leía, que es justo el riesgo de mudar un
+guardián a una caja donde no trabaja nadie.
+
+**Causa doble, y ninguna era el cron.** Publicar dependía de dos estados que el latido NO controla:
+
+1. **el árbol de trabajo** — el plan B (`git-sincronizar.sh`) hace `pull --rebase`, que se niega con
+ cualquier fichero modificado. Acá era un `scripts/servidor/claude-caja.sh` que resultó ser **byte
+ a byte lo que ya estaba en origin**: trabajo duplicado de otro clon, bloqueando por nada.
+2. **la rama local** — divergida, y `pull --ff-only` no puede volver NUNCA. El `--ff-only` es
+ deliberado (regla 2 ter de `CLAUDE.md`), así que la deriva es de ida.
+
+Los 580 commits contenían, en total, **cuatro líneas de timestamp**.
+
+⇒ **`scripts/farm/publicar-estado.sh`** arma el commit con *plumbing* sobre el `origin/main` recién
+traído, en un índice TEMPORAL (`GIT_INDEX_FILE`), y empuja el sha a `refs/heads/main`. No toca el
+árbol, no toca el índice compartido (regla 2), no le hace falta que la rama esté al día, y reintenta
+si otro empuja. Nunca fuerza: el rechazo ES la señal.
+
+**Y el espejo: el latido NUNCA lo empujó, ni ahora ni antes.** `/opt/takana` tenía `origin` sólo a
+gitea — `espejo-setup.sh` nunca se había corrido acá, aunque su propio comentario da por hecho que
+el latido espeja solo («para que TODO `git push origin main` que ya existe en los scripts (p.ej.
+cosecha-cron.sh) espeje solo»). Con 48 commits de estado al día, el espejo derivaba por ese número.
+
+Arreglarlo no era añadir la `pushurl`: el latido corre como **root** con `HOME=/root` y `gh` está
+logueado como **sergio**. El camino SSH se descartó midiéndolo (ninguna clave de `/root/.ssh` abre
+el repo del espejo). Pieza nueva de la caja:
+
+| | |
+|---|---|
+| `/usr/local/bin/credencial-espejo-github` | envoltorio de rutas sobre `scripts/servidor/credencial-espejo-github.sh`. Ayudante de credencial de git que pide el token con `su - sergio -c 'gh auth token'` **en el momento de empujar** y no lo escribe en ningún sitio. Declarado en el clon con `git config credential.https://github.com.helper` |
+
+Se descartaron las alternativas por lo que dejan **en reposo**: el token en `.git/config` o en
+`~/.git-credentials` es un secreto en claro en una caja con root compartido, y la URL con el token
+adentro sale además en cada mensaje de error de git. Si `gh` pierde la sesión, el push falla
+ruidosamente en vez de empujar con un token muerto.
+
+⚠ **De paso, `espejo-setup.sh` MENTÍA en esta caja**: daba `gh auth status` por única credencial
+válida, así que decía «el push a GitHub va a fallar» mientras el push funcionaba. Ahora mira primero
+el ayudante, que es lo que git usa de hecho. Un guardián que grita en falso se deja de leer, y el
+día que grite de verdad tampoco se lee — es el mismo defecto que el «al día» vacío del §5.9.
+
+**Verificado en producción:** `estado publicado … · espejo al día · rama local al día`, los dos
+remotos en el mismo sha, y el clon del latido queda **limpio** tras cada ciclo.
+
## 8. `churay` como instalador centralizado — la dirección, no la decisión
El usuario lo planteó el 2026-09-09: **un instalador único, con CLI y TUI**, que sirva para