diff --git a/docs/runbooks/stage1-vm-boot.md b/docs/runbooks/stage1-vm-boot.md index 904cdb59..cc1dbf34 100644 --- a/docs/runbooks/stage1-vm-boot.md +++ b/docs/runbooks/stage1-vm-boot.md @@ -235,6 +235,35 @@ Detecta la muerte **con su `ExitStatus`** (`Killed(SIGTERM)`), programa el resta que [ADR 0007](../adr/0007-arje-como-init-propio.md) prometía y la Fase 5 había diferido. Exponerlo a la capa de IA por `agent.sock` es B.2 (bus único). +## 8b. Stage 2 — verificación de reproducibilidad (pre-rebuild-in-rootfs) + +El `stage2` de hammer ancla el **content-hash** (`ArtifactHash::of_tree`, los bytes reales) del +rootfs y lo compara con un rebuild. El rebuild **dentro** del rootfs usando *sólo* las herramientas +de Stage 1 (auto-alojamiento pleno) necesita un **builder rootfs** — el Stage 1 mínimo es un +*runtime* (no trae zig/make/cargo), así que ese paso es un hito aparte. Lo que **sí** se verificó +acá: la **bit-reproducibilidad del build** (dos builds independientes del mismo componente → +`diff -r`): + +| Componente | Reproducible | Notas | +|---|---|---| +| **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 / arje-zero (Rust) | ☐ pendiente | rust bit-reproducible es más duro; el rebuild de arje-zero es caro (monorepo) | + +**La verificación cazó dos no-determinismos reales en busybox** (justo lo que [SDD 09 §2](../09-trust-model.md) pide): + +1. **Config interactiva.** Tras editar `.config`, el compile corre `silentoldconfig`, que ante + opciones NEW (WERROR, etc.) **prompea leyendo stdin** → el set de applets salía distinto por + corrida (build no-determinista *y* flaky). Fix: `yes '' | make oldconfig` (acepta defaults, + determinista; busybox 1.36.1 no tiene `olddefconfig`). +2. **Headers del kernel.** Con el config determinista, los applets de `console-tools` exigen + `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**. El próximo paso de Stage 2 es el +rebuild *dentro* de un builder rootfs y, antes, verificar/forzar la reproducibilidad de los +componentes Rust. + ## 9. Cross-check opcional — `arje-packager` arje trae su propio empaquetador (`03_ukupacha/arje/init/arje-packager`): diff --git a/recipes/busybox.toml b/recipes/busybox.toml index 3a44b97a..368fcbcd 100644 --- a/recipes/busybox.toml +++ b/recipes/busybox.toml @@ -31,7 +31,13 @@ link = "static" # `zig cc`, manteniendo la hermeticidad zig-only (ADR 0003) sin meter gcc al rootfs. # El link final asume GNU ld y pasa flags que `zig cc` valida y rechaza (`--warn-common`, `-Map`, # `--verbose`); los quitamos de scripts/trylink. Es la incompatibilidad GNU-ld↔zig/lld esperada. -configure = "make CC='zig cc' HOSTCC='zig cc' defconfig && sed -i -e 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' -e 's/^CONFIG_TC=y/# CONFIG_TC is not set/' -e 's/^CONFIG_FEATURE_TC_INGRESS=y/# CONFIG_FEATURE_TC_INGRESS is not set/' .config && sed -i -e 's/-Wl,--warn-common//g' -e 's/-Wl,-Map,[^ ]*//g' -e 's/-Wl,--verbose//g' scripts/trylink" +# +# `yes '' | make oldconfig` tras editar .config: sin esto, el compile corre `silentoldconfig`, que +# al ver opciones NEW (p.ej. WERROR) **prompea interactivamente** leyendo stdin — build +# no-determinista y flaky (lo destapó la verificación de reproducibilidad de Stage 2). `yes ''` +# acepta el default de cada NEW ⇒ config resuelta y determinista (busybox 1.36.1 no tiene el target +# `olddefconfig`, así que usamos `oldconfig` alimentado por `yes`). +configure = "make CC='zig cc' HOSTCC='zig cc' defconfig && sed -i -e 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' -e 's/^CONFIG_TC=y/# CONFIG_TC is not set/' -e 's/^CONFIG_FEATURE_TC_INGRESS=y/# CONFIG_FEATURE_TC_INGRESS is not set/' .config && yes '' | make CC='zig cc' HOSTCC='zig cc' oldconfig && sed -i -e 's/-Wl,--warn-common//g' -e 's/-Wl,-Map,[^ ]*//g' -e 's/-Wl,--verbose//g' scripts/trylink" compile = "make -j\"$(nproc)\" CC='zig cc' HOSTCC='zig cc'" # busybox instala en CONFIG_PREFIX (no DESTDIR): bin/busybox + symlinks de cada applet. install = "make CC='zig cc' HOSTCC='zig cc' CONFIG_PREFIX=/out install" diff --git a/scripts/bootstrap-devfs.sh b/scripts/bootstrap-devfs.sh index b82b68c5..7a9e15f4 100755 --- a/scripts/bootstrap-devfs.sh +++ b/scripts/bootstrap-devfs.sh @@ -113,7 +113,11 @@ else # y Alpine no trae ese std (no hay rustup). Falta resolver el toolchain Rust del lab (plan C.2) # — ver docs/runbooks/stage1-vm-boot.md §8. Hasta entonces, los componentes C (musl, busybox) # sí construyen end-to-end. - NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils rust cargo" + # `linux-headers`: UAPI del kernel (linux/kd.h, etc.) que zig cc NO bundlea; busybox y otros + # paquetes la necesitan. zig cc nativo la encuentra en /usr/include. (Lo destapó la + # verificación de reproducibilidad de Stage 2: con el config determinista, los applets de + # console-tools la exigen.) + NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils rust cargo linux-headers" missing="" for pkg in $NEEDED; do # apk info -e devuelve el paquete si está instalado, vacío si no.