Track posterior: Stage 0 del bootstrap (crate hammer-bootstrap) + ADR 0008

Primer eslabón ejecutable del bootstrap from-scratch (SDD 11). Stage 0 ingiere un
toolchain semilla pinned al store sin compilarlo:

- SeedSpec{kind,version,url,sha256}. seed_hash() deriva el ArtifactHash de la
  identidad pinned (clase+versión+sha256), no de rutas del host ni del url → el
  mismo tarball desde otro espejo produce el mismo artefacto.
- stage0(seed, store): descarga (curl, file:// offline-ok), verifica sha256
  ANTES de extraer, untar y seal en el store. Idempotente si ya está sellado;
  un sha erróneo no sella nada.
- Reusa el lab: hammer_build::download (+ nuevo verify_sha256 público), Store::seal.
  Staging bajo el root del store para que el rename de seal sea atómico.

ADR 0008 fija la decisión (3 stages, zig semilla primaria, musl-cross-make
escotilla, ingerir-no-compilar el Stage 0). Renumerado a 0008 porque el 0007 lo
tomó la decisión de adoptar arje como init; SDD 11 y refs alineados con arje
(init supervisado que entrega el CRASHED real, ADR 0007).

4 unit tests (hash por identidad, ingest+seal+readonly, idempotencia, rechazo de
sha) sin red (file://). Workspace 218 verde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-06-10 21:03:09 +00:00
co-authored by Claude Opus 4.8
parent ea10c9f716
commit 3d6d25661c
8 changed files with 348 additions and 6 deletions
+7 -6
View File
@@ -40,7 +40,7 @@ confianza de Alpine y la deriva de su propio [log de transparencia](09-trust-mod
└─────────────┬─────────────┘
│ artifact_hash(seed) ← dependencia de build de todo lo demás
┌─────────────▼─────────────┐
│ Stage 1 — userland mínimo │ musl, busybox/toybox, init propio, hammerd,
│ Stage 1 — userland mínimo │ musl, busybox/toybox, init arje, hammerd,
│ cross-compilado │ cross-compilados CON Stage 0. Sale un rootfs sellado.
└─────────────┬─────────────┘
│ rootfs_hash(stage1)
@@ -70,13 +70,14 @@ y se administra solo:
|---|---|---|
| `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 |
| `arje` | init: PID 1 + supervisión ([ADR 0007](adr/0007-arje-como-init-propio.md)) | toolchain Rust |
| `hammerd` | diario + bus de agente + watcher, **servicio supervisado por arje** | toolchain Rust |
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.
(`stage1_hash`). El init **no se escribe de cero**: se adopta `arje`
([ADR 0007](adr/0007-arje-como-init-propio.md)), cuyo PID 1 con supervisión real entrega el
`CRASHED` real que la Fase 5 dejó diferido — `hammerd` corre como servicio bajo arje.
> 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
@@ -126,7 +127,7 @@ hammer bootstrap stage2 --verify
hammer bootstrap --all # las tres + reporte de reproducibilidad
```
## 6. Decisiones abiertas (a fijar en ADR 0007)
## 6. Decisiones abiertas (fijadas en [ADR 0008](adr/0008-bootstrap-stages.md))
- **Semilla primaria:** `zig` vs `musl-cross-make`. Inclinación: `zig` por hermeticidad
(ADR 0003), con `musl-cross-make` como escotilla por receta.
+2
View File
@@ -32,3 +32,5 @@ Decisiones tomadas, con su contexto y consecuencias. Ver [`adr/`](adr/).
| 0004 | [No escribir nuestro propio Nix](adr/0004-no-custom-nix.md) |
| 0005 | [Hidratación por hardlinks](adr/0005-hardlink-hydration.md) |
| 0006 | [Commits fijados, no HEAD vivo](adr/0006-pinned-commits.md) |
| 0007 | [`arje` como init propio del track posterior](adr/0007-arje-como-init-propio.md) |
| 0008 | [Bootstrap en 3 stages, `zig` como semilla](adr/0008-bootstrap-stages.md) |
+50
View File
@@ -0,0 +1,50 @@
# ADR 0008 — Bootstrap from-scratch en 3 stages, con `zig` como semilla
- **Estado:** aceptada
- **Fecha:** 2026-06-10
## Contexto
El track posterior ([SDD 11](../11-bootstrap.md)) baja de "hammer sobre Alpine" a "hammer sobre
sí mismo": compilar el sistema completo sin heredar el toolchain ni el userland de Alpine. Hay
que decidir **cómo se rompe el cordón umbilical** sin reintroducir la fragilidad del Stage 0
clásico (cross-toolchain a mano) que [ADR 0003](0003-zig-cc-builder.md) ya esquivó.
## Decisión
Bootstrap en **tres etapas**, todas modeladas como recetas selladas en el `Store` (sin
mecanismo de build nuevo):
0. **Stage 0 — toolchain semilla:** se **ingiere** un toolchain ya construido (no se compila)
como una *fuente fijada por sha256*, y se sella en el store. El `ArtifactHash` se deriva de
la **identidad pinned** (clase + versión + sha256), no de rutas del host. **`zig` es la
semilla primaria**; `musl-cross-make` (gcc+musl) es escotilla detrás de la misma interfaz.
1. **Stage 1 — userland mínimo:** cross-compilado con Stage 0 (musl, busybox/toybox, el init
`arje` [ADR 0007](0007-arje-como-init-propio.md), `hammerd`), ensamblado en un rootfs sellado.
2. **Stage 2 — rebuild nativo:** dentro del rootfs Stage 1, recompilar con las herramientas de
Stage 1 y comparar hashes (`stage1` vs `stage1'`). Iguales ⇒ auto-alojado y reproducible.
El único insumo del host es la semilla pinned (más un kernel para correr procesos).
## Razones
- **Reusa el lab entero:** las etapas son subgrafos del grafo de dependencias de
[SDD 02](../02-build-lab.md); cero retrabajo.
- **`zig` semilla** por las mismas razones que ADR 0003: un binario hermético = compilador +
musl + headers, cross al target, determinista. Evita compilar gcc desde cero sólo para
arrancar.
- **Ingerir, no compilar, el Stage 0** mantiene el bootstrap finito y reproducible: el sha256
fijado ([ADR 0006](0006-pinned-commits.md)) ancla la confianza; quien audita reproduce desde
el mismo tarball.
- **Stage 2 como verificación** convierte la reproducibilidad en una propiedad *comprobable*,
no una promesa — alimenta el log de transparencia ([SDD 09](../09-trust-model.md) §4).
## Consecuencias / riesgos
- La semilla **no** se compila desde fuente: confiamos en el tarball pinned de `zig`. Es un
punto de confianza explícito y auditable (su sha256 está en el manifiesto), no oculto. Reducir
esa confianza (diverse double-compilation, semilla desde fuente) es trabajo futuro.
- El **kernel** se importa pinned en el primer hito; construirlo desde fuente es ortogonal al
auto-alojamiento del userland y queda después.
- Stage 2 expondrá no-determinismos reales (timestamps, paths embebidos, orden de enlace): es
el costo esperado de exigir bit-reproducibilidad, no una sorpresa.