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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user