1# Runbook — auto-alojamiento del toolchain (variante b, cerrada)
2
3Cierre de la **variante (b)** del SDD 11 §7.2: el toolchain del builder ya no es "todo Alpine" —
4cada pieza genuinamente en-camino se construye **desde fuente con hammer** y el auto-alojamiento de
5Stage 1 sigue siendo **bit a bit** (`of_tree(stage1') == of_tree(stage1)`).
6
7Este runbook documenta el end-state. Para el mecanismo base (rebuild in-rootfs, builder, QEMU) ver
8[stage1-vm-boot.md](stage1-vm-boot.md) §8c; para el detalle del frente rust ver
9[`scripts/rust-frontier/README.md`](../../scripts/rust-frontier/README.md).
10
11## 0. Qué demuestra / qué no
12
13- **SÍ:** el builder reconstruye los 4/4 de Stage 1 (musl, busybox, hammerd, arje-zero) con un
14 toolchain **construido por hammer desde fuente** (make, busybox, coreutils, bwrap, linux-headers
15 **y** rustc/cargo), y el `of_tree(stage1')` reproduce la referencia **bit a bit** dentro de la VM.
16- **NO:** no afirma igualdad byte-a-byte con el toolchain de Alpine. El criterio es
17 **auto-consistencia** (el toolchain hammer reproduce SU propio sello), no equivalencia con Alpine
18 (ver §3, los dos anclajes de `of_tree`).
19
20## 1. Las dos categorías de swap (clave conceptual)
21
22| categoría | piezas | efecto en `of_tree` |
23|-----------|--------|---------------------|
24| **herramientas que orquestan/copian** | make, busybox, coreutils, bwrap, linux-headers, binutils | producen **bytes idénticos** ⇒ `of_tree` NO cambia |
25| **el compilador que EMITE bytes** | rustc/cargo | un rustc distinto ⇒ bytes distintos de hammerd/arje-zero ⇒ `of_tree` DIVERGE |
26
27Por eso hay **dos anclajes** de `of_tree(stage1)`:
28
29- **`b3:9adefb82…`** — baseline con **rustc de Alpine** 1.91.1 (los 5 tools de sandbox lo mantienen).
30- **`b3:7fa6cb4e…`** (`RUST_EXPECT_REF`) — con **hammer-rust** 1.91.1 (mrustc→1.90→1.91.0→1.91.1).
31 ≠ 9adefb82 porque el compilador emite los bytes; es auto-consistente (reproducido en host y VM).
32
33## 2. Piezas del toolchain construidas desde fuente
34
35Todas estáticas musl con `zig cc` salvo donde se note. "En-camino" = la usan los `make`/`cargo` del 4/4.
36
37| pieza | receta | hash sellado | flag verify | en-camino | nota |
38|-------|--------|--------------|-------------|-----------|------|
39| GNU make 4.4.1 | `make.toml` | `fbad44ac…` | `SWAP_MAKE` | sí | herramienta base autotools |
40| busybox 1.36.1 | `busybox.toml` | `56664d70…` | `SWAP_BUSYBOX` | sí | `sh/sed/grep/awk/tar/find` |
41| GNU coreutils 9.8 | `coreutils.toml` | `b116944c…` | `SWAP_COREUTILS` | sí | multicall, `cp/mkdir/install` |
42| bwrap 0.11.0 (+libcap) | `bwrap.toml` | `f89e7160…` | `SWAP_BWRAP` | sí | el sandbox del propio lab |
43| linux-headers 6.16.12 | `linux-headers.toml` | `da3d714b…` | `SWAP_LINUX_HEADERS` | sí | swap-directorio (13 subdirs UAPI) |
44| rustc/cargo 1.91.1 | `scripts/rust-frontier/` | prefix `rust-1.91.1-prefix` | `SWAP_RUST` | sí | **compilador**, cambia `of_tree` |
45| binutils 2.45.1 | `binutils.toml` | `61400d05…` | — (`SWAPS=`) | **no (inerte)** | CC=gcc; ver §6 |
46| patch / m4 / pkgconf | `{patch,m4,pkgconf}.toml` | — | — (`SWAPS=`) | no | provenance autotools |
47
48`binutils` y `patch/m4/pkgconf` no están en el camino mínimo del 4/4 (zig provee as/ld/ar; ningún
494/4 corre `autoreconf`), así que NO tienen flag dedicado — se swapean con el escape
50`SWAPS="name=hash:rel …"`.
51
52## 3. Corridas de verificación (todas ✓ REPRODUCIBLE in-VM)
53
54Prerrequisitos: `./scripts/bootstrap-devfs.sh` (devfs Alpine + zig + g++ + musl 256-TLS-keys), KVM, y
55para `SWAP_RUST` el prefix hammer-rust en `.scratch/rust-1.91.1-prefix` (ver el README del frente).
56
57```sh
58# (a) 5 tools de sandbox acumulados (rustc = Alpine) → of_tree 9adefb82
59SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
60 KVM=1 MEM=24576 ./scripts/selfhost-verify.sh
61
62# (b) compilador hammer-rust solo → of_tree 7fa6cb4e
63SWAP_RUST=1 KVM=1 MEM=16384 PRESEED=hammerd ./scripts/selfhost-verify.sh
64
65# (c) CAPSTONE — los 6 juntos (todo el toolchain en-camino hammer-built) → of_tree 7fa6cb4e
66SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
67 SWAP_RUST=1 KVM=1 MEM=12288 PRESEED=hammerd ./scripts/selfhost-verify.sh
68```
69
70Resultados verificados:
71- **(a)** 2026-06-14, ~46 min, sobrevive presión de memoria fuerte.
72- **(b)** 2026-06-18, of_tree `7fa6cb4e` bit a bit (host y VM).
73- **(c)** 2026-06-19, builder `8ac23664` (17 entradas `--swap` + overlay rust), hammerd reconstruido
74 in-VM `d08fd273` byte-idéntico, `of_tree(stage1')=7fa6cb4e == RUST_EXPECT_REF`, ~7 min in-VM.
75 Demuestra que las dos categorías de swap **componen sin interferencia**.
76
77### `PRESEED=hammerd` es OBLIGATORIO con `SWAP_RUST`
78
79arje-zero (monorepo tawasuyu) vendorea ~1973 crates; un rebuild in-VM completo (`PRESEED=all`)
80desborda el rootfs en RAM de la VM (ENOSPC en `cargo vendor`). `PRESEED=hammerd` preseedea el
81arje-zero host-built (hammer-rust, `--locked`) y sólo reconstruye hammerd in-VM (vendor chico). Con
82esto la corrida (c) cabe en `MEM=12288` **sin swap del host**.
83
84### Reproducibilidad de arje-zero (drift cerrado)
85
86arje-zero deriva si su fuente no committea `Cargo.lock` (vendor sin `--locked` → deps no pineadas).
87Cerrado fijando el `Cargo.lock` del workspace en tawasuyu (`recipes/arje-zero.toml` → commit
88`9967b02c`): el fetch detecta el lock y vendorea `--locked` ⇒ bit-reproducible.
89
90## 4. El mecanismo de swap
91
92`hammer bootstrap builder --swap name=<hash>[:rel_path]` (repetible; `rel_path` default
93`usr/bin/<name>`) monta el artefacto sellado sobre el path del toolchain Alpine y lo ancla en el
94**hash lógico** del builder (`swaps_digest`), haciendo la procedencia auditable. Variantes:
95- **archivo** (make, busybox, coreutils, bwrap): estático musl ⇒ sin shim del loader.
96- **directorio** (linux-headers): un `rel_path` directorio reemplaza el árbol entero (remove+copy,
97 borra huérfanos Alpine) — `assemble_builder` en `hammer-bootstrap`.
98- **rust** (`swap-rust-into-toolchain.sh`): overlay aditivo+reversible de `.scratch/rust-1.91.1-prefix`
99 sobre `.dev-fs/alpine`; sólo reemplaza `/usr/bin/{rustc,cargo}` (el rustlib y `.so` de hammer no
100 chocan con los de Alpine). `--restore` deja el devfs prístino. Como el input-hash del store NO
101 incluye el rustc, `SWAP_RUST` usa un **store dedicado** (`store-rust`) para forzar el rebuild.
102
103## 5. Gotchas clave (reusables)
104
105- **zig 0.16 miscompila binarios musl grandes** ⇒ python3/cmake/binutils con `zig cc` SEGFAULTEAN al
106 correr. Fix: `CC=gcc CXX=g++` en las fases (gcc nativo, como Alpine). El compilador da igual para
107 una herramienta mientras corra.
108- **musl `PTHREAD_KEYS_MAX` 128→256:** el rustc booteado por mrustc agota los pthread keys de musl
109 ("out of TLS keys"). El `bootstrap-devfs.sh` reconstruye el loader del toolchain con 256 keys (es
110 el musl del TOOLCHAIN, no el target del 4/4 — no afecta `of_tree`).
111- **`CARGO_BUILD_JOBS=1` en el sandbox:** `codegen-units=1` NO basta — el backend paralelo de
112 rustc/LLVM (ThinLTO) divergía ~62 KB en arje-zero a >1 CPU. Serializar el jobserver lo vuelve
113 CPU-count-independiente (SDD 09 §2).
114- **`--swap` en zsh interactivo:** `$HASH:usr` se parsea como el modificador `:u` (uppercase). Bracear
115 `${HASH}:usr`. (El verify es bash, ahí es literal.)
116- **`for x in $VAR` en zsh** NO hace word-splitting — usar arrays o lista literal.
117
118## 6. binutils — inerte, verificado empíricamente
119
120`recipes/binutils.toml` (2.45.1, misma versión que Alpine) cierra la de-Alpinización del toolchain,
121pero es **inerte al `of_tree`**: el lab compila con `zig cc` (ensamblador interno + lld + `zig
122ar`/`ranlib`/`objcopy`), así que los 4/4 nunca invocan el `ld`/`as` de binutils.
123
124Verificado por bisección (host, sin VM, 2026-06-19): superponer hammer-binutils (`61400d05`) sobre
125`.dev-fs/alpine/usr/bin` (confirmando que el `ld` activo pasa a NEEDED `libc.so` sin `libbfd`) y
126reconstruir musl+busybox → **byte-idénticos** al baseline (`57b66a2e`, `56664d70`). El swap no mueve
127`of_tree`. (CC=gcc por el gotcha de zig; `link=dynamic` porque libtool descarta el `-static` del exe.)
128
129## 7. Estado
130
131**Frente de auto-alojamiento CERRADO.** Todo el toolchain en-camino es hammer-from-source y el
132auto-alojamiento está verificado bit a bit con todas las piezas juntas (capstone (c)). No quedan
133piezas del toolchain por de-Alpinizar (gcc nativo se usa sólo como bootstrap-compiler de las
134herramientas grandes, igual status que zig; el veto purista era sobre un *rustc* prebuilt, ya
135eliminado por el frente mrustc).
self-hosting-toolchain.md
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
7fa6cb4e bit a bit (host y VM).
- (c) 2026-06-19, builder
8ac23664 (17 entradas --swap + overlay rust), hammerd reconstruido
in-VM d08fd273 byte-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_path directorio reemplaza el árbol entero (remove+copy,
borra huérfanos Alpine) — assemble_builder en hammer-bootstrap.
- rust (
swap-rust-into-toolchain.sh): overlay aditivo+reversible de .scratch/rust-1.91.1-prefix
sobre .dev-fs/alpine; sólo reemplaza /usr/bin/{rustc,cargo} (el rustlib y .so de hammer no
chocan con los de Alpine). --restore deja el devfs prístino. Como el input-hash del store NO
incluye el rustc, SWAP_RUST usa 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 cc SEGFAULTEAN 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_MAX 128→256: el rustc booteado por mrustc agota los pthread keys de musl
("out of TLS keys"). El bootstrap-devfs.sh reconstruye el loader del toolchain con 256 keys (es
el musl del TOOLCHAIN, no el target del 4/4 — no afecta of_tree).
CARGO_BUILD_JOBS=1 en el sandbox: codegen-units=1 NO 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).
--swap en zsh interactivo: $HASH:usr se parsea como el modificador :u (uppercase). Bracear
${HASH}:usr. (El verify es bash, ahí es literal.)
for x in $VAR en 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).