Commit Graph
5 Commits
Author SHA1 Message Date
SergioandClaude Opus 5 8ad9f0490d planear: alternativas por familia funcional, con una recomendación que no es gusto
Un servicio casi nunca es insustituible. `planear.py` agrupa por familia (servidor web, base de
datos, caché, forja git, contenedores, dns, correo, base vectorial), muestra qué equivalentes tiene
takana —marcando sellado / receta sin sellar / no está— y recomienda.

Pero no «el mejor» en abstracto: un gusto disfrazado de dato es peor que no recomendar. Los criterios
son hechos comprobables, en orden:

1. **El que ya corre, si takana lo construye** — porque cambiar de servidor web no es cambiar un
   binario: es REESCRIBIR la configuración entera, y eso casi siempre pesa más que cualquier ventaja
   teórica del otro.
2. Si el que corre no está en el catálogo pero un equivalente sí, se recomienda ése (takana puede
   construirlo, firmarlo y reproducirlo) diciendo lo que cuesta.
3. Si no hay ninguno, se dice. **No se sugiere «usá otro» cuando ese otro tampoco está.**

Probado contra el catálogo real: `caddy` → se queda (ya corre y está sellado); `nginx` → recomienda
caddy nombrando el costo; `redis` → «recomendado: NINGUNO», porque ni redis ni valkey ni memcached
están en el corpus.

⚠ Y el dato que la recomendación destapa, que vale más que la recomendación: de servidores web el
corpus sólo tiene **caddy y traefik**. No hay nginx ni apache.

Si se elige un equivalente queda en el censo (`alternativa = "…"`) y el plan lo dice en su paso, con
la advertencia arriba de todo: la configuración del origen no sirve tal cual.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 17:07:05 +00:00
SergioandClaude Opus 5 6f5cc2629a mudanza: de dónde sale cada binario — y la herramienta corrigió un error mío
«Instalá el paquete» es lo que uno ya sabía. La respuesta útil sale de cruzar dos hechos: quién posee
el fichero en el ORIGEN (el censo se lo pregunta al gestor de paquetes de esa máquina, en un lote) y
si el corpus de takana tiene una receta con ese nombre.

  receta-takana  → instalarlo del repo firmado, NO copiar el binario
  paquete-ajeno  → escribir receta, o qorpa (ADR 0015)
  suelto         → nadie lo provee: llevarlo con su entorno o escribirle receta

Medido sobre gioser (37 servicios vivos): 4 receta-takana · 4 paquete-ajeno · **11 SUELTOS** · 7 sin
binario legible. Los sueltos viven en `/usr/local/bin` o en un home (`qdrant`, `matilda`,
`pacha-secretos`, `act_runner`, `shuma-gateway`) y son los que se pierden al apagar el origen.
Sorpresa medida: `/usr/bin/caddy` tampoco tiene dueño — está puesto a mano.

**Y corrigió un error mío**: en el SDD 28 escribí que `gitea` no tenía receta y que «es la que más
peso tiene». Las dos cosas falsas — `recipes/gitea.toml` existe y está sellada. Lo afirmé de memoria;
el programa fue a mirar. Corregido allá con la nota.

**Y un fallo del clasificador, que vale como regla**: calculaba la raíz del repo con un `dirname` de
menos, no encontraba ninguna receta y contestaba `suelto` A TODO, incluido `caddy`. Un clasificador
que contesta siempre lo mismo no clasifica. Ahora falla ruidosamente si no encuentra el catálogo, en
vez de dar una respuesta falsa con forma de respuesta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 17:02:52 +00:00
SergioandClaude Opus 5 96dc488842 mudanza: recomendaciones CON MOTIVO, y la lista completa a la vista para elegir
Correr en el origen no significa que aplique en el destino. `planear.py --revisar` muestra todo
agrupado con su recomendación **y su razón**, y `--decide` deja aceptarlas en bloque o revisarlas una
por una.

Cuatro familias que no aplican en una caja remota, cada una con su motivo:

  · hardware local            → bluetoothd, ModemManager, upowerd, adb
  · escritorio o pantalla     → waypipe
  · la red del destino        → NetworkManager, dhcpcd: **pelearían** con el init de allá
  · lo provee el init destino → udevd, dbus-daemon, elogind, polkitd, agetty

11 de 37 servicios de gioser caen ahí. El motivo no es cortesía: una recomendación sin razón no se
puede discutir, así que o se acepta a ciegas o se ignora entera.

**Y la recomendación DERIVADA, que es la más fuerte**: si el binario vive en un árbol que no se muda,
el servicio no podría arrancar allá. Se calcula cruzando el `cmdline` leído de `/proc` contra las
decisiones de datos. Probado en los dos sentidos: con `/mnt/vvv` muriendo, `puerta-f6e393ff` sale
«no mudar — su binario vive en /mnt/vvv»; con `/mnt/vvv` mudándose, sale «mudar».

Por eso la revisión decide los DATOS PRIMERO: de ellos se deriva la recomendación de los servicios, y
al revés no se puede calcular. Y los datos salen SIN recomendación a propósito — qué datos valen no
se deduce de la máquina; `terapeuta.ec` eran 279 M sin DNS y sólo el usuario podía decidirlo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 16:27:58 +00:00
SergioandClaude Opus 5 d682b9b240 mudanza: el APLICADOR — idempotente, reanudable, y que no marca como hecho lo que no ejecutó
`aplicar.py` ejecuta un plan. La regla que lo define: **un paso que no se ejecutó no se marca como
hecho**. Un plan tiene pasos ejecutables y pasos MANUALES cuyo `cmd` son comentarios («instalá el
paquete», «cambiá el registro A»); un aplicador que ejecuta un bloque de comentarios obtiene exit 0 y
lo marca «ok» — la peor mentira posible, porque deja el servicio caído con el informe en verde.

Acá quedan `pendiente-humano`, la corrida sale con ≠0, y `--hecho N` los confirma — negándose si el
paso sí tenía comandos («corrélo, no lo marques»).

Se le cree a la VERIFICACIÓN, no al exit code: el rsync que llenó el disco devolvió 0 y dejó 1367
artefactos vacíos. Si el comando sale bien y la verificación falla, queda `sospechoso`. Y las
verificaciones van TIPADAS (`cmd`/`humano`): «la columna Available debe ser > X» no es un comando.

Estado reanudable en `<plan>.estado.json`, escrito con temporal + fsync + rename — la lección que
costó un upgrade entero en el SDD 28.

**Y un fallo propio, encontrado en la primera corrida real**: la verificación del paso de datos
imprimía el número de directorios vacíos y devolvía 0 igual. Dio «✓ verificado: 2» donde ese 2 eran
dos vacíos en destino. Un guardián que siempre pasa no es un guardián. Ahora COMPARA los ficheros de
los dos lados y falla si difieren. Probado con rotura a propósito y con el control que debe pasar:
copia no hecha ⇒ origen=5 destino=0 ⇒ FALLA; copia hecha ⇒ 5 y 5 ⇒ PASA.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 15:20:15 +00:00
SergioandClaude Opus 5 6f12c231fa mudanza etapa 2: el PLAN — exportable, con el comando literal de cada paso
`planear.py` convierte un censo decidido en un plan ejecutable. Cada paso lleva su COMANDO LITERAL y
su verificación, así que el fichero se ejecuta a mano, línea por línea, sin la herramienta y sin este
repo. Eso es lo que el usuario pidió como «pasos exportables».

Las tres reglas, cada una pagada en el SDD 28:

1. **Nada sin decidir se ejecuta**: aborta con código 2 listando qué falta. El silencio no es
   consentimiento — sin decisión, ni mudar ni matar es correcto. Probado en los dos sentidos.
2. **Lo que muere se dice por su nombre y con su tamaño ANTES de borrar**, y el paso no borra nada:
   es una lista para leer antes de apagar el origen.
3. **Cada copia se verifica EN DESTINO**: el `rsync` que llenó el disco devolvió 0 y dejó 1367
   artefactos vacíos; sólo se vio contando del otro lado.

Y el preflight dimensiona con el tamaño de COPIA (hardlinks expandidos, 60 G → 85 G medidos), no con
`du`; si un tamaño resulta ilegible lo dice en vez de contarlo como 0.

**El añadido que cambia el valor: la invocación real.** Decir «este servicio no está declarado»
nombra el problema; lo que hace falta para resolverlo es cómo corre AHORA. El censo lo lee de
`/proc` (`cmdline` + `cwd`, nunca `environ`: el entorno trae tokens) y el plan lo emite:

    #   cmdline: /mnt/vvv/tawasuyu/target/debug/deps/puerta-f6e393ffbef3999e
    #   cwd    : /mnt/vvv/tawasuyu/shared/tejido
    #   puertos: 34221, 44961

Ese servicio de gioser es un binario de `target/debug/deps/` corriendo en producción, con dos
puertos, que ninguna declaración conoce. Apagada la máquina vieja, eso no se reconstruye de memoria.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 15:03:33 +00:00