Toda la variante (b) del SDD 11 §7.2 vivía sólo en scripts/rust-frontier/README y notas dispersas; el SDD §7.2 y el runbook §8c quedaban en "Pendiente: linux-headers → bwrap → rust/llvm". Nuevo runbook docs/runbooks/ self-hosting-toolchain.md documenta el end-state: - las dos categorías de swap y los dos anclajes de of_tree (9adefb82 rustc Alpine / 7fa6cb4e hammer-rust auto-consistente); - tabla de las piezas from-source (make/busybox/coreutils/bwrap/linux-headers /rust/binutils + patch/m4/pkgconf) con hash, flag y si están en-camino; - las 3 corridas verify (5-swap, rust, capstone 6-swap) con comandos y resultados ✓ REPRODUCIBLE in-VM; - el mecanismo --swap (archivo/directorio/rust-overlay); - gotchas reusables (zig miscompila→gcc, musl 256 TLS keys, CARGO_BUILD_JOBS=1, zsh :u / word-splitting); - binutils inerte verificado por bisección. SDD §7.2 y runbook §8c ahora marcan variante (b) cerrada y enlazan el runbook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.4 KiB
Runbook — auto-alojamiento del toolchain (variante b, cerrada)
Cierre de la variante (b) del SDD 11 §7.2: el toolchain del builder ya no es "todo Alpine" —
cada pieza genuinamente en-camino se construye desde fuente con hammer y el auto-alojamiento de
Stage 1 sigue siendo bit a bit (of_tree(stage1') == of_tree(stage1)).
Este runbook documenta el end-state. Para el mecanismo base (rebuild in-rootfs, builder, QEMU) ver
stage1-vm-boot.md §8c; para el detalle del frente rust ver
scripts/rust-frontier/README.md.
0. Qué demuestra / qué no
- SÍ: el builder reconstruye los 4/4 de Stage 1 (musl, busybox, hammerd, arje-zero) con un
toolchain construido por hammer desde fuente (make, busybox, coreutils, bwrap, linux-headers
y rustc/cargo), y el
of_tree(stage1')reproduce la referencia bit a bit dentro de la VM. - NO: no afirma igualdad byte-a-byte con el toolchain de Alpine. El criterio es
auto-consistencia (el toolchain hammer reproduce SU propio sello), no equivalencia con Alpine
(ver §3, los dos anclajes de
of_tree).
1. Las dos categorías de swap (clave conceptual)
| categoría | piezas | efecto en of_tree |
|---|---|---|
| herramientas que orquestan/copian | make, busybox, coreutils, bwrap, linux-headers, binutils | producen bytes idénticos ⇒ of_tree NO cambia |
| el compilador que EMITE bytes | rustc/cargo | un rustc distinto ⇒ bytes distintos de hammerd/arje-zero ⇒ of_tree DIVERGE |
Por eso hay dos anclajes de of_tree(stage1):
b3:9adefb82…— baseline con rustc de Alpine 1.91.1 (los 5 tools de sandbox lo mantienen).b3:7fa6cb4e…(RUST_EXPECT_REF) — con hammer-rust 1.91.1 (mrustc→1.90→1.91.0→1.91.1). ≠ 9adefb82 porque el compilador emite los bytes; es auto-consistente (reproducido en host y VM).
2. Piezas del toolchain construidas desde fuente
Todas estáticas musl con zig cc salvo donde se note. "En-camino" = la usan los make/cargo del 4/4.
| pieza | receta | hash sellado | flag verify | en-camino | nota |
|---|---|---|---|---|---|
| GNU make 4.4.1 | make.toml |
fbad44ac… |
SWAP_MAKE |
sí | herramienta base autotools |
| busybox 1.36.1 | busybox.toml |
56664d70… |
SWAP_BUSYBOX |
sí | sh/sed/grep/awk/tar/find |
| GNU coreutils 9.8 | coreutils.toml |
b116944c… |
SWAP_COREUTILS |
sí | multicall, cp/mkdir/install |
| bwrap 0.11.0 (+libcap) | bwrap.toml |
f89e7160… |
SWAP_BWRAP |
sí | el sandbox del propio lab |
| linux-headers 6.16.12 | linux-headers.toml |
da3d714b… |
SWAP_LINUX_HEADERS |
sí | swap-directorio (13 subdirs UAPI) |
| rustc/cargo 1.91.1 | scripts/rust-frontier/ |
prefix rust-1.91.1-prefix |
SWAP_RUST |
sí | compilador, cambia of_tree |
| binutils 2.45.1 | binutils.toml |
61400d05… |
— (SWAPS=) |
no (inerte) | CC=gcc; ver §6 |
| patch / m4 / pkgconf | {patch,m4,pkgconf}.toml |
— | — (SWAPS=) |
no | provenance autotools |
binutils y patch/m4/pkgconf no están en el camino mínimo del 4/4 (zig provee as/ld/ar; ningún
4/4 corre autoreconf), así que NO tienen flag dedicado — se swapean con el escape
SWAPS="name=hash:rel …".
3. Corridas de verificación (todas ✓ REPRODUCIBLE in-VM)
Prerrequisitos: ./scripts/bootstrap-devfs.sh (devfs Alpine + zig + g++ + musl 256-TLS-keys), KVM, y
para SWAP_RUST el prefix hammer-rust en .scratch/rust-1.91.1-prefix (ver el README del frente).
# (a) 5 tools de sandbox acumulados (rustc = Alpine) → of_tree 9adefb82
SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
KVM=1 MEM=24576 ./scripts/selfhost-verify.sh
# (b) compilador hammer-rust solo → of_tree 7fa6cb4e
SWAP_RUST=1 KVM=1 MEM=16384 PRESEED=hammerd ./scripts/selfhost-verify.sh
# (c) CAPSTONE — los 6 juntos (todo el toolchain en-camino hammer-built) → of_tree 7fa6cb4e
SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
SWAP_RUST=1 KVM=1 MEM=12288 PRESEED=hammerd ./scripts/selfhost-verify.sh
Resultados verificados:
- (a) 2026-06-14, ~46 min, sobrevive presión de memoria fuerte.
- (b) 2026-06-18, of_tree
7fa6cb4ebit a bit (host y VM). - (c) 2026-06-19, builder
8ac23664(17 entradas--swap+ overlay rust), hammerd reconstruido in-VMd08fd273byte-idéntico,of_tree(stage1')=7fa6cb4e == RUST_EXPECT_REF, ~7 min in-VM. Demuestra que las dos categorías de swap componen sin interferencia.
PRESEED=hammerd es OBLIGATORIO con SWAP_RUST
arje-zero (monorepo tawasuyu) vendorea ~1973 crates; un rebuild in-VM completo (PRESEED=all)
desborda el rootfs en RAM de la VM (ENOSPC en cargo vendor). PRESEED=hammerd preseedea el
arje-zero host-built (hammer-rust, --locked) y sólo reconstruye hammerd in-VM (vendor chico). Con
esto la corrida (c) cabe en MEM=12288 sin swap del host.
Reproducibilidad de arje-zero (drift cerrado)
arje-zero deriva si su fuente no committea Cargo.lock (vendor sin --locked → deps no pineadas).
Cerrado fijando el Cargo.lock del workspace en tawasuyu (recipes/arje-zero.toml → commit
9967b02c): el fetch detecta el lock y vendorea --locked ⇒ bit-reproducible.
4. El mecanismo de swap
hammer bootstrap builder --swap name=<hash>[:rel_path] (repetible; rel_path default
usr/bin/<name>) monta el artefacto sellado sobre el path del toolchain Alpine y lo ancla en el
hash lógico del builder (swaps_digest), haciendo la procedencia auditable. Variantes:
- archivo (make, busybox, coreutils, bwrap): estático musl ⇒ sin shim del loader.
- directorio (linux-headers): un
rel_pathdirectorio reemplaza el árbol entero (remove+copy, borra huérfanos Alpine) —assemble_builderenhammer-bootstrap. - rust (
swap-rust-into-toolchain.sh): overlay aditivo+reversible de.scratch/rust-1.91.1-prefixsobre.dev-fs/alpine; sólo reemplaza/usr/bin/{rustc,cargo}(el rustlib y.sode hammer no chocan con los de Alpine).--restoredeja el devfs prístino. Como el input-hash del store NO incluye el rustc,SWAP_RUSTusa un store dedicado (store-rust) para forzar el rebuild.
5. Gotchas clave (reusables)
- zig 0.16 miscompila binarios musl grandes ⇒ python3/cmake/binutils con
zig ccSEGFAULTEAN al correr. Fix:CC=gcc CXX=g++en las fases (gcc nativo, como Alpine). El compilador da igual para una herramienta mientras corra. - musl
PTHREAD_KEYS_MAX128→256: el rustc booteado por mrustc agota los pthread keys de musl ("out of TLS keys"). Elbootstrap-devfs.shreconstruye el loader del toolchain con 256 keys (es el musl del TOOLCHAIN, no el target del 4/4 — no afectaof_tree). CARGO_BUILD_JOBS=1en el sandbox:codegen-units=1NO basta — el backend paralelo de rustc/LLVM (ThinLTO) divergía ~62 KB en arje-zero a >1 CPU. Serializar el jobserver lo vuelve CPU-count-independiente (SDD 09 §2).--swapen zsh interactivo:$HASH:usrse parsea como el modificador:u(uppercase). Bracear${HASH}:usr. (El verify es bash, ahí es literal.)for x in $VARen zsh NO hace word-splitting — usar arrays o lista literal.
6. binutils — inerte, verificado empíricamente
recipes/binutils.toml (2.45.1, misma versión que Alpine) cierra la de-Alpinización del toolchain,
pero es inerte al of_tree: el lab compila con zig cc (ensamblador interno + lld + zig ar/ranlib/objcopy), así que los 4/4 nunca invocan el ld/as de binutils.
Verificado por bisección (host, sin VM, 2026-06-19): superponer hammer-binutils (61400d05) sobre
.dev-fs/alpine/usr/bin (confirmando que el ld activo pasa a NEEDED libc.so sin libbfd) y
reconstruir musl+busybox → byte-idénticos al baseline (57b66a2e, 56664d70). El swap no mueve
of_tree. (CC=gcc por el gotcha de zig; link=dynamic porque libtool descarta el -static del exe.)
7. Estado
Frente de auto-alojamiento CERRADO. Todo el toolchain en-camino es hammer-from-source y el auto-alojamiento está verificado bit a bit con todas las piezas juntas (capstone (c)). No quedan piezas del toolchain por de-Alpinizar (gcc nativo se usa sólo como bootstrap-compiler de las herramientas grandes, igual status que zig; el veto purista era sobre un rustc prebuilt, ya eliminado por el frente mrustc).