1# ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + hammer)
2
3**Estado:** aceptado; lado hammer implementado (pasos 1–3, 5 ✅; paso 2 ✅ vía pivote a ext4; paso 4
4—cero-parpadeo— pendiente). Instalador live EFI validado 2-stage en OVMF ✅. Falta metal real y el lado
5mirada. **Fecha:** 2026-07-06 (impl. EFI-stub 2026-07-10).
6**Contexto cruzado:** hammer (arranque/kernel/generaciones) + tawasuyu/mirada (render). El
7espejo de este ADR para el lado mirada vive en `tawasuyu/HANDOFF-arranque-grafo.md`.
8
9## Contexto
10
11hammer quiere instalarse en metal real con un arranque **soberano**: nada de `systemd-boot`
12(sigue siendo systemd) y GRUB-EFI está roto en este stack (GRUB 2.14 cuelga la rama UEFI, ver
13[etapa-metal-usb]). El compositor **mirada** (tawasuyu, `02_ruway/mirada`) ya es "lo que ves al
14arrancar": compositor Wayland + greeter + launcher sobre DRM/KMS. La idea del usuario: que el
15**menú de arranque sea de mirada**, no de un bootloader ajeno — "sólo falta ponerle su menú tipo
16GRUB".
17
18Al mirarlo, el menú no es un port de GRUB. En un Linux normal el menú es una **lista** de kernels.
19En hammer, donde el sistema es un **grafo content-addressed** de estados (SDD 15: config = paquete =
20función = proceso), el menú puede ser la **navegación de ese grafo**: elegir un nodo = activar un
21estado del sistema. Eso cierra el eslabón **proceso** que SDD 15 §H4 dejó abierto (las tres primeras
22lentes ya coinciden en el hash; faltaba el lado proceso = replay de un log monótono de estados).
23
24## Decisión
25
26El menú de arranque es un **selector gráfico sobre el grafo de estados del sistema**, renderizado por
27**mirada** sobre KMS, respaldado por el modelo de generaciones/DAG de **hammer**. La frontera entre
28los dos agentes es un **contrato de datos** (hammer describe el grafo; mirada lo dibuja y pide activar
29un nodo). Invariante de UX compartido: **cero parpadeo** de DRM desde la firmware hasta el escritorio.
30
31### Quién reside sobre qué
32
33| Capa | Reside en | Notas |
34|---|---|---|
35| Cargador EFI + entrada en el disco instalado | **hammer** | install-to-disk EFI; ver "loader" abajo. |
36| Kernel con KMS sin costura (simpledrm→i915) | **hammer** | `linux-generic.toml` ya trae `SIMPLEDRM+SYSFB_SIMPLEFB+FB_EFI+I915+FBDEV_EMULATION`. |
37| El grafo de estados (nodos que el menú ofrece) + activar nodo | **hammer** | generaciones (hammer-upgrade/recover), overlay, DAG de wawa-memo. |
38| Render + navegación del menú sobre KMS | **tawasuyu/mirada** | reusa Prezi espacial / Zoom-Z (árbol fractal) / constelaciones. |
39| Sostener el DRM master sin parpadeo menú→escritorio | **mirada** (con el kernel de hammer) | un solo framebuffer, abierto una vez, nunca re-modeset. |
40
41### El contrato (la frontera limpia)
42
43**hammer → mirada** — hammer escribe el grafo en un path bien conocido (propuesta:
44`/run/hammer/boot-graph.json`, regenerable, world-readable):
45
46```json
47{
48 "version": 1,
49 "current": "<blake3-del-nodo-vivo>",
50 "default": "<blake3-a-arrancar-si-no-hay-elección>",
51 "nodes": [
52 {
53 "id": "<blake3>",
54 "label": "texto corto para el usuario",
55 "kind": "generation | snapshot | base | recovery",
56 "created": "2026-07-06T12:00:00Z",
57 "parents": ["<blake3>", "..."],
58 "bootable": true,
59 "summary": "qué cambió respecto del padre (opcional)"
60 }
61 ]
62}
63```
64
65**mirada → hammer** — mirada pide activar el nodo elegido (propuesta: escribir el id en
66`/run/hammer/boot-select` **o** invocar `hammer boot activate <id>`). hammer valida el id contra el
67grafo, activa la generación (pivota overlay/root) y sigue el arranque. Un nodo `recovery` mapea al
68`hammer-recover --rollback` que ya existe.
69
70> El grafo es content-addressed: cada `id` es un hash, `parents` forma un DAG (no una lista lineal).
71> El modelo in-place actual de hammer (`hammer-recover`: "no hay menú de generaciones tipo NixOS")
72> se **sube** a un grafo navegable — es trabajo nuevo de hammer, no un refactor cosmético.
73
74### Cero parpadeo (invariante compartido)
75
76Un solo modo de video desde la firmware hasta el compositor:
77
781. Firmware GOP framebuffer → **`SYSFB_SIMPLEFB`** toma ese mismo FB → **i915 hace *seamless
79 takeover*** sin cambio de modo. (El kernel ya tiene los tres bits `=y`.)
802. Cmdline **`quiet loglevel=0`** + sin texto de fbcon ni cursor (`vt.global_cursor_default=0`), sin
81 getty en el VT gráfico durante el arranque. **Ajuste pendiente:** hoy `FRAMEBUFFER_CONSOLE=y`
82 puede pintar texto encima; afinar cmdline/config del kernel.
833. **mirada abre el DRM master directo** y lo sostiene desde el menú hasta el escritorio — nunca
84 suelta, nunca re-modeset.
85
86### El loader (cómo se lanza el kernel sin systemd-boot ni GRUB)
87
88El cuelgue del EFI-stub directo en el metal del usuario era por el **initrd de 100MB** (medición TPM
89+ quirk de LoadOptions del firmware). `linux-generic.toml` ya trae **squashfs+overlay** ⇒ el
90initramfs se vuelve **minúsculo** (sólo monta el squashfs). Con initrd chico, **EFI-stub directo
91vuelve a ser viable y soberano** — sin GRUB ni systemd. Es decir: squashfs+overlay no es un desvío,
92es lo que *destraba* el arranque EFI soberano y de paso el cero-parpadeo (initrd chico = menos tiempo
93muerto antes del takeover).
94
95## Plan (lado hammer)
96
971. **Este ADR + el handoff a tawasuyu** con el contrato (hecho en este commit).
982. **initrd chico → EFI-stub directo reprobado en OVMF** ✅ (2026-07-10): el disco instalado NO usa un
99 rootfs en RAM sino un **initramfs mínimo de pivote** (~770K) que resuelve `hammer-root` por LABEL y
100 hace `switch_root` a la ext4 real. Ese initrd chico es lo que destraba EFI-stub directo (el cuelgue
101 del metal era por un initrd de 100MB). Validado en OVMF por `scripts/efi-disk-boot-test.sh`. (La vía
102 squashfs+overlay del [post-etapa-e] queda como optimización futura; el pivote a ext4 ya cumple.)
1033. **Contrato de generaciones-grafo** ✅ (módulo `hammer_upgrade::boot_graph` + subcomando `hammer
104 boot`): el modelo in-place de generaciones se lee como un **grafo de estados** navegable (id =
105 `of_tree`, DAG por `parent`), `hammer boot graph` emite `/run/hammer/boot-graph.json` con el
106 formato del contrato, y `hammer boot activate <id>` (o `--from-select`) deja el sistema en un nodo
107 (no-op si ya vivo · rollback paso a paso a un ancestro · nodo `recovery` sintético · error honesto
108 ante un "redo" hacia una generación huérfana). Validado e2e por el CLI. **mirada ya puede maquetar
109 contra el grafo real.**
1104. **Afinar kernel/cmdline** para el handoff sin costura (punto cero-parpadeo).
1115. **install-to-disk EFI** ✅ (2026-07-10): el bzImage `linux-metal` ES el binario EFI
112 (`\EFI\BOOT\BOOTX64.EFI`, ruta fallback removible, sin GRUB ni systemd-boot). Como no hay LoadOptions
113 en esa ruta, el `CONFIG_CMDLINE` horneado (`… initrd=/initramfs.cpio.gz rdinit=/init`) activa el
114 pivote. Dos caminos, mismos artefactos:
115 - **host-side** `scripts/install-image-efi.sh` — GPT+ESP+root/store/state, ESP con mtools de hammer,
116 ext4 con `mke2fs -d` bajo `unshare -r`; **validado VERDE en OVMF** (`scripts/efi-disk-boot-test.sh`:
117 firmware → BOOTX64.EFI → pivote → arje-zero PID1).
118 - **live-side** `scripts/hammer-live-install.sh` rama EFI — detecta `/sys/firmware/efi` y hace
119 **MBR con ESP tipo 0xEF** (UEFI arranca una ESP MBR igual que GPT ⇒ alcanza busybox
120 fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools en el live). El kernel EFI-stub viaja en el payload
121 (`iso-image.sh INSTALLER=1` bundlea `bzImage-efi`). **Validación 2-stage VERDE en OVMF**
122 (`scripts/efi-install-test.sh`): ISO live UEFI → `hammer-install` detecta `/sys/firmware/efi` y toma
123 la rama EFI → el disco instalado bootea SOLO por EFI-stub soberano (sin GRUB) → arje-zero PID1.
124 Falta sólo el metal real (quemar USB + bootear, Secure Boot OFF).
125
126## Consecuencias
127
128- La frontera es un **fichero + un comando**, no una API compartida ⇒ los dos agentes avanzan sin
129 bloquearse. hammer puede emitir el grafo y probar EFI-stub sin esperar a mirada; mirada puede
130 maquetar el menú contra un `boot-graph.json` de ejemplo sin esperar a hammer.
131- El menú-grafo es la primera **manifestación del lado proceso** de SDD 15 §H4 — arrancar = activar
132 un nodo del DAG. No colapsa todo el modelo, pero lo hace visible.
133- No mueve la frontera de confianza (ADR 0007/0009): activar un nodo es reproducir/hidratar un árbol
134 sellado del store, no ejecutar algo nuevo.
135
136## Referencias
137
138- SDD 15 §H3/§H4 (`docs/15-frontier-ai-native.md`) — config = paquete = función = proceso; el lado
139 proceso = replay del MonotonicLog.
140- ADR 0007 (arje-zero como init propio), ADR 0009 (código content-addressed).
141- Memoria de proyecto: etapa-metal-usb (GRUB 2.14 roto, EFI-stub colgó por initrd 100MB, metal
142 booteado vía systemd-boot como crutch descartable), post-etapa-e-refinamientos (squashfs+overlay,
143 rollback-al-boot).
144- `tawasuyu/HANDOFF-arranque-grafo.md` — el espejo, lado mirada.
0010-arranque-grafo-mirada.md
ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + hammer)
Estado: aceptado; lado hammer implementado (pasos 1–3, 5 ✅; paso 2 ✅ vía pivote a ext4; paso 4
—cero-parpadeo— pendiente). Instalador live EFI validado 2-stage en OVMF ✅. Falta metal real y el lado
mirada. Fecha: 2026-07-06 (impl. EFI-stub 2026-07-10).
Contexto cruzado: hammer (arranque/kernel/generaciones) + tawasuyu/mirada (render). El
espejo de este ADR para el lado mirada vive en tawasuyu/HANDOFF-arranque-grafo.md.
Contexto
hammer quiere instalarse en metal real con un arranque soberano: nada de systemd-boot
(sigue siendo systemd) y GRUB-EFI está roto en este stack (GRUB 2.14 cuelga la rama UEFI, ver
[etapa-metal-usb]). El compositor mirada (tawasuyu, 02_ruway/mirada) ya es "lo que ves al
arrancar": compositor Wayland + greeter + launcher sobre DRM/KMS. La idea del usuario: que el
menú de arranque sea de mirada, no de un bootloader ajeno — "sólo falta ponerle su menú tipo
GRUB".
Al mirarlo, el menú no es un port de GRUB. En un Linux normal el menú es una lista de kernels.
En hammer, donde el sistema es un grafo content-addressed de estados (SDD 15: config = paquete =
función = proceso), el menú puede ser la navegación de ese grafo: elegir un nodo = activar un
estado del sistema. Eso cierra el eslabón proceso que SDD 15 §H4 dejó abierto (las tres primeras
lentes ya coinciden en el hash; faltaba el lado proceso = replay de un log monótono de estados).
Decisión
El menú de arranque es un selector gráfico sobre el grafo de estados del sistema, renderizado por
mirada sobre KMS, respaldado por el modelo de generaciones/DAG de hammer. La frontera entre
los dos agentes es un contrato de datos (hammer describe el grafo; mirada lo dibuja y pide activar
un nodo). Invariante de UX compartido: cero parpadeo de DRM desde la firmware hasta el escritorio.
Quién reside sobre qué
| Capa | Reside en | Notas |
| Cargador EFI + entrada en el disco instalado | hammer | install-to-disk EFI; ver "loader" abajo. |
| Kernel con KMS sin costura (simpledrm→i915) | hammer | linux-generic.toml ya trae SIMPLEDRM+SYSFB_SIMPLEFB+FB_EFI+I915+FBDEV_EMULATION. |
| El grafo de estados (nodos que el menú ofrece) + activar nodo | hammer | generaciones (hammer-upgrade/recover), overlay, DAG de wawa-memo. |
| Render + navegación del menú sobre KMS | tawasuyu/mirada | reusa Prezi espacial / Zoom-Z (árbol fractal) / constelaciones. |
| Sostener el DRM master sin parpadeo menú→escritorio | mirada (con el kernel de hammer) | un solo framebuffer, abierto una vez, nunca re-modeset. |
El contrato (la frontera limpia)
hammer → mirada — hammer escribe el grafo en un path bien conocido (propuesta:
/run/hammer/boot-graph.json, regenerable, world-readable):
{
"version": 1,
"current": "<blake3-del-nodo-vivo>",
"default": "<blake3-a-arrancar-si-no-hay-elección>",
"nodes": [
{
"id": "<blake3>",
"label": "texto corto para el usuario",
"kind": "generation | snapshot | base | recovery",
"created": "2026-07-06T12:00:00Z",
"parents": ["<blake3>", "..."],
"bootable": true,
"summary": "qué cambió respecto del padre (opcional)"
}
]
}
mirada → hammer — mirada pide activar el nodo elegido (propuesta: escribir el id en
/run/hammer/boot-select o invocar hammer boot activate <id>). hammer valida el id contra el
grafo, activa la generación (pivota overlay/root) y sigue el arranque. Un nodo recovery mapea al
hammer-recover --rollback que ya existe.
El grafo es content-addressed: cada id es un hash, parents forma un DAG (no una lista lineal).
El modelo in-place actual de hammer (hammer-recover: "no hay menú de generaciones tipo NixOS")
se sube a un grafo navegable — es trabajo nuevo de hammer, no un refactor cosmético.
Cero parpadeo (invariante compartido)
Un solo modo de video desde la firmware hasta el compositor:
- Firmware GOP framebuffer →
SYSFB_SIMPLEFB toma ese mismo FB → i915 hace seamless
takeover sin cambio de modo. (El kernel ya tiene los tres bits =y.)
- Cmdline
quiet loglevel=0 + sin texto de fbcon ni cursor (vt.global_cursor_default=0), sin
getty en el VT gráfico durante el arranque. Ajuste pendiente: hoy FRAMEBUFFER_CONSOLE=y
puede pintar texto encima; afinar cmdline/config del kernel.
- mirada abre el DRM master directo y lo sostiene desde el menú hasta el escritorio — nunca
suelta, nunca re-modeset.
El loader (cómo se lanza el kernel sin systemd-boot ni GRUB)
El cuelgue del EFI-stub directo en el metal del usuario era por el initrd de 100MB (medición TPM
- quirk de LoadOptions del firmware).
linux-generic.toml ya trae squashfs+overlay ⇒ el
initramfs se vuelve minúsculo (sólo monta el squashfs). Con initrd chico, EFI-stub directo
vuelve a ser viable y soberano — sin GRUB ni systemd. Es decir: squashfs+overlay no es un desvío,
es lo que destraba el arranque EFI soberano y de paso el cero-parpadeo (initrd chico = menos tiempo
muerto antes del takeover).
Plan (lado hammer)
- Este ADR + el handoff a tawasuyu con el contrato (hecho en este commit).
- initrd chico → EFI-stub directo reprobado en OVMF ✅ (2026-07-10): el disco instalado NO usa un
rootfs en RAM sino un initramfs mínimo de pivote (~770K) que resuelve
hammer-root por LABEL y
hace switch_root a la ext4 real. Ese initrd chico es lo que destraba EFI-stub directo (el cuelgue
del metal era por un initrd de 100MB). Validado en OVMF por scripts/efi-disk-boot-test.sh. (La vía
squashfs+overlay del [post-etapa-e] queda como optimización futura; el pivote a ext4 ya cumple.)
- Contrato de generaciones-grafo ✅ (módulo
hammer_upgrade::boot_graph + subcomando hammer boot): el modelo in-place de generaciones se lee como un grafo de estados navegable (id =
of_tree, DAG por parent), hammer boot graph emite /run/hammer/boot-graph.json con el
formato del contrato, y hammer boot activate <id> (o --from-select) deja el sistema en un nodo
(no-op si ya vivo · rollback paso a paso a un ancestro · nodo recovery sintético · error honesto
ante un "redo" hacia una generación huérfana). Validado e2e por el CLI. mirada ya puede maquetar
contra el grafo real.
- Afinar kernel/cmdline para el handoff sin costura (punto cero-parpadeo).
- install-to-disk EFI ✅ (2026-07-10): el bzImage
linux-metal ES el binario EFI
(\EFI\BOOT\BOOTX64.EFI, ruta fallback removible, sin GRUB ni systemd-boot). Como no hay LoadOptions
en esa ruta, el CONFIG_CMDLINE horneado (… initrd=/initramfs.cpio.gz rdinit=/init) activa el
pivote. Dos caminos, mismos artefactos:
- host-side
scripts/install-image-efi.sh — GPT+ESP+root/store/state, ESP con mtools de hammer,
ext4 con mke2fs -d bajo unshare -r; validado VERDE en OVMF (scripts/efi-disk-boot-test.sh:
firmware → BOOTX64.EFI → pivote → arje-zero PID1).
- live-side
scripts/hammer-live-install.sh rama EFI — detecta /sys/firmware/efi y hace
MBR con ESP tipo 0xEF (UEFI arranca una ESP MBR igual que GPT ⇒ alcanza busybox
fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools en el live). El kernel EFI-stub viaja en el payload
(iso-image.sh INSTALLER=1 bundlea bzImage-efi). Validación 2-stage VERDE en OVMF
(scripts/efi-install-test.sh): ISO live UEFI → hammer-install detecta /sys/firmware/efi y toma
la rama EFI → el disco instalado bootea SOLO por EFI-stub soberano (sin GRUB) → arje-zero PID1.
Falta sólo el metal real (quemar USB + bootear, Secure Boot OFF).
Consecuencias
- La frontera es un fichero + un comando, no una API compartida ⇒ los dos agentes avanzan sin
bloquearse. hammer puede emitir el grafo y probar EFI-stub sin esperar a mirada; mirada puede
maquetar el menú contra un
boot-graph.json de ejemplo sin esperar a hammer.
- El menú-grafo es la primera manifestación del lado proceso de SDD 15 §H4 — arrancar = activar
un nodo del DAG. No colapsa todo el modelo, pero lo hace visible.
- No mueve la frontera de confianza (ADR 0007/0009): activar un nodo es reproducir/hidratar un árbol
sellado del store, no ejecutar algo nuevo.
Referencias
- SDD 15 §H3/§H4 (
docs/15-frontier-ai-native.md) — config = paquete = función = proceso; el lado
proceso = replay del MonotonicLog.
- ADR 0007 (arje-zero como init propio), ADR 0009 (código content-addressed).
- Memoria de proyecto: etapa-metal-usb (GRUB 2.14 roto, EFI-stub colgó por initrd 100MB, metal
booteado vía systemd-boot como crutch descartable), post-etapa-e-refinamientos (squashfs+overlay,
rollback-al-boot).
tawasuyu/HANDOFF-arranque-grafo.md — el espejo, lado mirada.