Al lanzar COSMIC, sus 30 raíces fallaron de golpe con «spawn cargo vendor: No such file».
Medido:
worker: cargo AUSENTE · rustc AUSENTE · go AUSENTE
hub: cargo ✓ · rustc ✓ · go ✓
hammer invoca `cargo vendor` en el HOST, no dentro del sandbox — el vendoreo pasa ANTES de
entrar a la caja. El worker no trae toolchain de Rust ni Go ⇒ la granja no puede construir
NINGUNA receta Rust o Go: COSMIC entero, las 228 Rust del corpus y las 362 Go. **El 76% del
catálogo sólo puede construirse en el hub**, que es UNA SOLA MÁQUINA — justo lo que el respaldo
de ayer intentaba dejar de asumir.
ESTO EXPLICA HACIA ATRÁS todos los «no construyó» de esta campaña (amp, anew, apko, atlas, bom,
broot, cargo-audit, cargo-hack, git-absorb). Yo lo atribuí a «necesitan red para sus módulos» y
lo escribí así en el SDD 23 y en memoria. Era falso: **falta el binario**, no la red.
Y ARREGLARLO NO ROMPE LA HERMETICIDAD: cargo/go aquí son herramientas de FETCH, del mismo orden
que `git` para clonar — se usan para traer y fijar deps ANTES del build, no dentro del sandbox.
Instalarlos en la imagen golden no relaja el aislamiento; el build sigue ocurriendo en la caja
con el árbol ya vendoreado. Es lo contrario del caso «no engordar el rootfs del worker», que
habla del rootfs DEL SANDBOX.
⇒ Decisión de aprovisionamiento, no de arquitectura, y desbloquea tres cuartas partes del
catálogo para la granja.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
354 lines
19 KiB
Markdown
354 lines
19 KiB
Markdown
# SDD 23 — Plan del re-hasheo masivo (corregido: la etapa 1 desmintió mi primera conclusión)
|
||
|
||
Escrito 2026-08-08, tras la decisión del usuario de hacer «ahora, antes de seguir creciendo» el
|
||
re-hasheo que llevaba tiempo pendiente.
|
||
|
||
La campaña se había planteado como **dos** trabajos que exigen reconstruir casi todo el corpus:
|
||
|
||
1. `-ffile-prefix-map` global, para cerrar el «no-determinismo probable de 92 paquetes».
|
||
2. Separar la información de depuración en paquetes `-debug`.
|
||
|
||
**Los dos son reales.** La primera versión de este documento decía que el primero se cancelaba
|
||
«porque su premisa es falsa» — **me equivoqué, y la etapa 1 lo demostró en veinte minutos**. La
|
||
corrección está en el §1.bis, y es el mejor argumento a favor de tener puertas.
|
||
|
||
---
|
||
|
||
## 1. Lo primero fue verificar la premisa (y me pasé de confiado — ver §1.bis)
|
||
|
||
El plan heredado decía: *«92 paquetes con no-determinismo probable, todos por rutas `/src` en
|
||
`.debug_*`; pendiente: `-ffile-prefix-map` global re-hashea ~720»*. Antes de comprometer días de
|
||
granja, se comprobó. Tres medidas, en orden de fuerza creciente:
|
||
|
||
**(a) Las rutas `/src` son CONSTANTES, no variables.** El lab bindea el árbol de fuentes en `/src`
|
||
—literal, siempre— así que una ruta `/src/foo.c` embebida en `.debug_` es idéntica en cada rebuild y
|
||
en cualquier máquina. No es no-determinismo: es una ruta fija que *parece* sospechosa.
|
||
|
||
**(b) Barrido de 195 artefactos buscando rutas que sí variarían** (`/home/<user>/`,
|
||
`/opt/hammer/work`, `/tmp/<aleatorio>`): **15 las tienen (≈8%)**. Pero al mirarlas una a una,
|
||
ninguna es nuestra:
|
||
|
||
| ruta hallada | qué es realmente |
|
||
|---|---|
|
||
| `/home/buildozer/aports/main/musl/src/musl-1.2.6` | la ruta de compilación **de Alpine**, horneada en el `musl` prebuilt que inyectamos |
|
||
| `/tmp/apiresponse.json` | una **cadena literal** en el código fuente de `gron` |
|
||
| `/home/jimmac/`, `/home/sam/dev/` | rutas de los autores dentro de los **assets SVG** de adwaita |
|
||
|
||
Las tres son constantes que vienen de fuera y son idénticas en cada rebuild. **Buscar rutas con
|
||
pinta de host no mide no-determinismo: mide qué escribieron terceros en sus fuentes.** Es la misma
|
||
clase de error que la regla del `.a` no-PIC —`grep R_X86_64_32` contaba reubicaciones de
|
||
`.debug_*`— y que el conteo de licencias con `grep -l license`, que contaba comentarios.
|
||
|
||
**(c) La prueba que decide: reconstruir y comparar.** Se apartó el artefacto de `wl-clipboard`, se
|
||
reconstruyó con las deps cacheadas y se comparó con `hammer why-differs`:
|
||
|
||
```
|
||
24 entradas idénticas · 0 divergen
|
||
✓ los dos árboles son idénticos: REPRODUCE
|
||
```
|
||
|
||
⇒ Conclusión que saqué entonces: «la reproducibilidad se sostiene, el `-ffile-prefix-map` no hace
|
||
falta». **Era falsa, y duró lo que tardó en correrse la etapa 1.** Ver §1.bis.
|
||
|
||
> **La lección de método, que vale más que el ahorro:** el no-determinismo se mide **reconstruyendo y
|
||
> comparando**, no buscando cadenas sospechosas. Un `grep` sobre binarios genera candidatos, no
|
||
> veredictos. Y este proyecto ya tiene la herramienta del veredicto (`why-differs`); lo que faltaba
|
||
> era usarla antes de planificar.
|
||
>
|
||
> ⚠️ Esto **no** prueba que las 1153 reproduzcan — sólo que UNA lo hace. Yo lo leí como si probara
|
||
> más de lo que probaba, que es exactamente el error contra el que este mismo documento advierte
|
||
> dos párrafos más arriba.
|
||
|
||
---
|
||
|
||
## 1.bis 🔴 CORRECCIÓN: sí hay no-determinismo, y el `-ffile-prefix-map` SÍ hace falta
|
||
|
||
La etapa 1 (verificación amplia, `scripts/verificar-repro.sh`) se corrió sobre una muestra por clase
|
||
de build. Resultado sobre las que pudieron reconstruirse:
|
||
|
||
| receta | veredicto |
|
||
|---|---|
|
||
| `binutils` | ✓ REPRODUCE |
|
||
| `appstream` | ✗ **DIVERGE** |
|
||
| `bison` | ✗ **DIVERGE** |
|
||
| `wl-clipboard` (§1c) | ✓ REPRODUCE |
|
||
|
||
Y la causa, idéntica en las dos que divergen, dicha por `why-differs`:
|
||
|
||
```
|
||
difieren [.debug_aranges, .debug_info, .debug_pubnames, .debug_pubtypes, .debug_str]
|
||
— sólo info de depuración (el código ejecutable es idéntico)
|
||
· .debug_str sólo en B: /src/output/meson-private
|
||
```
|
||
|
||
**Dónde me equivoqué, exactamente:** el barrido de §1b buscaba rutas *del host* (`/home/<user>/`,
|
||
`/tmp/<aleatorio>`) y no encontró ninguna nuestra — cierto. Pero el no-determinismo no venía de una
|
||
ruta del host sino de **rutas internas al árbol de build** (`/src/output/meson-private`) que **varían
|
||
entre corridas** aunque `/src` sea constante. Buscar la forma equivocada de ruta y no hallarla no
|
||
prueba que no haya otra. Y una sola muestra que reproduce (§1c) no es una muestra.
|
||
|
||
⇒ La lección del §1 sigue siendo válida y encima se refuerza: **el veredicto es reconstruir y
|
||
comparar**. Sólo que esta vez el equivocado fui yo, y lo que me salvó fue haber puesto la puerta
|
||
antes de la campaña en vez de después.
|
||
|
||
### 🎁 Y de ahí sale la mejor noticia del plan
|
||
Las dos mitades **son el mismo trabajo**. La divergencia vive ENTERA en secciones `.debug_*` y el
|
||
código ejecutable es idéntico ⇒ **separar el debug del artefacto principal hace que el artefacto
|
||
principal reproduzca**, sin necesidad de tocar `-ffile-prefix-map` en 720 recetas.
|
||
|
||
Queda por decidir qué se hace con el paquete `-debug` en sí (su contenido seguiría siendo
|
||
no-determinista sin `-ffile-prefix-map`), pero eso es un problema mucho más chico: afecta a un
|
||
artefacto secundario que nadie instala por defecto, y puede resolverse después o a la vez.
|
||
|
||
---
|
||
|
||
## 2. Lo que sí queda: separar la información de depuración
|
||
|
||
Medido sobre muestra amplia de `.so`/`.a`: **el 79% del contenido binario del store son secciones
|
||
`.debug_*`** (qtdeclarative solo llega al 87%). Consecuencias, las dos que importan:
|
||
|
||
- **~96 G de reserva de disco** — el store de ~126 G serían ~30 G sin debug.
|
||
- **El espejo público baja de 126 G a ~30 G**, que es la diferencia entre alojar barato y caro, y
|
||
toca la decisión ya tomada de servirlo desde el Storage Box.
|
||
|
||
Y no es sólo tamaño: separar el debug es lo que permite **entregar los símbolos a quien depura sin
|
||
imponérselos a quien sólo usa la distro**, que es lo que hacen todas las distros serias.
|
||
|
||
### El precio, dicho claro
|
||
`strip` posterior **no sirve**: rompería la verificación bit-a-bit, porque el `ArtifactHash` es de
|
||
ENTRADA (se calcula sin construir) y quedaría apuntando a un contenido que un rebuild ya no
|
||
reproduce. Hay que hacerlo **dentro del build**, y eso cambia la fase `install` ⇒ **re-hashea las
|
||
recetas afectadas**. Es el coste real y no hay atajo.
|
||
|
||
---
|
||
|
||
## 3. 🧨 El hallazgo que condiciona CÓMO se hace: el entorno del lab NO está hasheado
|
||
|
||
Al buscar dónde poner una flag global apareció algo que hay que decidir antes de tocar nada.
|
||
|
||
`crates/hammer-build/src/sandbox.rs` fija el entorno de TODOS los builds:
|
||
|
||
```
|
||
CC=hammer-zig-cc (con -mcpu=baseline) SOURCE_DATE_EPOCH=1 TZ=UTC LC_ALL=C
|
||
AR="zig ar" CARGO_BUILD_… codegen-units=1
|
||
```
|
||
|
||
**Ninguna de esas entra en `Recipe::hash_inputs`**, que es una lista blanca de source, compiler,
|
||
target, link, `zig_version`, patches, flags, phases y deps.
|
||
|
||
⇒ **Cambiar el entorno del lab cambia lo que sale del build SIN cambiar un solo hash.** El store
|
||
diría que todo está al día mientras los artefactos ya no corresponden a su hash. Es el fallo más
|
||
peligroso posible en un sistema direccionado por contenido: no falla, miente.
|
||
|
||
Hoy no muerde porque ese entorno no cambia nunca. Pero una campaña global es precisamente el momento
|
||
en que cambiaría.
|
||
|
||
### Las dos formas de hacerlo, y cuál propongo
|
||
- **A — flag en el lab** (una línea en `sandbox.rs`, aplica a todo): barata de escribir y
|
||
**rompe el invariante** por lo de arriba. **Descartada**, salvo que se añada primero un
|
||
`lab_version` a `hash_inputs` (ver abajo).
|
||
- **B — en la fase `install` de cada receta** (`strip --only-keep-debug` + `objcopy`): explícita,
|
||
entra al hash por diseño, re-hashea sólo lo que toca, y es auditable receta a receta.
|
||
**Propuesta.** El coste es editar ~1100 recetas, pero es mecánico y verificable — exactamente el
|
||
mismo patrón con el que hoy se poblaron 1074 licencias sin mover un hash.
|
||
|
||
**Y un ticket previo, chico y de fondo:** añadir a `hash_inputs` un `lab_version` que capture el
|
||
entorno del sandbox, con el mismo diseño que `zig_version` (**sólo entra si está fijado**, así los
|
||
hashes actuales no se mueven). Sin eso, cualquier cambio futuro del lab vuelve a ser invisible.
|
||
Es barato hoy y caro el día que haga falta.
|
||
|
||
---
|
||
|
||
## 4. El plan, por etapas y con puertas
|
||
|
||
Cada etapa tiene una **puerta**: si no pasa, se para. Una campaña de días sin puertas es una forma
|
||
elegante de romper el corpus.
|
||
|
||
| # | etapa | puerta |
|
||
|---|---|---|
|
||
| 0 | `lab_version` en `hash_inputs` (opcional, no fijado) | los 1153 hashes actuales **no se mueven** |
|
||
| 1 | Verificación AMPLIA de reproducibilidad: apartar y reconstruir una muestra de ~30 recetas de clases distintas (C, Rust, Go, meson, cmake) y comparar con `why-differs` | **0 divergencias**; si alguna diverge, ESO es el trabajo real y esta campaña se pospone |
|
||
| 2 | Piloto del split de debug en **5 recetas** representativas | los `-debug` salen, el binario sigue funcionando, y el rebuild reproduce bit a bit |
|
||
| 3 | Medir de verdad el ahorro y extrapolar — ✅ **HECHO, ver §6** | el ahorro real es **~60%**, no 79%: se replanteó |
|
||
| 4 | Aplicar al corpus por tandas, con la granja | cada tanda: `store-gc` + `why-differs` sobre una muestra |
|
||
| 5 | Regenerar grafos, perfiles y el espejo | los perfiles siguen cerrando (`escritorio-sway` 121/121, KDE 162/162…) |
|
||
|
||
**Orden respecto de lo demás**: la etapa 1 conviene ANTES que nada, porque si la reproducibilidad
|
||
amplia no se sostiene, todo lo demás cambia de prioridad. Y es barata: apartar artefactos y
|
||
reconstruir con deps cacheadas, como se hizo con `wl-clipboard`.
|
||
|
||
---
|
||
|
||
## 5. Lo que este plan NO propone
|
||
|
||
- **No** tocar `-ffile-prefix-map`: §1 lo cerró.
|
||
- **No** strippear artefactos ya sellados: rompe la evidencia, que es el invariante del proyecto.
|
||
- **No** meter la flag en el lab sin `lab_version`: §3.
|
||
- **No** empezar por el corpus entero: §4 empieza por 5 recetas, y con razón.
|
||
|
||
|
||
---
|
||
|
||
## 6. Etapa 3 — el ahorro real es ~60%, no 79%, y las cifras publicadas estaban infladas
|
||
|
||
El «79%» que este documento y los SDD 19/20 venían citando era **el 79% del CONTENIDO BINARIO**,
|
||
medido sobre una muestra de `.so`/`.a`. Es cierto y es la cifra equivocada para planificar, porque
|
||
un artefacto no es sólo binarios: trae cabeceras, datos, iconos, locales, `.pc` y documentación,
|
||
que no encogen.
|
||
|
||
**Medido con `scripts/medir-debug.sh`** (suma los tamaños reales de las secciones `.debug_*` vía
|
||
`readelf -S` y los compara con el tamaño del árbol, sin reconstruir nada):
|
||
|
||
| muestra | artefactos | volumen | debug / artefacto |
|
||
|---|---:|---:|---:|
|
||
| pequeña | 40 | 876 MB | 27% |
|
||
| media (truncada a 60 ficheros/artefacto) | 100 | 2 955 MB | 38% |
|
||
| **grande, sin truncar** | **250** | **8 874 MB** | **60%** |
|
||
|
||
La varianza es alta porque unos pocos artefactos grandes dominan el total. **La muestra grande es la
|
||
que manda** (cubre ~7% del store), y encaja con lo único que está medido de verdad —el piloto de la
|
||
etapa 2, donde se reconstruyó y se pesó:
|
||
|
||
- `bison` 6 M → 3 M = **−50%**
|
||
- `appstream` 56 M → 18 M = **−68%**
|
||
|
||
Los dos **encierran** el 60% de la muestra grande. Ésa es la validación: el modelo predice lo que el
|
||
rebuild real produjo.
|
||
|
||
### Las cifras corregidas
|
||
Con el store en **127 G** y un ahorro de ~60%:
|
||
|
||
| | se venía diciendo | medido |
|
||
|---|---|---|
|
||
| reserva de disco | ~96 G | **~76 G** |
|
||
| store tras el split | ~30 G | **~51 G** |
|
||
| espejo público | ~30 G | **~51 G** |
|
||
|
||
Sigue siendo el mayor ahorro disponible en el proyecto —76 G no es poco— pero **no es lo prometido**,
|
||
y la diferencia importa para dimensionar el alojamiento del espejo, que es una decisión con factura.
|
||
|
||
> **La lección:** una cifra medida sobre una cosa (contenido binario) se citó durante semanas como si
|
||
> midiera otra (tamaño de artefacto), y llegó a tres documentos. El número no era falso; **la unidad
|
||
> sí**. Cuando un número vaya a decidir un gasto, conviene volver a la medición original y comprobar
|
||
> QUÉ estaba midiendo.
|
||
|
||
|
||
---
|
||
|
||
## 7. 🔴 SEGUNDA CORRECCIÓN: lo que medía el test era DERIVA, no no-determinismo
|
||
|
||
El §1.bis dijo «sí hay no-determinismo» al ver que `appstream` y `bison` divergían. **También estaba
|
||
mal**, y por un fallo de diseño del propio test.
|
||
|
||
`scripts/verificar-repro.sh` aparta el artefacto **guardado en el store** y lo compara con una
|
||
reconstrucción de hoy. Pero ese artefacto guardado puede tener meses: se construyó con **otro estado
|
||
del lab** (otro zig, otras flags, otro entorno). Así que el test conflaba dos cosas muy distintas:
|
||
|
||
- **no-determinismo** — mismas entradas, mismo lab, salidas distintas. Es lo que rompe el invariante.
|
||
- **deriva** — el artefacto viejo no es lo que el lab de HOY produce. No es no-determinismo: es que
|
||
el mundo se movió.
|
||
|
||
**La prueba que las separa: construir DOS VECES HOY.** Y se hizo, sin querer al principio: al volver
|
||
a correr el verificador sobre recetas ya reconstruidas, todas pasaron a REPRODUCIR.
|
||
|
||
| receta | 1ª corrida (viejo vs hoy) | 2ª corrida (hoy vs hoy) |
|
||
|---|---|---|
|
||
| `anew` | ✗ diverge | ✓ **REPRODUCE** |
|
||
| `gron` | ✗ diverge | ✓ **REPRODUCE** |
|
||
| `age` | ✗ diverge (¡y le faltaba `age-inspect`!) | ✓ **REPRODUCE** |
|
||
|
||
⇒ **El corpus es determinista hoy.** Lo que hay es deriva contra artefactos viejos.
|
||
|
||
### Qué significa para la campaña, sin adornos
|
||
- **El argumento de reproducibilidad para el split de debug se cae.** El split se justifica por
|
||
ESPACIO (~60%, §6), que sigue siendo real y grande. Nada más.
|
||
- **Pero la deriva es un problema por derecho propio, y mayor**: el store contiene artefactos que el
|
||
lab de hoy **no reproduciría**. Mientras eso siga así, la frase «nuestros artefactos son
|
||
verificables por terceros» es falsa para una parte del corpus — un tercero que reconstruya no
|
||
obtendrá lo publicado. `age` es el caso feo: la reconstrucción **ni siquiera instala los mismos
|
||
ficheros** (falta `age-inspect`), o sea que la deriva no es cosmética.
|
||
- ⇒ La reconstrucción masiva **sigue valiendo la pena**, pero por una razón distinta de la que este
|
||
documento decía: no para *arreglar* el determinismo, sino para **poner el store al día con el lab**
|
||
y que la promesa de reproducibilidad sea cierta sobre todo el corpus.
|
||
|
||
### La lección, que es la misma tres veces
|
||
Este documento se equivocó tres veces seguidas y las tres se corrigieron midiendo:
|
||
1. «el `-ffile-prefix-map` no hace falta» — falso, por leer una sola muestra;
|
||
2. «sí hay no-determinismo» — falso, por un test que confundía deriva con no-determinismo;
|
||
3. y de paso, el «79%» que medía contenido binario y se citaba como tamaño de artefacto.
|
||
|
||
**Un test hay que diseñarlo contra la pregunta, no contra lo que es fácil de comparar.** Comparar
|
||
con lo que hay en el store es cómodo; comparar dos builds de hoy es lo que contesta.
|
||
|
||
---
|
||
|
||
## 8. Etapa 4 en marcha — y la lección que faltaba en el plan: **el orden importa más que la tanda**
|
||
|
||
La primera tanda activó `strip_debug` en `expat`, `zstd` y `ncurses`. Son **bibliotecas base**, así
|
||
que re-hashearon en cascada **54 recetas**, incluida la cadena `wlroots`/`sway` sellada la noche
|
||
anterior. No se pierde nada —los artefactos viejos siguen en el store y en el respaldo, y las recetas
|
||
están en git— pero el perfil `escritorio-sway` deja de cerrar 121/121 hasta reconstruirlas.
|
||
|
||
⇒ **La tanda correcta no es «las 25 siguientes por orden alfabético», sino las HOJAS del grafo
|
||
primero, o un nivel entero de una vez con la granja arriba.** Tocar tres bibliotecas base costó 54
|
||
reconstrucciones; tocar cincuenta hojas no habría costado ninguna extra. `scripts/desplegar-strip.sh`
|
||
toma las candidatas en orden alfabético y eso hay que cambiarlo: debe ordenar por profundidad en el
|
||
grafo, de hoja a raíz.
|
||
|
||
### Operación: dos cosas que cuestan una corrida cada una
|
||
- **Un drenaje largo NO puede vivir dentro del `ssh`.** La primera pasada se cortó a las 14 de 54 al
|
||
caerse la sesión. Va con `nohup` en el worker, siempre.
|
||
- **La flota efímera reusa IPs** ⇒ la clave de host cambia y cada conexión grita MITM. Se limpia con
|
||
`ssh-keygen -R <ip>`, pero con workers que van y vienen esto se repetirá: conviene que `farm-up` lo
|
||
haga solo al crear la máquina.
|
||
|
||
### Fallos de la primera pasada — ninguno nuevo, y uno que ya estaba avisado
|
||
| receta | causa |
|
||
|---|---|
|
||
| `dwarves` | el muro de libdw/musl, ya documentado como frente propio |
|
||
| `dbus` | «Unhandled python exception» de meson |
|
||
| `adwaita-hello` | **error 39 (Directory not empty)** — la carrera del árbol de fuentes del **ADR 0012** |
|
||
|
||
El último merece subrayado: **ADR 0012 lleva meses «pendiente sin decidir» y acaba de morder**. Dos
|
||
builds de la misma dep se pisan el árbol de `work/sources` y lo dejan a medias. Una campaña de
|
||
reconstrucción masiva es exactamente el escenario que lo dispara, así que decidirlo deja de ser
|
||
opcional si la etapa 4 va a correr en paralelo.
|
||
|
||
---
|
||
|
||
## 9. 🧱 EL BLOQUEANTE ESTRUCTURAL DE LA GRANJA: el worker no tiene Rust ni Go
|
||
|
||
Medido el 2026-08-08 al lanzar COSMIC:
|
||
|
||
```
|
||
worker: cargo AUSENTE · rustc AUSENTE · go AUSENTE
|
||
hub: cargo ✓ · rustc ✓ · go ✓ (~/.cargo/bin)
|
||
```
|
||
|
||
`hammer` invoca **`cargo vendor` en el HOST**, no dentro del sandbox hermético — el vendoreo pasa
|
||
antes de entrar a la caja. El worker no trae ningún toolchain de Rust ni Go, así que **la granja no
|
||
puede construir NINGUNA receta Rust o Go**:
|
||
|
||
- **COSMIC entero** (todos los `cosmic-*` son Rust) — de ahí que 30 raíces fallaran de golpe con
|
||
`spawn cargo vendor: No such file`;
|
||
- las **228 recetas Rust** y las **362 Go** del corpus.
|
||
|
||
O sea que **el 76% del catálogo sólo puede construirse en el hub**, que es una sola máquina — justo
|
||
lo que el respaldo del 2026-08-07 intentaba dejar de asumir.
|
||
|
||
### Lo que esto explica hacia atrás
|
||
Todos los «no construyó» de Go/Rust en el worker durante esta campaña —`amp`, `anew`, `apko`,
|
||
`atlas`, `bom`, `broot`, `cargo-audit`, `cargo-hack`, `git-absorb`— eran **la misma causa**, y yo la
|
||
atribuí primero a «necesitan red para sus módulos». La red no tenía nada que ver: **falta el
|
||
binario**.
|
||
|
||
### Y no rompe la hermeticidad arreglarlo
|
||
`cargo`/`go` aquí son **herramientas de FETCH**, del mismo orden que `git` para clonar un repo: se
|
||
usan para traer y fijar las deps ANTES del build, no dentro del sandbox. Instalarlos en la imagen
|
||
golden del worker no relaja el aislamiento del build — el build sigue ocurriendo en la caja con el
|
||
árbol ya vendoreado. Es exactamente lo contrario del caso «no engordar el rootfs del worker», que
|
||
habla del rootfs DEL SANDBOX.
|
||
|
||
⇒ **Es una decisión de aprovisionamiento, no de arquitectura**, y desbloquea tres cuartas partes del
|
||
catálogo para la granja.
|