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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9f7c1aa040
commit
bbab5a204d
@@ -0,0 +1,143 @@
|
||||
# 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 0–6 (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.
|
||||
Reference in New Issue
Block a user