El menú de arranque como navegación del grafo content-addressed de estados (no una lista de kernels). Sube el modelo de generaciones in-place a un grafo navegable y define el contrato de datos con mirada. - hammer_upgrade::boot_graph: BootGraph/BootNode (formato exacto del contrato), build() arma el DAG desde las generaciones (id = of_tree sin b3:, parents = of_tree del padre, Base para la raíz del linaje), nodo Recovery sintético colgando de la viva, emit() atómico a /run/hammer/boot-graph.json. - activate(): resuelve el id content-addressed a una generación y deja el sistema en ese nodo — no-op si ya viva, rollback paso a paso a un ancestro (reusa el rollback E4), recovery = un rollback, error honesto ante un "redo" hacia una generación huérfana (pide re-aplicar el árbol). - CLI `hammer boot graph [--out|--stdout]` y `hammer boot activate <id> [--from-select]` (lee el id que mirada deja en /run/hammer/boot-select). - Tests: DAG + activate a ancestro + recovery + redo-falla; rfc3339 sin deps. Validado e2e por el binario (3 generaciones → grafo → activar → recovery). Cierra el lado `proceso` de SDD 15 §H4 (arrancar = activar un nodo del DAG). mirada ya puede maquetar contra el grafo real (HANDOFF-arranque-grafo.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.3 KiB
ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + hammer)
Estado: aceptado (plan), sin implementar.
Fecha: 2026-07-06.
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
ides un hash,parentsforma 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_SIMPLEFBtoma 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: hoyFRAMEBUFFER_CONSOLE=ypuede 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.tomlya 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).
- squashfs+overlay en el medio/instalador → initrd chico → reprobar EFI-stub directo en OVMF, dejarlo listo para el metal. (Refinamiento #2 de [post-etapa-e].)
- Contrato de generaciones-grafo ✅ (módulo
hammer_upgrade::boot_graph+ subcomandohammer boot): el modelo in-place de generaciones se lee como un grafo de estados navegable (id =of_tree, DAG porparent),hammer boot graphemite/run/hammer/boot-graph.jsoncon el formato del contrato, yhammer boot activate <id>(o--from-select) deja el sistema en un nodo (no-op si ya vivo · rollback paso a paso a un ancestro · nodorecoverysinté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 (
hammer-live-installrama EFI): GPT+ESP con el bzImage como\EFI\BOOT\BOOTX64.EFI(EFI-stub), root/store/var en particiones; validar el disco instalado en OVMF hasta que mirada tome el control.
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.jsonde 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.