la caja ya es un HUB: el lab viajó pineado, hashea igual, y el latido late desde allá
El §6.42 dijo qué faltaba y el usuario eligió darle el lab a la caja. Hecho. EL LAB NO SE COPIA, SE PINEA Y SE TRAE. `lab-image.sh` lo empaqueta determinista (--sort=name --mtime=@1 --owner=0, excluyendo var/log/apk.log), lo publica al Storage Box con el sha en el nombre, y `--traer` VERIFICA EL SHA ANTES DE EXTRAER. El empaquetado de hoy devolvió exactamente el sha que el repo ya pineaba (d1e341d5…): o sea que el lab de gioser nunca derivó Y que el empaquetado es reproducible de verdad, no una promesa del comentario. LA PRUEBA QUE DECIDE no es que arranque: la caja calcula los MISMOS ArtifactHash que gioser en las cuatro recetas de control (zlib, busybox, caddy, shuma-daemon). Con otro lab serían otros números y el store no lo notaría. ⚠ Y UNA TRAMPA QUE ME TENDÍ SOLO: al copiar los 24 artefactos que le faltaban al store de la caja, `rsync -a --files-from=<lista de DIRECTORIOS>` mandó 2.281 bytes y «total size is 0» — pero creó los 24 DIRECTORIOS VACÍOS. `--files-from` no recursa sin `-r`, y un directorio vacío en el store ES UN CACHE-HIT (§3 de CLAUDE.md): `build` lo habría dado por sellado sin construir. Detectado contando, borrados los 24, repetido con `-r`: 3,18 GB y 0 vacíos de 1655. Stores convergidos (copiados también obs-studio y spectacle, justo los dos que el worker no logra construir). La caja no empata, gana por uno: corpus 930 selladas / 1 deuda contra 929 / 2; KDE 1106 / 1 contra 1105 / 2. Latido mudado: cron apagado en gioser, encendido en la caja, y un ciclo corrido a mano mirando lo que publica — `repo al día (ff)` → `estado commiteado+pusheado` con los números buenos. ⚠ Lo que queda apretado es el disco: /store de la caja al 91%, 8,2 G libres. Un hub sella y cosecha: ése es el próximo muro, y `store-gc.sh` no se corrió nunca allá. ⇒ PUERTA 4 EN VERDE. De las ocho quedan dos a medias (5 y 7) y ninguna en rojo. 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 · +73 −0
diff --git a/docs/28-servidor-de-produccion.md b/docs/28-servidor-de-produccion.md
index 33cb855f..b594053c 100644
--- a/docs/28-servidor-de-produccion.md
+++ b/docs/28-servidor-de-produccion.md
@@ -2543,6 +2543,79 @@ tiene el lab— y vuelve a publicar los números buenos.
No rompe nada, pero un `EPIPE` sin manejar en una herramienta de operación aparece justo cuando
alguien la encadena.
+### 6.43 🧪 La caja ya es un HUB: el lab viajó, y con él la puerta 4 *(2026-09-17)*
+
+El §6.42 dejó dicho qué faltaba y el usuario eligió: **darle el lab a la caja**. Hecho, y el latido
+ya late desde allá.
+
+#### El lab no se copia: se PINEA y se trae
+
+`scripts/lab-image.sh` empaqueta `.dev-fs/alpine` de forma **determinista** (`--sort=name
+--mtime=@1 --owner=0 --group=0 --numeric-owner`, excluyendo `var/log/apk.log`, que es lo único que
+difiere entre dos labs por lo demás iguales), lo publica al Storage Box con el sha en el nombre, y
+del otro lado `--traer` **verifica el sha antes de extraer**. Eso es lo que hace que las dos máquinas
+hasheen igual, y es un contrato, no una copia.
+
+**Y el empaquetado de hoy devolvió el sha que el repo ya pineaba:**
+
+```
+ empaquetado hoy en gioser : d1e341d5dd434a11e2290b0340069ce4a83563eac8e4481f379b4951b1162045
+ pineado en bootstrap-devfs.sh: d1e341d5dd434a11e2290b0340069ce4a83563eac8e4481f379b4951b1162045
+```
+
+O sea dos cosas de una: **el lab de gioser nunca derivó** desde que se publicó esa imagen, y **el
+empaquetado es reproducible de verdad** — no era una promesa del comentario.
+
+#### La prueba que decide, y no es que arranque
+
+Con el lab instalado, la caja calcula los MISMOS `ArtifactHash` que gioser:
+
+```
+ zlib b3:dc363f26… busybox b3:2a2b1280…
+ caddy b3:c915987d… shuma-daemon b3:f9b92acc…
+```
+
+Cuatro de cuatro. Si el lab fuera otro, estos números serían otros y el store no lo notaría — es el
+agujero que `hash_inputs` cerró (§lab en el hash) y la razón por la que esto se verifica con hashes
+y no con un «arrancó bien».
+
+#### Los stores, convergidos — y una trampa que me tendí solo
+
+El grafo seguía discrepando porque el store de la caja no tenía 24 artefactos vigentes de gioser
+(3,0 G). Al copiarlos con `rsync -a --files-from=<lista de DIRECTORIOS>` pasó lo peor posible:
+
+```
+ sent 2,281 bytes … total size is 0 ← no copió NADA
+ artefactos en la caja: 1631 → 1655 ← pero creó los 24 DIRECTORIOS
+```
+
+**`--files-from` no recursa sin `-r`**: creó 24 directorios vacíos en el store. Y un directorio vacío
+en el store **es un cache-hit** (§3 de CLAUDE.md, [[artefacto-vacio-envenena-cache]]): `build` lo
+habría dado por sellado y habría salido OK sin construir. Se detectó al contar, se borraron los 24 y
+se repitió con `-r`: 3,18 GB transferidos, **0 vacíos de 1655**.
+
+Después faltaban dos más (`obs-studio` y `spectacle`, justo los que el worker no logra construir),
+copiados también. Resultado:
+
+| | gioser | caja |
+|---|---|---|
+| corpus | sealed 929 · debt 2 | **sealed 930 · debt 1** |
+| KDE | sealed 1105 · debt 2 | **sealed 1106 · debt 1** |
+
+La caja no empata: **gana por uno** en los dos grafos.
+
+#### El latido, mudado
+
+Cron apagado en gioser, encendido en la caja, y un ciclo completo corrido a mano para mirar lo que
+publica: `==> repo al día (ff)` → `==> estado commiteado+pusheado`, con los números buenos. El
+`build-state` que hay en `main` ahora lo escribió la caja.
+
+⚠ **Lo que queda apretado es el disco**: `/store` de la caja está al **91 %, 8,2 G libres**. Un hub
+sella y cosecha, así que ése es el próximo muro — `scripts/store-gc.sh` es la herramienta y todavía
+no se corrió allá.
+
+⇒ **puerta 4 en verde.** Con eso, de las ocho quedan sólo las dos a medias (5 y 7) y ninguna en rojo.
+
## 7. Reusar los scripts que ya existen, y no escribir de nuevo
Pedido explícito del usuario. El inventario de lo que ya hace el trabajo: