diff --git a/docs/10-roadmap.md b/docs/10-roadmap.md index 58cee248..f5e7bfb0 100644 --- a/docs/10-roadmap.md +++ b/docs/10-roadmap.md @@ -176,11 +176,19 @@ pre-requisito de validación. --- ## Track posterior — distro propia -- Reemplazar el init de Alpine por **tu init** (bus por pipes nativo). -- **Bootstrap from-scratch** con `zig`/`musl-cross-make` (Stage 0/1/2 → imagen destino). + +Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resumen: + +- **Bootstrap from-scratch** en tres etapas ([SDD 11 §3](11-bootstrap.md)): Stage 0 (toolchain + semilla `zig`/`musl-cross-make` sellado por hash), Stage 1 (userland mínimo cross-compilado: + musl + busybox/toybox + init + `hammerd`), Stage 2 (rebuild nativo dentro del rootfs y diff + de hashes ⇒ auto-alojamiento bit-reproducible). ⏭️ **Primer paso del track.** +- Reemplazar el init de Alpine por **tu init** (bus por pipes nativo) — habilita el `CRASHED` + real que la Fase 5 dejó diferido. - Userland propio (busybox/toybox a elección), `/etc/service/*` propios. - Decidir particionado/montaje de `/store` y `/var/lib/hammer`. -- Log de transparencia para compartir en comunidad ([SDD 09](09-trust-model.md) §4). +- Log de transparencia para compartir en comunidad ([SDD 09](09-trust-model.md) §4): el + `bootstrap.json` de [SDD 11 §4](11-bootstrap.md) es su embrión. - Mini-lenguaje de consulta del sistema para la IA ([SDD 08](08-ai-integration.md) §6). --- diff --git a/docs/11-bootstrap.md b/docs/11-bootstrap.md new file mode 100644 index 00000000..ad7c2a99 --- /dev/null +++ b/docs/11-bootstrap.md @@ -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--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; // toolchain semilla +pub fn stage1(seed: &ArtifactHash, store: &Store) -> Result; // userland mínimo +pub fn stage2(stage1: &RootfsHash, store: &Store) -> Result; // rebuild + diff +pub fn all(seed: SeedSpec, store: &Store) -> Result; +``` + +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. diff --git a/docs/README.md b/docs/README.md index 61771976..2acf4af8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ discrepancia, o se corrige el código o se actualiza el SDD con un commit que ex | 08 | [Integración de la IA](08-ai-integration.md) | El bucle agéntico, seguridad, intención → `.swm` | | 09 | [Modelo de confianza](09-trust-model.md) | Reproducibilidad, verificar-no-confiar, log de transparencia | | 10 | [Roadmap](10-roadmap.md) | Fases, MVP, primer entregable | +| 11 | [Bootstrap from-scratch](11-bootstrap.md) | Track posterior: Stage 0/1/2, auto-alojamiento, semilla pinned | ## Architecture Decision Records (ADR)