tawasuyu

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 1411Shell 141Markdown 85Rust 842918 Definiciones

ADR 0017 y 0018: ciclo de vida del kernel y soberanía del arranque

Dos preguntas de diseño convertidas en decisiones con su evidencia.

0017 — el kernel es artefacto de contenido, así que el rollback ya es
gratis y atómico: lo que falta es A/B con vuelta atrás automática.
Medido sobre los 6 .config SELLADOS (no sobre las recetas): KEXEC=y en
los 6 (la primitiva está pagada, gratis), LIVEPATCH en NINGUNO —lo que
hay es HAVE_LIVEPATCH, que es capacidad de arch, no el feature— y
MODULES sólo en linux. Un livepatch ES un .ko ⇒ en los 5 kernels que
arrancan máquinas está cerrado por construcción.

0018 — la NVRAM es estado mutable compartido y Windows es otro agente
escribiendo sin coordinarse: el mismo patrón del índice de git (regla 2)
y del árbol de fuentes (ADR 0012) ⇒ reconciliación, no confianza.
Medido: efibootmgr aparece 0 veces en el repo y takana NUNCA crea una
entrada NVRAM — depende entera de la ruta EFI/BOOT/BOOTX64.EFI, que por
especificación es la de medios REMOVIBLES. Correcto para el USB, la peor
dirección posible para un disco compartido con Windows.

Se atan por KEXEC_FILE: no está en ninguno de los 6, y lockdown (que
trae Secure Boot) prohíbe el kexec_load viejo ⇒ hoy kexec y Secure Boot
no coexisten.

Abiertas a propósito: si el perfil servidor quiere livepatch (variante
con MODULES, nunca un flag en el kernel general), y si takana firma.
"No firmar" no es neutral: le traslada al usuario la clave de
recuperación de BitLocker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj

822d0b89497ec48c1e8e971ebf5f1542a8ff7b0f · por Sergio · (hace 1 mes) · padre 2a836e522 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

2 archivos · +289 −0

diff --git a/docs/adr/0017-ciclo-de-vida-del-kernel.md b/docs/adr/0017-ciclo-de-vida-del-kernel.md
new file mode 100644
index 00000000..b1a5c56b
--- /dev/null
+++ b/docs/adr/0017-ciclo-de-vida-del-kernel.md
@@ -0,0 +1,149 @@
+# ADR 0017 — Ciclo de vida del kernel: cambiarlo sin pagar el reboot
+
+- **Estado:** PROPUESTO — **dos decisiones abiertas** (ver §Lo que no decide este ADR). No
+  implementar los verbos antes de cerrar la primera.
+- **Fecha:** 2026-09-11
+- **Frontera:** `recipes/linux-*.toml`, `crates/takana-cli/src/kernel_cmd.rs`, el GC del store.
+- **Hermano:** [ADR 0018](0018-soberania-del-arranque.md) — arrancar el kernel nuevo es otro
+  problema y tiene su propio fichero. **Relacionado:** [SDD 22](../22-configurador-kernel.md)
+  (el armador), [SDD 25](../25-tasas-del-kernel.md).
+
+## Contexto
+
+El problema de actualizar kernels en una distro normal no es técnico: es que **el kernel es un
+nombre mutable**. `/boot/vmlinuz-6.16.12` es un fichero que se sobreescribe, y conservar los N
+anteriores es una convención que se rompe sola cuando `/boot` se llena. De ahí sale todo lo demás —
+el rollback deja de ser una operación y pasa a ser un procedimiento con pasos que hay que recordar
+bajo presión.
+
+En takana el kernel es un artefacto de contenido: el viejo y el nuevo coexisten sin disputarse un
+nombre. **La mitad de este problema está resuelta por construcción y sin haberlo buscado.** Esto
+importa porque cambia dónde conviene invertir:
+
+> Una distro no se hace fuerte logrando que la actualización no falle.
+> Se hace fuerte logrando que **fallar no cueste nada**.
+
+## Lo medido (2026-09-11)
+
+Sobre los `.config` **sellados en `./store`**, no sobre las recetas — el `.config` producido es la
+identidad del artefacto (SDD 22 §1) y es lo único que dice qué kernel hay de verdad.
+
+| kernel (store) | `KEXEC` | `KEXEC_FILE` | `LIVEPATCH` | `MODULES` | `EFI_STUB` | `CMDLINE_BOOL` |
+|---|---|---|---|---|---|---|
+| `linux` `d2a03c29` | ✅ | — | — | **✅** | ✅ | — |
+| `linux-generic` `23f1cc43` | ✅ | — | — | — | ✅ | ✅ |
+| `linux-metal` `2ed8f54a` | ✅ | — | — | — | ✅ | ✅ |
+| `linux-metal-dual` `714f46d6` | ✅ | — | — | — | ✅ | ✅ |
+| `linux-gioser` `8ffd5710`, `b026fe14` | ✅ | — | — | — | ✅ | ✅ |
+
+Cuatro consecuencias, y ninguna es opinión:
+
+1. **`CONFIG_KEXEC=y` en los 6.** La primitiva para cambiar de kernel sin pasar por el firmware
+   **ya está pagada en todo el corpus**, gratis, heredada del `defconfig` de x86. No hay que
+   re-hashear nada para empezar a usarla.
+2. **`CONFIG_LIVEPATCH` en ninguno.** Lo que sí aparece es `CONFIG_HAVE_LIVEPATCH=y`, que es
+   *capacidad de la arquitectura*, no el feature — confundirlos es exactamente el modo de error que
+   este repo ya tiene nombrado: la etiqueta no es el hecho.
+3. **`CONFIG_MODULES` sólo en `linux`.** Los cinco kernels que arrancan máquinas son monolíticos
+   (`recipes/linux-metal.toml:108` lo apaga a propósito). **Un livepatch *es* un `.ko`** ⇒ en esos
+   cinco kernels el livepatch está cerrado por construcción, no por falta de trabajo.
+4. **`CONFIG_KEXEC_FILE` en ninguno.** Sólo existe el syscall viejo `kexec_load`. Con *lockdown*
+   activo (lo que trae Secure Boot) el kernel **rechaza `kexec_load`** y sólo acepta
+   `kexec_file_load` con imagen firmada. ⇒ **kexec y Secure Boot no coexisten hoy**, y eso ata este
+   ADR con el 0018 §Secure Boot.
+
+Y el estado de la herramienta: `takana kernel` tiene hoy nueve subcomandos —`closure`, `probe`,
+`bundles`, `plan`, `diff-back`, `gate`, `hw`, `contract`, `stats`— y **ninguno escribe nada**. SDD 22
+construyó deliberadamente un armador que sólo analiza. Los verbos que propone este ADR serían los
+primeros del namespace que actúan sobre la máquina, y esa frontera merece cruzarse a propósito.
+
+No existen en el catálogo `kexec-tools`, `efibootmgr`, `sbsigntool`, `shim`, `mokutil` ni `efivar`.
+
+## Decisión
+
+### 1. Tres verbos, nombrados por lo que cuestan
+
+Casi toda la industria vende «live kernel update» y entrega una de tres cosas distintas. Takana
+nombra las tres por separado y **no llama a ninguna lo que no es**:
+
+| verbo | qué hace | downtime | alcance | perfil |
+|---|---|---|---|---|
+| `takana kernel stage <hash>` | deja el kernel listo; entra en el próximo reboot natural | **cero** | total | escritorio |
+| `takana kernel kexec <hash>` | reboot **suave**: salta firmware y gestor de arranque | ~2 s, **mata el userspace** | total | servidor |
+| *(livepatch)* | parchea funciones del kernel vivo | cero real | **una función** | CVE en producción |
+
+`kexec` **no es «sin rebootear»** y la ayuda del comando tiene que decirlo con esas palabras. Lo que
+ahorra son los 20–30 s de POST/UEFI, que en un servidor remoto es la mitad del downtime y en un
+portátil no es nada. Ser la distro que **no confunde las tres** ya es una posición: la confusión es
+la norma del mercado.
+
+### 2. A/B con vuelta atrás automática — esto es lo que vale
+
+`kexec` solo no hace fuerte a nadie: hace *rápido*. Lo que hace fuerte es que **si el kernel nuevo
+no llega a “sistema sano” en T segundos, la máquina vuelve sola al anterior.**
+
+- systemd-boot aproxima esto con contadores en el *nombre del fichero* (`+3-0`). Takana puede
+  hacerlo mejor porque el estado al que volver es **un hash**, no un fichero que puede no estar.
+- Qué es «sano» lo declara el perfil, no el kernel: para `perfil.servidor` es que arje-zero levantó
+  sus servicios supervisados; para un escritorio, que el compositor pintó.
+- **Sin esto, actualizar el kernel de una máquina remota da miedo, y con razón.** Es la única
+  entrada de esta lista que yo pondría primero.
+
+### 3. El kernel corriendo se identifica por **hash**, no por `uname -r`
+
+`uname -r` dice `6.16.12` y no dice qué `.config`, qué parches ni qué toolchain. Eso es el bug
+clásico de los livepatches y los módulos fuera de árbol: se construyen contra el kernel equivocado y
+el síntoma aparece meses después. Con el hash del artefacto viajando en el kernel vivo —el cmdline
+embebido ya lo permite en 5 de los 6— la pregunta «¿esto corresponde a este kernel?» pasa de
+heurística a **comparación exacta**.
+
+Es un cierre de invariante, no una comodidad.
+
+### 4. El kernel EN EJECUCIÓN es raíz del GC
+
+No el de la entrada de arranque: **el que está corriendo**. Si el GC poda el artefacto del kernel
+vivo, se pierden el rollback y la capacidad de diagnosticar, **y se pierden en silencio** — el
+sistema sigue andando perfecto hasta el día que lo necesitás. Es el mismo patrón que ya costó caro
+en el CAS: lo que no es alcanzable desde una raíz desaparece sin avisar.
+
+⇒ `scripts/store-gc.sh` y cualquier GC nuevo leen el hash del kernel vivo y lo tratan como raíz.
+Guardián con **rotura a propósito y control que tiene que pasar**, no sólo con el caso feliz.
+
+### 5. Si alguna vez hace falta livepatch, es una **variante**, no un flag
+
+La salida correcta **no** es encender `MODULES` en el kernel general —eso revierte una decisión de
+reproducibilidad y superficie de ataque para todo el mundo—. Es una variante
+`linux-servidor-livepatch` con su propio hash, para el perfil que la pida. En takana las variantes
+son baratas; lo caro es descubrir dentro de dos años que la puerta se cerró sin que nadie supiera
+que la estaba cerrando.
+
+### 6. El límite que este ADR no cruza, dicho en voz alta
+
+Para «actualizar **sin interrumpir el trabajo**» la respuesta no está en el kernel: es CRIU o drenar
+la carga a otra máquina. **Ninguna distro resuelve eso**, y prometerlo es donde empiezan las
+mentiras de la categoría. `stage` + `kexec` + rollback automático es el techo honesto.
+
+## Lo que no decide este ADR
+
+1. **¿El perfil servidor quiere livepatch?** ⛔ **ABIERTA.**
+   Si **sí**: hay que prever la variante con `MODULES` ahora y aceptar su costo (superficie de
+   módulos cargables, `vermagic`, firma de módulos si además hay Secure Boot).
+   Si **no**: hay que escribirlo como decisión explícita, para que dentro de un año nadie lo
+   «arregle» encendiendo módulos en el kernel general creyendo que fue un olvido.
+
+2. **¿`kexec` es compatible con el destino de Secure Boot?** ⛔ **ABIERTA, y depende del 0018.**
+   Hoy no lo es: falta `KEXEC_FILE` y sobra el `kexec_load` viejo. Si el 0018 decide firmar, este
+   ADR necesita `CONFIG_KEXEC_FILE=y` + `KEXEC_SIG`, que **re-hashea los seis kernels**. Si el 0018
+   decide no firmar, `kexec` funciona como está y esto se cierra sin costo.
+
+## Riesgos conocidos
+
+- **El cmdline está embebido** (`CONFIG_CMDLINE_BOOL`, `linux-metal.toml:150`) porque el arranque
+  normal es por EFI-STUB. `kexec` **no usa el stub**: carga por el protocolo de boot de Linux, así
+  que initrd y cmdline hay que pasárselos explícitos. El traspaso de la EFI memmap por `setup_data`
+  con `efi=novamap` puesto **es el punto a probar en QEMU antes de tocar metal** — no está
+  verificado y no debe darse por bueno.
+- **El monolítico es una ventaja acá**: sin módulos no hay `vermagic` ni `/lib/modules` que se
+  desincronicen. El modo de fallo clásico de «actualicé el kernel y no rebooteé» —enchufás un USB y
+  no pasa nada porque el módulo ya no está en disco— **no existe en takana**. Vale anotarlo del lado
+  del haber cuando se discuta el punto 1.
diff --git a/docs/adr/0018-soberania-del-arranque.md b/docs/adr/0018-soberania-del-arranque.md
new file mode 100644
index 00000000..3134557b
--- /dev/null
+++ b/docs/adr/0018-soberania-del-arranque.md
@@ -0,0 +1,140 @@
+# ADR 0018 — Soberanía del arranque: convivir con otro gestor sin depender de su buena fe
+
+- **Estado:** PROPUESTO — **una decisión abierta** (§Secure Boot). El resto es implementable ya.
+- **Fecha:** 2026-09-11
+- **Frontera:** `scripts/install-image-efi.sh`, `scripts/install-image.sh`, `scripts/iso-image.sh`,
+  `scripts/metal-usb-sdboot.sh`, `scripts/takana-live-install.sh`, el instalador TUI.
+- **Hermano:** [ADR 0017](0017-ciclo-de-vida-del-kernel.md). **Relacionado:**
+  [ADR 0010](0010-arranque-grafo-mirada.md), [SDD 27](../27-perfiles-instalables-y-el-instalador.md).
+
+## Contexto
+
+El caso es: el usuario tiene Windows y quiere takana al lado. Cada tanto Windows Update o el
+«Startup Repair» reordenan el arranque y takana deja de aparecer. El reflejo es tratarlo como un
+enemigo; es más útil y más productivo verlo por lo que es:
+
+> **La NVRAM del firmware es estado mutable compartido, y Windows es otro agente escribiendo en él
+> sin coordinarse con nadie.**
+
+Ese patrón este repo ya lo pagó dos veces: el índice de git es estado compartido (regla 2 de
+`CLAUDE.md`, commit `ff0b556`) y el árbol de fuentes es estado compartido ([ADR 0012](0012-arbol-de-fuentes-compartido-vs-privado.md)).
+La lección que costó las dos veces es la misma y se aplica igual acá:
+
+> No se resuelve pidiéndole al otro que se porte bien. Se resuelve con **reconciliación**, no con
+> **confianza**.
+
+Conviene además separar los dos modos de fallo, porque se arreglan distinto y se confunden siempre:
+
+- **El común (la enorme mayoría):** Windows **no borra nada**, te *desordena*. Reescribe `BootOrder`
+  poniéndose primero. Tu gestor sigue intacto; simplemente ya no es el que arranca.
+- **El feo:** algo **pisa el fichero**. Y ahí takana está hoy expuesta por diseño propio, no por
+  culpa de Windows.
+
+## Lo medido (2026-09-11)
+
+| qué | resultado |
+|---|---|
+| apariciones de `efibootmgr` en `scripts/` y `crates/` | **0** |
+| receta `efibootmgr`, `efivar` en el catálogo | **no existen** |
+| scripts que escriben `\EFI\BOOT\BOOTX64.EFI` | **5** (`install-image-efi.sh:190`, `install-image.sh`, `iso-image.sh`, `metal-usb-sdboot.sh:64`, `takana-live-install.sh`) |
+| entradas NVRAM que takana crea | **ninguna, nunca** |
+
+**El hallazgo que ordena todo el ADR:** takana **jamás crea una entrada de arranque**. Depende
+íntegramente de `\EFI\BOOT\BOOTX64.EFI`, que **por especificación UEFI es la ruta de medios
+removibles** — el último recurso que prueba el firmware cuando no encuentra nada más. Es tierra de
+nadie: la usa cualquier USB, la repueblan varias rutas de recuperación de Windows, y no le pertenece
+a ningún sistema instalado.
+
+Para un USB de bringup eso es **correcto**: es literalmente su lugar. Para una instalación en disco
+que comparte firmware con otro sistema operativo es **la peor dirección posible**, y hoy los dos
+casos comparten script.
+
+## Decisión
+
+### 1. Dos casos, dos rutas — y dejan de compartir código
+
+- **Medio removible (USB, ISO):** sigue en `\EFI\BOOT\BOOTX64.EFI`. Es su lugar por especificación.
+- **Instalación en disco:** vendor path propio **`\EFI\takana\takanax64.efi`** + entrada NVRAM
+  creada explícitamente. Deja de haber colisión de nombres con nadie.
+
+### 2. Estado de arranque **declarado** + reconciliador idempotente
+
+Takana declara cómo debe verse su arranque —vendor path, entrada NVRAM, posición en `BootOrder`— y
+**en cada arranque converge la NVRAM hacia esa declaración**. Corre siempre, no sólo cuando hay
+sospecha. Windows te desordena el lunes; el martes takana lo deshace.
+
+Y —esto no es opcional— **el reconciliador reporta que tuvo que actuar**. Si repara en silencio, el
+usuario vive con una rareza intermitente que nunca entiende. Un ausente falla ruidosamente; un
+arreglo mudo se parece demasiado a que no pasó nada (regla 3 de `CLAUDE.md`).
+
+### 3. La segunda pata, porque el reconciliador tiene huevo y gallina
+
+Si Windows deja a takana de último, **takana no arranca, y entonces el reconciliador nunca corre**.
+Hace falta algo que no dependa de haber arrancado:
+
+- **Dos punteros al mismo artefacto.** Vendor path (el de verdad) **y** una copia en la ruta
+  fallback como red. Es content-addressed: **el mismo kernel, dos entradas, coste marginal cero**.
+  Si Windows pisa la fallback, sobrevive la vendor; si desordena la NVRAM, en varios firmwares la
+  fallback todavía salva.
+- **La tecla del menú de arranque del firmware, como parte del contrato del instalador.** Suena
+  tonto. Es lo único que funciona cuando todo lo demás falló, y **ninguna distro te lo dice antes de
+  que lo necesites** — te lo dice un foro, a las dos de la mañana, desde otro dispositivo.
+
+### 4. El instalador mira el terreno y lo dice en voz alta
+
+Antes de tocar nada: qué hay en la ESP, cuánto espacio libre queda, cómo está `BootOrder`, si hay un
+Windows Boot Manager. Reportar, no adivinar.
+
+Un fallo silencioso concreto que muerde mucho: **las ESP de fábrica son de 100 MB**, Windows las
+llena, y un bzImage con EFI-stub más su initramfs **no entra**. Eso tiene que fallar **ruidosamente
+y por adelantado**, con el número medido en pantalla — nunca truncar a mitad de instalación.
+
+### 5. Respaldo del arranque como artefacto sellado
+
+Volcar NVRAM (`efibootmgr -v`) + el contenido de la ESP al store en cada instalación y cada
+actualización de arranque. «Restaurar el arranque» pasa de adivinar a **reproducir un estado
+conocido**. Barato, y es exactamente la forma de takana.
+
+### 6. Lo que takana NO hace: `os-prober`
+
+No se escanean discos ajenos para adivinar qué sistemas hay y generar entradas. Es una fuente
+clásica de roturas y de menús que mienten. El firmware **ya tiene** un menú de arranque: takana pone
+su entrada, cuida su entrada, y no toca la del vecino.
+
+### 7. Las tres trampas del vecino, que no son del gestor de arranque
+
+Van en el manual del piloto (SDD 21) porque muerden a todo el mundo y no tienen que ver con el
+loader:
+
+- **BitLocker.** Tocar Secure Boot o el orden de arranque con BitLocker activo hace que Windows pida
+  la clave de recuperación de 48 dígitos en el siguiente arranque. **Suspenderlo antes.**
+- **Fast Startup.** Windows hiberna en vez de apagar: deja la ESP y la NTFS en estado sucio, y
+  montarlas desde Linux puede corromperlas. `powercfg /h off`.
+- **El reloj.** Windows escribe la RTC en hora local y Linux la espera en UTC. Cada cambio de
+  sistema desfasa el reloj — y acá un reloj mal puesto ensucia cualquier medición con marca de
+  tiempo, que es la mitad de la evidencia del proyecto.
+
+## Lo que no decide este ADR
+
+**¿Takana firma para Secure Boot?** ⛔ **ABIERTA.** Y no es cosmética: **ata con el
+[ADR 0017](0017-ciclo-de-vida-del-kernel.md)**, porque *lockdown* prohíbe el `kexec_load` viejo, que
+es el único que los seis kernels sellados soportan hoy.
+
+El caso de takana es **el más fácil que existe**: un único bzImage, sin módulos ⇒ una sola firma,
+cero infraestructura de firma de módulos.
+
+| opción | a favor | en contra |
+|---|---|---|
+| **shim + MOK** | cero fricción: el usuario no entra al firmware | dependencia de la cadena de confianza de Microsoft, en un proyecto cuyo eje es la soberanía |
+| **Claves propias** | soberano de punta a punta, coherente con el ADN del proyecto | el usuario entra al firmware a enrolar; hay que dejar enroladas las de Microsoft o Windows deja de arrancar |
+| **No firmar** | cero trabajo hoy | **le trasladás el costo al usuario**: desactivar Secure Boot con BitLocker activo = clave de recuperación de 48 dígitos. Y cierra `kexec` bajo lockdown |
+
+La tercera fila es la que suele elegirse por omisión y la que hay que mirar de frente: «no firmar» no
+es neutral, es **mover el costo a la persona que instala**.
+
+## Dependencias que este ADR crea
+
+- Recetas nuevas: `efibootmgr` (o `efivar` y hablarle a la NVRAM desde el binario de takana).
+  Si se decide firmar: `sbsigntool`, y `shim`/`mokutil` en la variante shim.
+- Los 5 scripts de arriba se parten en «medio removible» y «disco».
+- El instalador TUI gana el informe del terreno del §4.