docs: SDD 23 — plan del re-hasheo, y media campaña CANCELADA porque su premisa era falsa
El usuario decidió hacer «ahora» el re-hasheo masivo. Lo primero fue verificar la premisa, y no
se sostuvo: **el `-ffile-prefix-map` global no hace falta**. Se ahorran días de granja y un
re-hasheo de ~720 recetas.
EL PLAN HEREDADO decía «92 paquetes con no-determinismo probable, todos por rutas /src en
.debug_». Tres medidas, de fuerza creciente, lo desmontan:
(a) Las rutas `/src` son CONSTANTES: el lab bindea el árbol de fuentes en /src literal, así que
una ruta embebida en .debug_ es idéntica en cada rebuild y en cualquier máquina. Parece
sospechosa y es fija.
(b) Barrido de 195 artefactos buscando rutas que SÍ variarían (/home/<user>/, /opt/hammer/work,
/tmp/<aleatorio>): 15 las tienen, y ninguna es nuestra —
· /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 fuente de gron;
· /home/jimmac/, /home/sam/dev/ = rutas de los autores en los assets SVG de adwaita.
(c) LA PRUEBA QUE DECIDE: apartar el artefacto de wl-clipboard, reconstruir con deps cacheadas y
comparar con `hammer why-differs` → «24 entradas idénticas · 0 divergen · REPRODUCE».
La lección de método vale más que el ahorro: el no-determinismo se mide RECONSTRUYENDO Y
COMPARANDO, no buscando cadenas sospechosas. Un grep sobre binarios da candidatos, no veredictos
— misma clase de error que la regla del `.a` no-PIC (contaba reubicaciones de .debug_*) y que el
conteo de licencias con `grep -l license` (contaba comentarios). El proyecto YA tenía la
herramienta del veredicto; lo que faltaba era usarla antes de planificar.
QUEDA EL SPLIT DE DEBUG, que sí es real: el 79% del contenido binario del store son secciones
.debug_ ⇒ ~96 G de reserva y el espejo público de 126 G a ~30 G, que toca la decisión ya tomada
de servirlo desde el Storage Box.
🧨 Y APARECIÓ ALGO QUE CONDICIONA EL CÓMO: el entorno del lab NO ESTÁ HASHEADO. sandbox.rs fija
CC/-mcpu=baseline, SOURCE_DATE_EPOCH, TZ, LC_ALL, AR y codegen-units para TODOS los builds, y
ninguna entra en hash_inputs. ⇒ Cambiar el entorno del lab cambiaría lo que sale del build SIN
mover un solo hash: el store diría que todo está al día mientras los artefactos ya no
corresponden. En un sistema direccionado por contenido ese es el peor fallo posible: no falla,
miente. Hoy no muerde porque ese entorno no cambia nunca — y una campaña global es justo el
momento en que cambiaría.
Por eso el plan propone hacer el split en la fase `install` de cada receta (entra al hash por
diseño, auditable, re-hashea sólo lo que toca) y NO como flag del lab; más un ticket previo
chico: un `lab_version` en hash_inputs con el diseño de `zig_version` — sólo entra si está
fijado, así los hashes actuales no se mueven.
Cinco etapas con PUERTA cada una. La primera es verificación amplia de reproducibilidad (~30
recetas de clases distintas); si alguna diverge, ESO es el trabajo real y la campaña se pospone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# SDD 23 — Plan del re-hasheo masivo (y por qué es la mitad de lo que se creía)
|
||||
|
||||
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`.
|
||||
|
||||
**El primero se CANCELA: su premisa es falsa, y está medido.** Queda el segundo, que es real.
|
||||
|
||||
---
|
||||
|
||||
## 1. 🛑 Lo primero fue verificar la premisa — y no se sostuvo
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
⇒ **La reproducibilidad se sostiene. El `-ffile-prefix-map` global no hace falta.** Se ahorran días
|
||||
de granja y un re-hasheo de ~720 recetas.
|
||||
|
||||
> **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: prueba que la causa que se les atribuía no existe,
|
||||
> y que el mecanismo funciona. La verificación amplia es el §4.
|
||||
|
||||
---
|
||||
|
||||
## 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 sobre esas 5 y extrapolar | si el ahorro real se aleja mucho del 79% teórico, se replantea |
|
||||
| 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.
|
||||
Reference in New Issue
Block a user