gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definitions

⎇ 3d6d25661cc690e2779a7e873089691493f2c539 docs/11-bootstrap.md Markdown · 144 lines · 7.4 KB History raw

Track posterior: Stage 0 del bootstrap (crate hammer-bootstrap) + ADR 0008 3d6d25661 · Sergio ·

1# SDD 11 — Bootstrap from-scratch (track posterior)
2
3> **Estado:** diseño. Abre el *track posterior* del [SDD 10](10-roadmap.md): bajar de "hammer
4> sobre Alpine" a "hammer sobre sí mismo". No bloquea ninguna Fase 0–6 (ya cerradas); reusa
5> el laboratorio de build ([SDD 02](02-build-lab.md)) sin retrabajo.
6
7## 1. El problema: el cordón umbilical
8
9Hoy el userland que `hammer` muta es el de Alpine (musl + busybox ya cocidos). Eso fue
10deliberado ([ADR 0002](adr/0002-alpine-first.md)): validar la capa AI-nativa antes de gastar
11meses en un bootstrap. Validada esa capa, el track posterior responde una sola pregunta:
12
13> **¿Puede `hammer` compilar el sistema completo que ejecuta, sin pedirle nada al host más
14> que un toolchain semilla fijado por hash?**
15
16Si la respuesta es sí *y* el resultado es bit-reproducible, hammer deja de heredar la
17confianza de Alpine y la deriva de su propio [log de transparencia](09-trust-model.md).
18
19## 2. Principios (heredados, no nuevos)
20
21- **Todo es una receta sellada.** Cada pieza del bootstrap es un `Recipe` ([SDD 02 §1](02-build-lab.md))
22  cuyo artefacto se direcciona por hash en el `Store`. El bootstrap es un *subgrafo* del grafo
23  de dependencias que ya construye el lab, no un mecanismo aparte.
24- **Nada del host entra al hash.** El único insumo externo es el **toolchain semilla**, y entra
25  como un artefacto importado con su sha256 **fijado** ([ADR 0006](adr/0006-pinned-commits.md)),
26  igual que un tarball upstream. El host aporta un kernel para correr procesos y nada más.
27- **`zig cc` es la semilla primaria** ([ADR 0003](adr/0003-zig-cc-builder.md)): un binario
28  hermético = compilador C/C++ + musl + headers, cross al target. Escotilla `musl-cross-make`
29  (gcc+musl) detrás de la misma interfaz de receta (`compiler = "gcc"`) para los paquetes con
30  gcc-ismos.
31
32## 3. Las tres etapas
33
34```
35        host (sólo kernel + seed pinned)
36                 │
37   ┌─────────────▼─────────────┐
38   │ Stage 0 — toolchain semilla│  zig (o musl-cross-make) ingerido al store,
39   │   sellado por hash         │  fijado por sha256. Cross → x86_64-linux-musl.
40   └─────────────┬─────────────┘
41                 │ artifact_hash(seed)  ← dependencia de build de todo lo demás
42   ┌─────────────▼─────────────┐
43   │ Stage 1 — userland mínimo  │  musl, busybox/toybox, init arje, hammerd,
44   │   cross-compilado          │  cross-compilados CON Stage 0. Sale un rootfs sellado.
45   └─────────────┬─────────────┘
46                 │ rootfs_hash(stage1)
47   ┌─────────────▼─────────────┐
48   │ Stage 2 — rebuild nativo   │  DENTRO del rootfs Stage 1 (bwrap/chroot/VM),
49   │   y verificación           │  recompila toolchain+userland con las herramientas
50   └────────────────────────────┘  de Stage 1, NO las del host. Compara hashes.
51```
52
53### Stage 0 — Toolchain semilla
54
55Modelamos el toolchain semilla como una **fuente fijada**, no como un build: un `Source` de
56tipo tarball con `sha256` pinned (`zig-<ver>-linux-x86_64.tar.xz`, o el tarball de
57`musl-cross-make`). `hammer-bootstrap` lo descarga (fuera del sandbox, como `hammer_build::download`
58ya hace para `patch_url`/`content_url`), verifica el sha256 **antes** de sellarlo, y lo deposita
59en el store con un `artifact_hash` derivado de ese sha256. A partir de ahí el resto del
60bootstrap depende del *hash del store*, no de la ruta del host.
61
62Salida: `seed_hash` (artefacto del toolchain en el store).
63
64### Stage 1 — Userland mínimo
65
66Un conjunto de recetas cuyo `[deps].build` incluye `seed_hash`. El conjunto mínimo que arranca
67y se administra solo:
68
69| Pieza | Por qué | Compilador |
70|---|---|---|
71| `musl` | libc del target | zig cc (estático) |
72| `busybox` o `toybox` | coreutils + shell | zig cc (estático) |
73| `arje` | init: PID 1 + supervisión ([ADR 0007](adr/0007-arje-como-init-propio.md)) | toolchain Rust |
74| `hammerd` | diario + bus de agente + watcher, **servicio supervisado por arje** | toolchain Rust |
75
76Todo se enlaza estático o con rpath controlado (sin depender del loader del host). El resultado
77se ensambla en un **rootfs** (árbol FHS) que a su vez se sella como un artefacto del store
78(`stage1_hash`). El init **no se escribe de cero**: se adopta `arje`
79([ADR 0007](adr/0007-arje-como-init-propio.md)), cuyo PID 1 con supervisión real entrega el
80`CRASHED` real que la Fase 5 dejó diferido — `hammerd` corre como servicio bajo arje.
81
82> El **kernel** no es el foco de este hito: se importa pinned (como la semilla) para poder
83> correr el rootfs. Construirlo desde fuente es un sub-ítem posterior, ortogonal al
84> auto-alojamiento del userland.
85
86### Stage 2 — Rebuild nativo y verificación
87
88El corte del cordón. Dentro del rootfs Stage 1 (primero vía `bwrap`/`chroot`, luego en la VM
89destino), se reconstruyen Stage 0' y Stage 1' usando **sólo** las herramientas de Stage 1.
90Se compara:
91
92```
93hash(stage1)   vs   hash(stage1')
94```
95
96- **Iguales** ⇒ el sistema se compila a sí mismo bit a bit: reproducible y auto-alojado. Hito
97  cumplido.
98- **Distintos** ⇒ hay no-determinismo (timestamps, paths embebidos, orden de enlace). Se
99  caza y se elimina; es exactamente el trabajo que [SDD 09](09-trust-model.md) §2 exige.
100
101## 4. Manifiesto de bootstrap (embrión del log de transparencia)
102
103Cada etapa anota una línea `(stage, recipe_hash, artifact_hash, seed_hash, ts)` en un
104`bootstrap.json` versionado. Ese manifiesto **es** la semilla del log de transparencia
105([SDD 09 §4](09-trust-model.md)): un tercero reproduce el bootstrap y verifica que sus hashes
106coinciden con los publicados, sin confiar en el binario del autor.
107
108## 5. Interfaz
109
110Nuevo crate `hammer-bootstrap` (orquestador) sobre `hammer-build` + `hammer-core::Store`.
111No introduce mecanismo nuevo de build: encadena recetas y persiste el manifiesto.
112
113```rust
114// hammer-bootstrap
115pub fn stage0(seed: SeedSpec, store: &Store) -> Result<ArtifactHash>;        // toolchain semilla
116pub fn stage1(seed: &ArtifactHash, store: &Store) -> Result<RootfsHash>;     // userland mínimo
117pub fn stage2(stage1: &RootfsHash, store: &Store) -> Result<VerifyReport>;   // rebuild + diff
118pub fn all(seed: SeedSpec, store: &Store) -> Result<BootstrapManifest>;
119```
120
121CLI:
122
123```
124hammer bootstrap stage0 [--seed zig|musl-cross-make]
125hammer bootstrap stage1 [--target x86_64-linux-musl]
126hammer bootstrap stage2 --verify
127hammer bootstrap --all                # las tres + reporte de reproducibilidad
128```
129
130## 6. Decisiones abiertas (fijadas en [ADR 0008](adr/0008-bootstrap-stages.md))
131
132- **Semilla primaria:** `zig` vs `musl-cross-make`. Inclinación: `zig` por hermeticidad
133  (ADR 0003), con `musl-cross-make` como escotilla por receta.
134- **busybox vs toybox** para el userland mínimo (licencia vs cobertura de comandos).
135- **Particionado/montaje** de `/store` y `/var/lib/hammer` en la imagen destino (track posterior
136  del roadmap).
137- **Kernel:** importado pinned ahora; from-source después.
138
139## 7. Hecho cuando
140
141`hammer bootstrap --all` produce un rootfs que, ejecutado en la VM destino, **reconstruye su
142propio toolchain y userland con hashes idénticos a los publicados**, sin que ninguna
143herramienta del host entre al resultado. Ese día Alpine deja de ser una dependencia y pasa a
144ser, a lo sumo, una conveniencia de desarrollo.

11-bootstrap.md

SDD 11 — Bootstrap from-scratch (track posterior)

Estado: diseño. Abre el track posterior del SDD 10: 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) 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): 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.

2. Principios (heredados, no nuevos)

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 arje, 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:

PiezaPor quéCompilador
musllibc del targetzig cc (estático)
busybox o toyboxcoreutils + shellzig cc (estático)
arjeinit: PID 1 + supervisión (ADR 0007)toolchain Rust
hammerddiario + bus de agente + watcher, servicio supervisado por arjetoolchain 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 no se escribe de cero: se adopta arje (ADR 0007), 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 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')

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): 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.

// 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 (fijadas en ADR 0008)

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.