Files
takana/docs/runbooks/armador-de-kernel.md
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

265 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Runbook — el armador de kernel (`takana kernel`)
Implementa `docs/22-configurador-kernel.md` (SDD 22), que contesta
`tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md`.
**La regla que no se cruza:** takana **lee** el grafo de Kconfig, no lo resuelve. El `.config` lo
sigue produciendo el `olddefconfig` del propio kernel. Lo que la app emite son **fragmentos**
(`scripts/config -e/-d`) y una **receta derivada**.
**El hecho que ordena todo:** las fases de build entran en `Recipe::hash_inputs` y el config vive en
la fase `configure`**el config ES la identidad del artefacto**. Cambiar un símbolo cambia el
`ArtifactHash`. Por eso una perilla de UI no es un parámetro de runtime: es una edición de receta.
---
## 0. Preparar el árbol de fuentes
Los comandos necesitan un árbol de fuentes del kernel, del que sólo se leen los `Kconfig*` y los
`Makefile*`. No hace falta extraer el tarball entero (1,5 G): con estos dos juegos bastan 38 M.
```sh
curl -sSLo work/tarballs/linux-6.16.12.tar.gz \
https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.16.12.tar.gz
mkdir -p work/kconfig-6.16.12
tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kconfig-6.16.12 --strip-components=1 \
--wildcards '*/Kconfig*' '*/Makefile' '*/Makefile.*' 'linux-6.16.12/arch/x86/configs/*'
export TAKANA_KCONFIG_ROOT=work/kconfig-6.16.12
```
`work/` está en `.gitignore`. El `sha256` del tarball es el mismo que pinea `recipes/linux.toml`.
Control de salud del lector — **si esto no da 0 avisos, no confíes en lo que sigue**:
```sh
takana kernel stats
# ficheros 1646 · símbolos 18212 (15169 visibles) · select 15165 · imply 445 · avisos 0
```
## 1. Modo reversa — mirar el kernel que ya corre
Cero build, cero riesgo. Lee `/proc/config.gz` (o `/boot/config-<release>`).
```sh
takana kernel probe
```
Contesta tres cosas: cuánto de cada bundle ya rige, qué capacidad carga este kernel que esta máquina
no usa, y dónde el hardware **contradice** a un bundle aplicado. La detección va en un solo sentido:
sirve para contradecir, **nunca** para podar sola — no se puede detectar el dock que se enchufa el
mes que viene ni el fs del USB de rescate.
Si el config vivo y el catálogo son de series distintas, lo avisa: las clausuras salen aproximadas.
## 2. El catálogo
`docs/state/kernel-bundles.toml`. Un bundle **no es una lista de símbolos**: es una raíz y su
clausura. `disable = ["WIRELESS"]` son ~400 `CONFIG_*` que takana calcula.
```sh
takana kernel bundles # el catálogo con las clausuras resueltas
takana kernel bundles --check # falla si envejeció respecto de este árbol
```
`--check` es el control de frescura y hace dos cosas:
- **Símbolos idos**: un `-d` sobre un símbolo que ya no existe es un no-op silencioso. Así se
descubrió que las cuatro recetas de kernel del repo apagaban `THUNDERBOLT` (hoy `USB4`) y
`REISERFS_FS` (retirado), o sea que no apagaban nada. Corregido en `2602218`.
- **Fugas `select` sin declarar**: cada `select` nuevo que entra a un bundle es un símbolo que
upstream agregó y nadie revisó. Es la mitad barata de la curación del delta entre versiones, y
sale de comparar el grafo con el catálogo — sin IA.
Para decidir qué hacer con una fuga:
```sh
takana kernel closure WIRELESS --fixpoint
```
El punto fijo **reporta el precio, no lo aplica**. Cerrar «sin audio» exige apagar
`DRM_I915`/`NOUVEAU`/`AMD_DC`, que hacen `select` del códec HDMI: en un escritorio eso es una
decisión, no una limpieza. Se resuelve una vez, con `close_leaks` o `accept_leaks`.
## 3. Planear
```sh
takana kernel plan --recipe recipes/linux.toml \
--bundle sin-wifi --bundle sin-audio --bundle solo-ext4 \
--knob jaula-y-eio-moderna \
--out work/kernel-plans/plan.json \
--recipe-out work/kernel-plans/linux-derivada.toml
```
Emite **raíces**, no clausuras: 16 banderas, no 1775 líneas. Y lleva el `ArtifactHash` de la receta
derivada dentro del JSON, así que la UI puede decir «esto ya está construido y firmado» **sin
construir**.
⚠ Para construir la receta derivada hay que dejarla **junto a la receta base**, o sus `deps.build`
no resuelven. `plan` no escribe en `recipes/` por su cuenta: ese directorio es el corpus compartido.
**Y al construirla, `takana build` va SIEMPRE bajo el lock compartido:**
```sh
flock work/.farm-build.lock ./target/release/takana --store ./store build recipes/linux-derivada.toml
```
`takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds simultáneos
que compartan una dependencia se pisan: uno hace fetch y borra el árbol mientras el otro lo usa, y
**el árbol queda roto para siempre** — reintentar no lo arregla. Medido a escala: al invalidar
`libdrm`, ~93 de 205 recetas KDE murieron con «`/src/.zwrap/cc` is not a full path to an existing
compiler tool», que es el wrapper de zig que la receta deja **en el árbol de fuente**, barrido por el
fetch concurrente de otra. Es el ADR 0012, todavía sin decidir.
Es el mismo fichero de lock que toman `scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh`, así
que con esto quedan serializados los tres. Deliberadamente **no** está dentro de `takana build`: los
scripts de la granja ya lo toman por fuera y takana se bloquearía contra ellos.
Nada más del armador toca el store: `plan` calcula el `ArtifactHash` con `takana_build::artifact_hash`,
que es cómputo puro sobre las recetas, y `probe`/`gate`/`bundles`/`closure` sólo leen.
**Si el build muere con «no encuentro el ejecutable zig en …», el zig está bien: falta ESA versión.**
takana lo busca por **directorio versionado** (`.dev-fs/tools/zig-x86_64-linux-<ver>/`), no por el
symlink `tools/zig` ni por el `PATH`. `linux.toml` no pinea `zig_version`, pero tres de sus
`deps.build` sí — `flex`, `openssl` y `elfutils`, las tres a **0.13.0** — y la receta derivada las
hereda enteras.
## 4. El gate de no-regresión — **por objetivo**
La regla es «todo dispositivo en uso debe seguir teniendo driver». Aplicada global rechazaría
`recipes/linux.toml`, que apaga USB, HID e INPUT **a propósito** por ser el kernel de QEMU con
consola serie. Por eso el gate **no corre sin `--objective`**.
En la máquina **destino** (que no tiene por qué ser la de build):
```sh
takana kernel hw --out work/kernel-plans/hw.json
```
En la de build:
```sh
takana kernel gate --plan work/kernel-plans/plan.json \
--objective qemu-serial --devices work/kernel-plans/hw.json
```
El mismo plan **pasa** en `qemu-serial` y **bloquea** en `metal-escritorio`. Ése es todo el punto.
El gate mapea driver → símbolo leyendo las reglas `obj-$(CONFIG_X) += y.o` de los Makefiles (15 789
reglas en 3182 ficheros). Lo que no pueda mapear lo lista como **sin comprobar** y **no** lo cuenta
como aprobado: un portón que calla lo que no miró no es un portón. Los que suelen quedar fuera son
built-ins de núcleo (`pcieport`, `serial8250`) cuyo nombre de driver no coincide con el del módulo.
## 4.bis Probar un plan SIN construir (30 s en vez de 40 min)
Lo caro es `compile`, no `configure`. La cadena entera del armador vive en `configure`, así que se
puede validar un plan completo sin pagar un build:
```sh
tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kernel-test # el árbol ENTERO, 1,7 G
cd work/kernel-test/linux-6.16.12
python3 -c "import json;print(json.load(open('../../kernel-plans/plan.json'))['configure'])" > /tmp/cfg.sh
bash /tmp/cfg.sh # defconfig + fragmento + olddefconfig
takana kernel diff-back --plan ../../kernel-plans/plan.json --config .config
```
Necesita `gcc`, `make`, `flex`, `bison` y `perl` en el host (`bc` sólo hace falta para compilar).
**Para medir la clausura contra la verdad de campo, la base tiene que ser `defconfig` pelado**, no
el config de `linux.toml`: esa receta ya apaga wifi/audio/fs a mano, así que el lado `disable` del
plan no apagaría nada y la predicción no se pondría a prueba. Es el error que cometí la primera vez.
## 4.ter Caso resuelto: el kernel a medida de gioser
`docs/state/kernel-plans/` guarda el plan y la receta derivada del primer kernel que salió del
armador de punta a punta. Reproducirlo:
```sh
takana kernel plan --recipe recipes/linux-metal.toml \
--bundle sin-wifi --bundle sin-bluetooth --bundle sin-nfc --bundle sin-radios \
--bundle sin-audio --bundle sin-camaras-tv --bundle sin-infiniband --bundle sin-bus-industrial \
--bundle sin-thunderbolt-firewire --bundle sin-lector-de-tarjetas --bundle sin-virtualizacion \
--bundle sin-gpu-intel --bundle solo-ext4 \
--knob invitado-virtio --knob plataforma-pc --knob jaula-y-eio-moderna \
--out plan.json --recipe-out linux-gioser.toml
```
Resultado: `bzImage` de **11,1 MB** contra los 14,5 MB de `linux-metal` (23%), 1647 símbolos
encendidos contra 1805, diff-back limpio y gate en verde.
**La receta derivada NO se deja en `recipes/`.** `build-state.py` y yupana barren `recipes/` y
`recipes/incoming-*/`, así que dejarla ahí la mete en el grafo compartido como deuda. Para
construir, copiala a `recipes/incoming-kernel/` **temporalmente** (sus `deps.build` resuelven contra
el catálogo padre) y sacala después. El artefacto sellado sobrevive; la receta se regenera con el
comando de arriba.
⚠ La copia guardada **ya no lleva** el `-j1` de la primera vuelta: al pagar SDD 25 H1 (MEMCG+PSI)
hubo que regenerarla contra la base nueva, y ese re-hasheo obligado fue el momento barato para
soltar la concesión y volver al `-j"$(nproc)"` que emite `plan` — capar el paralelismo en la receta
cambia el `ArtifactHash` aunque el binario sea idéntico (SDD 22 §13), y el throttling va por
entorno. Historia de los hashes: `b3:8ffd5710…` era la variante `-j1` sin MEMCG; la vigente es
`b3:b026fe14…`.
**Regenerar la derivada NO alcanza para que el MEMCG de la base llegue acá**: la receta derivada
**inlinea** la fase `configure` de su base. Cambiar `recipes/linux-metal.toml` y no regenerar deja
la derivada con el configure viejo, y el gate lo ve (el sellado sale como vigente sin la
capacidad).
## 5. Diff-back — después de construir
Sin esto la UI miente: un `-e FOO` cuya dependencia no se cumple se pierde en silencio.
```sh
takana kernel diff-back --plan work/kernel-plans/plan.json \
--config <artefacto>/boot/config-6.16.12
```
Clasifica cada símbolo pedido en cumplido / **incumplido** (el `.config` dice otra cosa) / **ausente**
(el kernel ni lo menciona: la bandera fue un no-op), con la procedencia de quién lo pidió. Sale
distinto de cero si el config no honra el plan.
## 6. El contrato de capacidades — antes de dar un kernel por bueno
`diff-back` contesta «¿el `.config` honra el PLAN?». Falta la otra mitad: **«¿el kernel trae lo que
el userland usa por nombre?»**. Son preguntas distintas — un kernel puede honrar su plan al pie de
la letra y no tener `CONFIG_MEMCG`, que nunca estuvo en ningún plan porque nadie lo pidió (SDD 25
§4: por eso `memory.max` no existe y arje escribe al vacío dejando sólo un `warn!`).
```sh
takana --store ./store kernel contract --sealed # todos los kernels sellados del store
takana kernel contract --config <art>/boot/config-7.1.2 # uno suelto (el perfil sale del nombre)
takana kernel contract --profile anfitrion-cards # el kernel VIVO de esta máquina
takana kernel contract --list # qué promete takana, y quién lo consume
```
Sale distinto de cero si falta una capacidad **exigida por el perfil**. Tres cosas que conviene
saber antes de leer su salida:
- **Corre por perfil.** `docs/state/kernel-contract.toml` mapea artefacto → perfil. `linux` es
`qemu-serial` (no hospeda Cards, y su hash es el baseline del selfhost-verify); los demás son
`anfitrion-cards`. Un artefacto **sin fila** sale **SIN COMPROBAR**, que no es lo mismo que
aprobado.
- **Se mira contra el `.config` producido**, nunca contra la receta: entre el `scripts/config -e X`
y el `.config` está `olddefconfig`.
- **Hoy sale rojo a propósito**: 2 de 11 configs sellados cumplen. Es H1 de SDD 25 sin pagar
(`-e MEMCG -e PSI` re-hashea los kernels ⇒ va en tanda). Cuando se pague, este comando es la
prueba.
**Una capacidad nueva entra al contrato con consumidor —ruta y símbolo— o no entra.** «Estaría
bueno tenerlo» pertenece a un bundle del catálogo, no acá.
---
## Lo que todavía NO está
- **Sonda de arranque en VM** (#4 del handoff): construir → bootear en QEMU con el perfil de la
máquina destino → recién ahí dar de alta en el árbol de generaciones.
- **Atestación por huella** (#3): `takana kernel hw` ya da la huella y el plan ya da el
`ArtifactHash`; falta el eje `fingerprint` en `PackageEntry` y el quórum de `minga atestar`. Y
ojo: «booteó» no basta para atestar — la atestación tiene que llevar **qué se comprobó**, que es
el mismo dato que calcula el gate.
- **Bisección** (#5): un kernel completo tarda 3560 min ⇒ la bisección ingenua son ~4,5 h. La salida
propuesta (SDD 22 §4) es un kernel «superset» con lo bisecable como módulo: 6 **reinicios** en vez
de 6 rebuilds.
- **Curación del delta con modelo** (#2): hoy `--check` marca lo no clasificado; falta que alguien
proponga a qué bundle va cada símbolo nuevo.
- Aplicar las perillas `side = "recipe"`: hoy se **declaran** y no se aplican.