tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1181Shell 82Rust 62Markdown 521515 Definitions

sandbox: CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 — arje-zero reproducible (Stage 2 no-det #3)

El rebuild in-VM daba ✗ DIVERGENTE (of_tree host 984e002f ≠ VM 94b93585) pese a
que los 4 componentes sellaban bajo el mismo key of_inputs. Bisección por componente
en el host: musl, busybox y hammerd reconstruyen byte-idéntico, y la ensambladura del
rootfs (of_tree) es determinista e independiente del entorno. El culpable era arje-zero:
dos builds del MISMO host daban binarios distintos (Δ ~9.6 KB por .text/.rodata/.eh_frame/
.gcc_except_table) — firma del codegen paralelo de rustc.

Raíz: el workspace de hammer pinea [profile.release] codegen-units=1 (por eso hammerd
reproducía), pero el monorepo tawasuyu no declara perfil → cargo default codegen-units=16,
cuyo reparto del crate en N objetos varía build-a-build.

Fix: el sandbox impone codegen-units=1 para TODAS las crates Cargo (el var de cargo gana
sobre el profile del repo fuente). El lab elimina el no-determinismo en vez de confiar en
upstream (SDD 09 §2). Verificado: dos builds cu=1 de arje-zero → byte-idénticos.

Nueva referencia reproducible 4/4: of_tree(stage1)=b3:0039b2b9… (reemplaza 198f209f… de
cu=16). Actualizados EXPECT_REF (selfhost-verify.sh) y runbook §8b/§8c.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

4c9bcc016b2f821a8cda896d61637ca8fb3010df · by sergio · (4 months ago) · parent 1ab53a650 · browse the tree at this commit

Semantic delta · 1

crates/hammer-build/src/sandbox.rs

3 files · +40 −9

diff --git a/crates/hammer-build/src/sandbox.rs b/crates/hammer-build/src/sandbox.rs
index 6c07098a..76481d43 100644
--- a/crates/hammer-build/src/sandbox.rs
+++ b/crates/hammer-build/src/sandbox.rs
@@ -177,6 +177,16 @@ impl Sandbox {
             // (constante entre rebuilds, sea cual sea el work dir del host). Ver SDD 11 / runbook.
             ("SOURCE_DATE_EPOCH", "1"),
             ("TZ", "UTC"),
+            // Reproducibilidad de builds Cargo: fuerza `codegen-units = 1` para TODAS las crates
+            // del build (el var de cargo gana sobre el `[profile.release]` del repo fuente). Con el
+            // default (16) el codegen paralelo de rustc reparte el crate en N unidades y la salida
+            // varía build-a-build incluso en la MISMA máquina y con entradas idénticas — Stage 2 lo
+            // cazó: `arje-zero` (cuyo workspace tawasuyu no fija perfil) divergía ~10 KB repartidos
+            // por .text/.rodata/.eh_frame, mientras `hammerd` (workspace hammer que SÍ pinea
+            // codegen-units=1) reproducía bit-a-bit. El lab lo impone en vez de confiar en que cada
+            // repo upstream lo declare (SDD 09 §2). Inerte para builds no-Cargo (musl/busybox lo
+            // ignoran). Ver runbook §8c / SDD 11.
+            ("CARGO_PROFILE_RELEASE_CODEGEN_UNITS", "1"),
         ];
         if self.cache_dir.is_some() {
             defaults.push(("ZIG_GLOBAL_CACHE_DIR", "/cache/zig"));
diff --git a/docs/runbooks/stage1-vm-boot.md b/docs/runbooks/stage1-vm-boot.md
index f4933ec7..457008ef 100644
--- a/docs/runbooks/stage1-vm-boot.md
+++ b/docs/runbooks/stage1-vm-boot.md
@@ -248,8 +248,8 @@ acá: la **bit-reproducibilidad del build** (dos builds independientes del mismo
 |---|---|---|
 | **musl** 1.2.5 | ✅ byte-idéntico | el build C con zig cc es determinista de fábrica |
 | **busybox** 1.36.1 | ✅ tras dos fixes | (ver abajo) |
-| **hammerd** (Rust) | ✅ byte-idéntico | crt-static + cargo `--locked` + `SOURCE_DATE_EPOCH` + paths fijos `/src` ⇒ rust reproducible |
-| **arje-zero** (Rust) | ✅ (indirecto) | mismo camino que hammerd; su único riesgo era el lock generado sin `--locked` — verificado: dos `cargo generate-lockfile` del commit pinned dan un `Cargo.lock` **byte-idéntico** (resolución determinista). Committear el lock lo haría explícito (plan C.2 #5) |
+| **hammerd** (Rust) | ✅ byte-idéntico | crt-static + cargo `--locked` + `SOURCE_DATE_EPOCH` + paths fijos `/src` + el workspace de hammer pinea **`codegen-units = 1`** ⇒ rust reproducible |
+| **arje-zero** (Rust) | ✅ tras fix codegen-units | dos builds del host divergían ~10 KB repartidos por `.text`/`.rodata`/`.eh_frame`: el workspace tawasuyu **no** fija `[profile.release]` → cargo usa el default `codegen-units = 16` y el codegen paralelo de rustc no es reproducible. Fix: el sandbox impone `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1` para **todas** las crates (ver nota de cierre §8c). Verificado: dos builds cu=1 → byte-idénticos |
 
 **La verificación cazó dos no-determinismos reales en busybox** (justo lo que [SDD 09 §2](../09-trust-model.md) pide):
 
@@ -261,10 +261,11 @@ acá: la **bit-reproducibilidad del build** (dos builds independientes del mismo
    `linux/kd.h` (UAPI) que zig **no** bundlea → `linux-headers` en el lab (`bootstrap-devfs.sh`);
    zig cc nativo los encuentra en `/usr/include`.
 
-Con ambos, `musl` y `busybox` reconstruyen **bit-idéntico**. Sumados `hammerd` (directo) y
-`arje-zero` (indirecto), **los 4 componentes están verificados reproducibles**. Lo único que le
-queda a Stage 2 es el rebuild *dentro* de un **builder rootfs** (un Stage 1 que incluya el
-toolchain) — el auto-alojamiento pleno, un hito propio (el Stage 1 mínimo es runtime, sin compilador).
+Con ambos, `musl` y `busybox` reconstruyen **bit-idéntico**. Sumados `hammerd` y `arje-zero` (este
+último tras el fix de `codegen-units`, nota de cierre §8c), **los 4 componentes reconstruyen byte-a-byte**
+— cada uno verificado con dos builds independientes del host. Lo único que le queda a Stage 2 es el
+rebuild *dentro* de un **builder rootfs** (un Stage 1 que incluya el toolchain) — el auto-alojamiento
+pleno, un hito propio (el Stage 1 mínimo es runtime, sin compilador).
 
 ## 8c. Builder rootfs — el rebuild in-rootfs (auto-alojamiento, SDD 11 §7)
 
@@ -423,6 +424,25 @@ no-interactivo) → adentro `rebuild-stage1` debe reproducir **`198f209f…`** 
 cerraría el auto-alojamiento **4/4** bit a bit. Bajo TCG es lento y la RAM va justa; el veredicto
 pleno gana con **KVM + más RAM** (y boot desde disco).
 
+**No-determinismo #3 cazado — `arje-zero` y `codegen-units` (2026-06-12).** El rebuild in-VM dio
+`✗ DIVERGENTE` (`of_tree` host `984e002f…` ≠ VM `94b93585…`) pese a que los 4 componentes sellaban
+bajo el mismo key `of_inputs`. La caza (bisección por componente en el host): `musl`, `busybox` y
+`hammerd` reconstruyen **byte-idéntico** entre dos builds del host, y la ensambladura del rootfs
+(`of_tree`) es determinista e independiente del entorno. El culpable era **`arje-zero`**: dos builds
+del **mismo** host daban binarios distintos (Δ ~9.6 KB repartidos por `.text` −8064, `.rodata` −896,
+`.eh_frame`, `.gcc_except_table`, `.data.rel.ro`, `.got`) — la firma del **codegen paralelo** de
+rustc. Raíz: el workspace de hammer pinea `[profile.release] codegen-units = 1` (por eso `hammerd`
+reproducía), pero el monorepo tawasuyu **no** declara perfil → cargo usa el default `codegen-units = 16`.
+Con 16 unidades el reparto del crate en N objetos varía build-a-build incluso con entradas idénticas,
+y la salida cambia. **Fix:** el sandbox impone `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1` para todas las
+crates Cargo (`crates/hammer-build/src/sandbox.rs`) — el lab elimina el no-determinismo en vez de
+confiar en que cada repo upstream lo declare ([SDD 09 §2](../09-trust-model.md)). Verificado: dos
+builds cu=1 de `arje-zero` → **byte-idénticos**, y el `of_tree(stage1)` queda estable. Nueva referencia
+**reproducible 4/4** (cu=1): `of_tree(stage1)=b3:0039b2b90fddf62221ad5b804a34df93ef57dc533cb210512cf53752876afdff`
+(reemplaza la `198f209f…` de cu=16, que ya era no-reproducible). El store del host se refrescó a este
+baseline y `EXPECT_REF` en `selfhost-verify.sh` se actualizó. **Pendiente:** re-correr el verify
+end-to-end in-VM (debe dar `✓ REPRODUCIBLE` contra `0039b2b9…`).
+
 **Tarea reproducible end-to-end: `scripts/selfhost-verify.sh`.** Un solo comando hace TODO el pipeline
 (stage0 → stage1 baseline → stage2 ref → inyectar `overlay.ko`/`e1000.ko` del kernel local → ensamblar
 builder → empaquetar → bootear + driver no-interactivo → veredicto), pensado para correr en una
@@ -435,8 +455,8 @@ KVM=1 MEM=10240 ./scripts/selfhost-verify.sh
 ```
 
 Cross-check de reproducibilidad **entre máquinas**: el script compara su `of_tree(stage1)` contra
-`EXPECT_REF` (la referencia conocida-buena `198f209f…` del host de dev). Si difieren, o cambió un pin
-(toolchain/recipes/seed) o hay un no-determinismo nuevo. En el host de dev (Artix, 7.6 GB, **sin KVM**)
+`EXPECT_REF` (la referencia conocida-buena `0039b2b9…` del host de dev, baseline cu=1). Si difieren, o
+cambió un pin (toolchain/recipes/seed) o hay un no-determinismo nuevo. En el host de dev (Artix, 7.6 GB, **sin KVM**)
 el rebuild **4/4** in-VM colgó por presión de RAM bajo TCG a los ~17 min del `make` de musl — el muro
 de hardware que esta tarea traslada a un host capaz.
 
diff --git a/scripts/selfhost-verify.sh b/scripts/selfhost-verify.sh
index ff25ab83..142e3f7c 100755
--- a/scripts/selfhost-verify.sh
+++ b/scripts/selfhost-verify.sh
@@ -40,7 +40,8 @@ TOOLCHAIN="${TOOLCHAIN:-.dev-fs/alpine}"
 TOOLCHAIN_TAG="${TOOLCHAIN_TAG:-alpine-3.23.4-builder}"
 # Referencia conocida-buena (store baseline en el host de dev, runbook §8c). Si tu corrida produce
 # otra, o bien cambió algo del pin (toolchain/recipes/seed) o hay un no-determinismo nuevo.
-EXPECT_REF="${EXPECT_REF:-b3:198f209f5e2c9d3a37411823b2ea42a09074d4e482f044278958b1b86f232dbb}"
+# Baseline con codegen-units=1 en el sandbox (reproducibilidad de arje-zero, ver §8c y SDD 09 §2).
+EXPECT_REF="${EXPECT_REF:-b3:0039b2b90fddf62221ad5b804a34df93ef57dc533cb210512cf53752876afdff}"
 : "${KVM:=$([[ -w /dev/kvm ]] && echo 1 || echo 0)}"
 
 say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }