ADR 0017: la vuelta atrás del kernel, MEDIDA — y la hace el firmware

Cuatro arranques en OVMF con dos kernels REALES del store:

  1º  6.16.12 estable → stage del 7.1.2: "copiado (17142784 bytes)",
      "BootNext = Boot0004"
  2º  starting Boot0004 "takana (candidato)" → uname = 7.1.2,
      " EN PRUEBA" → confirm → "✓ promovido"
  3º  arranque normal → uname = 7.1.2: el estable cambió
  4º  stage de un candidato ROTO (4K de basura) y reinicio →
      starting Boot0002 (la ruta normal) → uname = 7.1.2 →
      "✗ el candidato Boot0004 NO arrancó: esta sesión vino del estable"

La cuarta es la que justifica todo: la máquina sobrevivió a que le
instalaran un kernel que no arranca, sin que nadie interviniera, y
además SABE que pasó.

Y lo hizo sin código nuestro: BootNext es de un solo uso y el firmware
la consume al leerla, así que si el kernel muere el siguiente arranque
ya no la encuentra y cae en BootOrder. No hay contador que mantener ni
estado que se pueda corromper — el mecanismo ES el firmware.

Se suma la detección de entradas huérfanas en boot-status, que salió de
ver al banco de pruebas perder el estado en un tmpfs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
This commit is contained in:
Sergio
2026-09-12 00:52:20 +00:00
co-authored by Claude Opus 5
parent 6642b93858
commit 93d873c41d
+43 -2
View File
@@ -1,7 +1,7 @@
# 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.
- **Estado:** PROPUESTO, con **§1, §2 y §4 IMPLEMENTADOS y verificados en OVMF** (2026-09-12).
Siguen **las dos decisiones abiertas** (ver §Lo que no decide este ADR); ninguna bloquea lo hecho.
- **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
@@ -89,6 +89,47 @@ no llega a “sistema sano” en T segundos, la máquina vuelve sola al anterior
- **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.
> **ENMIENDA 2026-09-12 — el §2 implementado, y la vuelta atrás la hace el FIRMWARE.**
>
> `takana kernel {stage, boot-status, confirm, rollback}`. El hallazgo que lo vuelve barato: hay **dos
> modos de fallo y sólo uno necesita código nuestro**.
>
> 1. **El candidato no arranca** (pánico temprano, EFI-stub que el firmware rechaza). Lo cubre
> **`BootNext`**, y sale gratis: es una variable **de un solo uso** que el firmware **consume** al
> leerla. Si el kernel muere, el siguiente arranque ya no la encuentra, cae en `BootOrder` y vuelve
> al estable **sin que corra una sola línea nuestra**. No hay contador que mantener ni estado que
> se pueda corromper — el mecanismo *es* el firmware, y por eso no se puede romper.
> 2. **Arranca pero el sistema no queda sano.** Eso el firmware no lo puede saber ⇒ confirmación
> explícita, y cada arranque sin confirmar gasta un intento.
>
> **Verificado con dos kernels REALES del store, cuatro arranques en OVMF:**
>
> | # | qué pasó | evidencia |
> |---|---|---|
> | 1 | estable 6.16.12, sin candidato → `stage` del 7.1.2 | `copiado (17142784 bytes)` · `BootNext = Boot0004` |
> | 2 | **arranca el candidato** | `starting Boot0004 "takana (candidato)"` · `AB: kernel corriendo = 7.1.2` · `⏳ EN PRUEBA` → `confirm` → `✓ promovido` |
> | 3 | arranque normal: el estable **cambió** | `AB: kernel corriendo = 7.1.2` · `sin candidato` |
> | 4 | **`stage` de un candidato ROTO** (4 KiB de basura) y reinicio | `starting Boot0002` —la ruta normal— · `AB: kernel corriendo = 7.1.2` · `✗ el candidato Boot0004 NO arrancó: esta sesión vino del estable` |
>
> La fila 4 es la que justifica todo: **la máquina sobrevivió a que le instalaran un kernel que no
> arranca, sin que nadie interviniera, y además sabe que pasó.**
>
> Tres decisiones que la prueba obligó a tomar:
> - **El candidato va a una ranura propia de la ESP**, nunca encima del estable: un fallo a media
> copia dejaría sin kernel al que volver. La copia es `tmp` + `rename`.
> - **`stage` repone el `BootOrder`** después de crear la entrada. `reconcile` deja al candidato
> primero —correcto para el ADR 0018, incorrecto acá—: un candidato tiene que arrancar **una vez**,
> no volverse el default. Para eso está `BootNext`.
> - **El estado A/B tiene que persistir.** El primer banco de pruebas lo tenía en un tmpfs y `stage` +
> reinicio lo perdía. **Falló seguro** —dijo «sin candidato» y no promovió nada— pero dejaba la
> entrada NVRAM huérfana sin que nadie lo supiera. Ahora `boot-status` se lo pregunta al firmware,
> que es la fuente de verdad, y lo dice.
>
> Y un bug que cazó un **test**, no una revisión: `en_esp` normalizaba los backslashes *después* de
> quitar la barra inicial, así que una ruta en formato EFI salía como `/EFI/…` —absoluta— y
> `Path::join` con una ruta absoluta **descarta la base**: habría escrito en el `/EFI` de la raíz del
> sistema en vez de dentro de la ESP.
### 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