Files
takana/docs/11-bootstrap.md
T
SergioandClaude Opus 4.8 bbab5a204d docs: SDD 11 — bootstrap from-scratch (abre el track posterior)
Diseña el corte del cordón umbilical con Alpine en tres etapas, reusando el lab
de Fase 0 sin mecanismo nuevo:

- Stage 0: toolchain semilla (zig primario, musl-cross-make como escotilla)
  ingerido al store como fuente pinned por sha256 — el único insumo del host.
- Stage 1: userland mínimo cross-compilado (musl, busybox/toybox, init propio,
  hammerd) ensamblado en un rootfs sellado. El init propio habilita el CRASHED
  real diferido de Fase 5.
- Stage 2: rebuild nativo dentro del rootfs y diff de hashes stage1 vs stage1'
  ⇒ auto-alojamiento bit-reproducible.

Cada etapa anota (stage, recipe_hash, artifact_hash) en bootstrap.json, embrión
del log de transparencia (SDD 09 §4). Propone el crate hammer-bootstrap y la CLI
`hammer bootstrap stage0|stage1|stage2|--all`. Decisiones abiertas marcadas para
un futuro ADR 0007. Enlazado desde el índice de docs y el roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:51:13 +00:00

144 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDD 11 — Bootstrap from-scratch (track posterior)
> **Estado:** diseño. Abre el *track posterior* del [SDD 10](10-roadmap.md): bajar de "hammer
> sobre Alpine" a "hammer sobre sí mismo". No bloquea ninguna Fase 06 (ya cerradas); reusa
> el laboratorio de build ([SDD 02](02-build-lab.md)) sin retrabajo.
## 1. El problema: el cordón umbilical
Hoy el userland que `hammer` muta es el de Alpine (musl + busybox ya cocidos). Eso fue
deliberado ([ADR 0002](adr/0002-alpine-first.md)): validar la capa AI-nativa antes de gastar
meses en un bootstrap. Validada esa capa, el track posterior responde una sola pregunta:
> **¿Puede `hammer` compilar el sistema completo que ejecuta, sin pedirle nada al host más
> que un toolchain semilla fijado por hash?**
Si la respuesta es sí *y* el resultado es bit-reproducible, hammer deja de heredar la
confianza de Alpine y la deriva de su propio [log de transparencia](09-trust-model.md).
## 2. Principios (heredados, no nuevos)
- **Todo es una receta sellada.** Cada pieza del bootstrap es un `Recipe` ([SDD 02 §1](02-build-lab.md))
cuyo artefacto se direcciona por hash en el `Store`. El bootstrap es un *subgrafo* del grafo
de dependencias que ya construye el lab, no un mecanismo aparte.
- **Nada del host entra al hash.** El único insumo externo es el **toolchain semilla**, y entra
como un artefacto importado con su sha256 **fijado** ([ADR 0006](adr/0006-pinned-commits.md)),
igual que un tarball upstream. El host aporta un kernel para correr procesos y nada más.
- **`zig cc` es la semilla primaria** ([ADR 0003](adr/0003-zig-cc-builder.md)): un binario
hermético = compilador C/C++ + musl + headers, cross al target. Escotilla `musl-cross-make`
(gcc+musl) detrás de la misma interfaz de receta (`compiler = "gcc"`) para los paquetes con
gcc-ismos.
## 3. Las tres etapas
```
host (sólo kernel + seed pinned)
┌─────────────▼─────────────┐
│ Stage 0 — toolchain semilla│ zig (o musl-cross-make) ingerido al store,
│ sellado por hash │ fijado por sha256. Cross → x86_64-linux-musl.
└─────────────┬─────────────┘
│ artifact_hash(seed) ← dependencia de build de todo lo demás
┌─────────────▼─────────────┐
│ Stage 1 — userland mínimo │ musl, busybox/toybox, init propio, hammerd,
│ cross-compilado │ cross-compilados CON Stage 0. Sale un rootfs sellado.
└─────────────┬─────────────┘
│ rootfs_hash(stage1)
┌─────────────▼─────────────┐
│ Stage 2 — rebuild nativo │ DENTRO del rootfs Stage 1 (bwrap/chroot/VM),
│ y verificación │ recompila toolchain+userland con las herramientas
└────────────────────────────┘ de Stage 1, NO las del host. Compara hashes.
```
### Stage 0 — Toolchain semilla
Modelamos el toolchain semilla como una **fuente fijada**, no como un build: un `Source` de
tipo tarball con `sha256` pinned (`zig-<ver>-linux-x86_64.tar.xz`, o el tarball de
`musl-cross-make`). `hammer-bootstrap` lo descarga (fuera del sandbox, como `hammer_build::download`
ya hace para `patch_url`/`content_url`), verifica el sha256 **antes** de sellarlo, y lo deposita
en el store con un `artifact_hash` derivado de ese sha256. A partir de ahí el resto del
bootstrap depende del *hash del store*, no de la ruta del host.
Salida: `seed_hash` (artefacto del toolchain en el store).
### Stage 1 — Userland mínimo
Un conjunto de recetas cuyo `[deps].build` incluye `seed_hash`. El conjunto mínimo que arranca
y se administra solo:
| Pieza | Por qué | Compilador |
|---|---|---|
| `musl` | libc del target | zig cc (estático) |
| `busybox` o `toybox` | coreutils + shell | zig cc (estático) |
| `init` propio | PID 1 + bus por pipes nativo ([SDD 07](07-agent-bus.md)) | zig cc |
| `hammerd` | diario + bus de agente + watcher | el toolchain Rust del lab |
Todo se enlaza estático o con rpath controlado (sin depender del loader del host). El resultado
se ensambla en un **rootfs** (árbol FHS) que a su vez se sella como un artefacto del store
(`stage1_hash`). El init propio es la pieza que habilita `CRASHED` real (el único ítem diferido
de la Fase 5): PID 1 supervisa servicios y publica `Crashed` al bus.
> El **kernel** no es el foco de este hito: se importa pinned (como la semilla) para poder
> correr el rootfs. Construirlo desde fuente es un sub-ítem posterior, ortogonal al
> auto-alojamiento del userland.
### Stage 2 — Rebuild nativo y verificación
El corte del cordón. Dentro del rootfs Stage 1 (primero vía `bwrap`/`chroot`, luego en la VM
destino), se reconstruyen Stage 0' y Stage 1' usando **sólo** las herramientas de Stage 1.
Se compara:
```
hash(stage1) vs hash(stage1')
```
- **Iguales** ⇒ el sistema se compila a sí mismo bit a bit: reproducible y auto-alojado. Hito
cumplido.
- **Distintos** ⇒ hay no-determinismo (timestamps, paths embebidos, orden de enlace). Se
caza y se elimina; es exactamente el trabajo que [SDD 09](09-trust-model.md) §2 exige.
## 4. Manifiesto de bootstrap (embrión del log de transparencia)
Cada etapa anota una línea `(stage, recipe_hash, artifact_hash, seed_hash, ts)` en un
`bootstrap.json` versionado. Ese manifiesto **es** la semilla del log de transparencia
([SDD 09 §4](09-trust-model.md)): un tercero reproduce el bootstrap y verifica que sus hashes
coinciden con los publicados, sin confiar en el binario del autor.
## 5. Interfaz
Nuevo crate `hammer-bootstrap` (orquestador) sobre `hammer-build` + `hammer-core::Store`.
No introduce mecanismo nuevo de build: encadena recetas y persiste el manifiesto.
```rust
// hammer-bootstrap
pub fn stage0(seed: SeedSpec, store: &Store) -> Result<ArtifactHash>; // toolchain semilla
pub fn stage1(seed: &ArtifactHash, store: &Store) -> Result<RootfsHash>; // userland mínimo
pub fn stage2(stage1: &RootfsHash, store: &Store) -> Result<VerifyReport>; // rebuild + diff
pub fn all(seed: SeedSpec, store: &Store) -> Result<BootstrapManifest>;
```
CLI:
```
hammer bootstrap stage0 [--seed zig|musl-cross-make]
hammer bootstrap stage1 [--target x86_64-linux-musl]
hammer bootstrap stage2 --verify
hammer bootstrap --all # las tres + reporte de reproducibilidad
```
## 6. Decisiones abiertas (a fijar en ADR 0007)
- **Semilla primaria:** `zig` vs `musl-cross-make`. Inclinación: `zig` por hermeticidad
(ADR 0003), con `musl-cross-make` como escotilla por receta.
- **busybox vs toybox** para el userland mínimo (licencia vs cobertura de comandos).
- **Particionado/montaje** de `/store` y `/var/lib/hammer` en la imagen destino (track posterior
del roadmap).
- **Kernel:** importado pinned ahora; from-source después.
## 7. Hecho cuando
`hammer bootstrap --all` produce un rootfs que, ejecutado en la VM destino, **reconstruye su
propio toolchain y userland con hashes idénticos a los publicados**, sin que ninguna
herramienta del host entre al resultado. Ese día Alpine deja de ser una dependencia y pasa a
ser, a lo sumo, una conveniencia de desarrollo.