paquetería: zsh se puede instalar — los parches múltiples viajaban unidos y el target_bin era una adivinanza

Cerrar el lazo servir→instalar sobre `zsh` (pedido del usuario) destapó dos fallos de la familia
del `strip_debug` del SDD 28 §5.2: cosas que el `.swm` no transportaba fiel y que sólo se ven
cerrando el lazo, no leyendo el código.

1. `pack` CONCATENABA los N parches en el `patch` inline único, y `hash_inputs` mete una entrada
   por parche (con `of_inputs` length-prefijado) ⇒ el receptor sellaba en otra dirección y el
   `expected_hash` no coincidía NUNCA. Medido: zsh anclado b3:0be3630d…, reproducido b3:d6329a62…
   (idéntico al de una copia de la receta con sus 6 parches concatenados a mano). Afectaba a las
   16 recetas del corpus con ≥2 parches. Ahora viajan como lista `patches`, en orden, un fichero
   por parche del lado del receptor; el `patch` único se sigue leyendo para no invalidar lo
   publicado. Regresión con control negativo en swm_bridge.

2. `target_bin` se adivinaba `/usr/bin/{name}`; zsh instala en `/bin` (--bindir=/bin), así que el
   paquete prometía un path que el artefacto no tiene y el error salía en el cliente DESPUÉS de
   hidratar 1331 ficheros. Con `--build` el artefacto está delante: se comprueba, se corrige
   diciéndolo, y si no hay ningún binario con ese nombre se ABORTA al publicar.

Y el verbo que faltaba para «avisame cuándo actualizar»: `takana outdated` compara la DB de
instalados con el índice firmado (por hash, no por versión: una re-publicación con la misma
etiqueta es otro artefacto). No construye, no baja .swm y no actualiza — instalar es verificar.

Medido tras el arreglo: install zsh ⇒ cache-hit del hash anclado, 1331 ficheros en 0,14 s, zsh 5.9
corriendo.
This commit is contained in:
Sergio
2026-09-21 18:31:14 +00:00
parent c5a27d9b4c
commit 9fe1e010e2
8 changed files with 548 additions and 62 deletions
+65
View File
@@ -483,6 +483,71 @@ el Storage Box o gioser. Si no, el primer origen roto es el último.
([SDD 19 §5.2](19-lanzamiento-publico.md)), hecho sobre una máquina que importa pero que **no es el
laptop de nadie**.
### 5.5 ⚠ Dos bugs más del lazo, que sólo aparecen si el paquete NO es un binario de Rust *(2026-09-21)*
Pedido del usuario: «instalar zsh con el comando takana». `zsh` es el primer paquete que se instala
**con varios parches** y **con el binario fuera de `/usr/bin`**, y cada una de esas dos cosas era un
fallo distinto. Los dos son de la familia del `strip_debug` del §5.2 —algo que el `.swm` no
transportaba fiel— y los dos **se descubren sólo cerrando el lazo**, no leyendo el código.
**1. Los parches viajaban CONCATENADOS, y eso cambia el hash.** `pack` unía los N parches de la
receta en el `patch` inline único; `Recipe::hash_inputs` mete **una entrada por parche** y
`of_inputs` va con longitud prefijada ⇒ N sueltos y N unidos hashean distinto. Medido sin esperar al
build, con una copia de `recipes/zsh.toml` con sus 6 parches concatenados en uno:
```
b3:0be3630d… ← expected_hash anclado (y lo que hay en /store)
b3:d6329a62… ← lo que reconstruye el receptor (= la copia concatenada, exacto)
```
O sea: `install zsh` recompilaba zsh **entero** y recién entonces moría con `expected_hash no
coincide`. **Alcance: las 16 recetas del corpus con ≥2 parches** — waterfox 12, firefox y gnupg 11,
parted/zsh/strace 6, doas/mandoc/giflib 4, freetype 3, libxml2/file/brotli 2…
Arreglado con un campo `patches` (lista, en orden) en el `source_patch`; el `patch` único se sigue
leyendo para no invalidar lo ya publicado. El receptor materializa **un fichero por parche**.
Regresión con su **control negativo** en `swm_bridge`: si se concatenan, los `hash_inputs` TIENEN
que diferir, o el test pasaría por la razón equivocada.
⚠ La tentación era arreglarlo del otro lado —hashear los parches concatenados— y habría sido mucho
peor: mueve el hash de las **50** recetas con parches e invalida sus artefactos sellados.
**2. `target_bin` era una adivinanza que nadie comprobaba.** `pack` publica `/usr/bin/{name}`;
`zsh` configura `--bindir=/bin`, así que su binario queda en `/bin/zsh`. El paquete se publicaba
prometiendo un path que el artefacto no tiene, y el error salía **en la máquina que instala,
después de hidratar los 1331 ficheros**:
```
Error: source_patch declara target_bin=/usr/bin/zsh pero no quedó en <prefix>/usr/bin/zsh
```
Con `--build` el artefacto está delante: ahora `pack` comprueba el path contra él, lo corrige si el
binario quedó en otro bindir (diciéndolo) y **aborta al publicar** si no hay ningún binario con ese
nombre. Un paquete que promete algo que no trae no debería llegar a existir.
**El lazo, después:** `takana install zsh --repo http://… --require-signed` ⇒ cache-hit del hash
anclado, **1331 ficheros hidratados en 0,14 s**, y el binario corre (`zsh 5.9`). Antes: recompilar
zsh entero para morir al final.
### 5.6 `takana outdated` — el aviso que faltaba *(2026-09-21)*
Ningún verbo comparaba lo instalado con el catálogo (`upgrade` es de **árboles/generaciones**, no de
paquetes), así que «avisame cuándo hay que actualizar» no tenía respuesta. `outdated` la da leyendo
sólo el índice firmado y la DB de instalados — no construye, no baja `.swm`, no escribe nada.
**Compara por hash, no por versión**, y eso es lo único que lo hace útil: un repo se re-publica sin
que cambie la versión upstream cada vez que se mueve algo que entra en `hash_inputs` (una flag, el
lab, un parche), y mirar la etiqueta diría «al día» sobre un artefacto que ya no es el que el repo
sirve. La versión es el respaldo para cuando falta el ancla, y se dice que el juicio es más débil.
`scripts/servidor/avisar-actualizaciones.sh` lo corre por cron y **sólo grita cuando la lista de
novedades cambia** (un guardián que repite lo mismo cada 30 min deja de leerse). Un repo caído no se
disfraza de «al día»: lo dice, sale ≠ 0 y **conserva el último estado bueno**.
**Lo que sigue sin poder hacerse, y no lo arregla ningún verbo:** actualizar en una máquina sin
lab. El aviso llega a cualquiera; el `install` que lo resuelve sigue siendo reproducir desde fuente
(§5.4). Por eso `outdated` **no actualiza**: instalar es verificar, y eso no se cuelga de un cron.
---
## 6. La mudanza como experimento: el criterio de borrado