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 1182Shell 87Rust 63Markdown 541592 Sutichaykuna

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

93d873c41dfccfebc3d04d6e327e47698769df8e · ruwaq Sergio · (1 killa ñawpaq) · tayta 6642b9385 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

1 qillqakuna · +43 −2

diff --git a/docs/adr/0017-ciclo-de-vida-del-kernel.md b/docs/adr/0017-ciclo-de-vida-del-kernel.md
index 3cc470a2..0568cad7 100644
--- a/docs/adr/0017-ciclo-de-vida-del-kernel.md
+++ b/docs/adr/0017-ciclo-de-vida-del-kernel.md
@@ -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