1# Runbook — el armador de kernel (`takana kernel`)
2
3Implementa `docs/22-configurador-kernel.md` (SDD 22), que contesta
4`tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md`.
5
6**La regla que no se cruza:** takana **lee** el grafo de Kconfig, no lo resuelve. El `.config` lo
7sigue produciendo el `olddefconfig` del propio kernel. Lo que la app emite son **fragmentos**
8(`scripts/config -e/-d`) y una **receta derivada**.
9
10**El hecho que ordena todo:** las fases de build entran en `Recipe::hash_inputs` y el config vive en
11la fase `configure` ⇒ **el config ES la identidad del artefacto**. Cambiar un símbolo cambia el
12`ArtifactHash`. Por eso una perilla de UI no es un parámetro de runtime: es una edición de receta.
13
14---
15
16## 0. Preparar el árbol de fuentes
17
18Los comandos necesitan un árbol de fuentes del kernel, del que sólo se leen los `Kconfig*` y los
19`Makefile*`. No hace falta extraer el tarball entero (1,5 G): con estos dos juegos bastan 38 M.
20
21```sh
22curl -sSLo work/tarballs/linux-6.16.12.tar.gz \
23 https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.16.12.tar.gz
24mkdir -p work/kconfig-6.16.12
25tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kconfig-6.16.12 --strip-components=1 \
26 --wildcards '*/Kconfig*' '*/Makefile' '*/Makefile.*' 'linux-6.16.12/arch/x86/configs/*'
27export TAKANA_KCONFIG_ROOT=work/kconfig-6.16.12
28```
29
30`work/` está en `.gitignore`. El `sha256` del tarball es el mismo que pinea `recipes/linux.toml`.
31
32Control de salud del lector — **si esto no da 0 avisos, no confíes en lo que sigue**:
33
34```sh
35takana kernel stats
36# ficheros 1646 · símbolos 18212 (15169 visibles) · select 15165 · imply 445 · avisos 0
37```
38
39## 1. Modo reversa — mirar el kernel que ya corre
40
41Cero build, cero riesgo. Lee `/proc/config.gz` (o `/boot/config-<release>`).
42
43```sh
44takana kernel probe
45```
46
47Contesta tres cosas: cuánto de cada bundle ya rige, qué capacidad carga este kernel que esta máquina
48no usa, y dónde el hardware **contradice** a un bundle aplicado. La detección va en un solo sentido:
49sirve para contradecir, **nunca** para podar sola — no se puede detectar el dock que se enchufa el
50mes que viene ni el fs del USB de rescate.
51
52Si el config vivo y el catálogo son de series distintas, lo avisa: las clausuras salen aproximadas.
53
54## 2. El catálogo
55
56`docs/state/kernel-bundles.toml`. Un bundle **no es una lista de símbolos**: es una raíz y su
57clausura. `disable = ["WIRELESS"]` son ~400 `CONFIG_*` que takana calcula.
58
59```sh
60takana kernel bundles # el catálogo con las clausuras resueltas
61takana kernel bundles --check # falla si envejeció respecto de este árbol
62```
63
64`--check` es el control de frescura y hace dos cosas:
65- **Símbolos idos**: un `-d` sobre un símbolo que ya no existe es un no-op silencioso. Así se
66 descubrió que las cuatro recetas de kernel del repo apagaban `THUNDERBOLT` (hoy `USB4`) y
67 `REISERFS_FS` (retirado), o sea que no apagaban nada. Corregido en `2602218`.
68- **Fugas `select` sin declarar**: cada `select` nuevo que entra a un bundle es un símbolo que
69 upstream agregó y nadie revisó. Es la mitad barata de la curación del delta entre versiones, y
70 sale de comparar el grafo con el catálogo — sin IA.
71
72Para decidir qué hacer con una fuga:
73
74```sh
75takana kernel closure WIRELESS --fixpoint
76```
77
78El punto fijo **reporta el precio, no lo aplica**. Cerrar «sin audio» exige apagar
79`DRM_I915`/`NOUVEAU`/`AMD_DC`, que hacen `select` del códec HDMI: en un escritorio eso es una
80decisión, no una limpieza. Se resuelve una vez, con `close_leaks` o `accept_leaks`.
81
82## 3. Planear
83
84```sh
85takana kernel plan --recipe recipes/linux.toml \
86 --bundle sin-wifi --bundle sin-audio --bundle solo-ext4 \
87 --knob jaula-y-eio-moderna \
88 --out work/kernel-plans/plan.json \
89 --recipe-out work/kernel-plans/linux-derivada.toml
90```
91
92Emite **raíces**, no clausuras: 16 banderas, no 1775 líneas. Y lleva el `ArtifactHash` de la receta
93derivada dentro del JSON, así que la UI puede decir «esto ya está construido y firmado» **sin
94construir**.
95
96⚠ Para construir la receta derivada hay que dejarla **junto a la receta base**, o sus `deps.build`
97no resuelven. `plan` no escribe en `recipes/` por su cuenta: ese directorio es el corpus compartido.
98
99⚠ **Y al construirla, `takana build` va SIEMPRE bajo el lock compartido:**
100
101```sh
102flock work/.farm-build.lock ./target/release/takana --store ./store build recipes/linux-derivada.toml
103```
104
105`takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds simultáneos
106que compartan una dependencia se pisan: uno hace fetch y borra el árbol mientras el otro lo usa, y
107**el árbol queda roto para siempre** — reintentar no lo arregla. Medido a escala: al invalidar
108`libdrm`, ~93 de 205 recetas KDE murieron con «`/src/.zwrap/cc` is not a full path to an existing
109compiler tool», que es el wrapper de zig que la receta deja **en el árbol de fuente**, barrido por el
110fetch concurrente de otra. Es el ADR 0012, todavía sin decidir.
111
112Es el mismo fichero de lock que toman `scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh`, así
113que con esto quedan serializados los tres. Deliberadamente **no** está dentro de `takana build`: los
114scripts de la granja ya lo toman por fuera y takana se bloquearía contra ellos.
115
116Nada más del armador toca el store: `plan` calcula el `ArtifactHash` con `takana_build::artifact_hash`,
117que es cómputo puro sobre las recetas, y `probe`/`gate`/`bundles`/`closure` sólo leen.
118
119**Si el build muere con «no encuentro el ejecutable zig en …», el zig está bien: falta ESA versión.**
120takana lo busca por **directorio versionado** (`.dev-fs/tools/zig-x86_64-linux-<ver>/`), no por el
121symlink `tools/zig` ni por el `PATH`. `linux.toml` no pinea `zig_version`, pero tres de sus
122`deps.build` sí — `flex`, `openssl` y `elfutils`, las tres a **0.13.0** — y la receta derivada las
123hereda enteras.
124
125## 4. El gate de no-regresión — **por objetivo**
126
127La regla es «todo dispositivo en uso debe seguir teniendo driver». Aplicada global rechazaría
128`recipes/linux.toml`, que apaga USB, HID e INPUT **a propósito** por ser el kernel de QEMU con
129consola serie. Por eso el gate **no corre sin `--objective`**.
130
131En la máquina **destino** (que no tiene por qué ser la de build):
132
133```sh
134takana kernel hw --out work/kernel-plans/hw.json
135```
136
137En la de build:
138
139```sh
140takana kernel gate --plan work/kernel-plans/plan.json \
141 --objective qemu-serial --devices work/kernel-plans/hw.json
142```
143
144El mismo plan **pasa** en `qemu-serial` y **bloquea** en `metal-escritorio`. Ése es todo el punto.
145
146El gate mapea driver → símbolo leyendo las reglas `obj-$(CONFIG_X) += y.o` de los Makefiles (15 789
147reglas en 3182 ficheros). Lo que no pueda mapear lo lista como **sin comprobar** y **no** lo cuenta
148como aprobado: un portón que calla lo que no miró no es un portón. Los que suelen quedar fuera son
149built-ins de núcleo (`pcieport`, `serial8250`) cuyo nombre de driver no coincide con el del módulo.
150
151## 4.bis Probar un plan SIN construir (30 s en vez de 40 min)
152
153Lo caro es `compile`, no `configure`. La cadena entera del armador vive en `configure`, así que se
154puede validar un plan completo sin pagar un build:
155
156```sh
157tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kernel-test # el árbol ENTERO, 1,7 G
158cd work/kernel-test/linux-6.16.12
159python3 -c "import json;print(json.load(open('../../kernel-plans/plan.json'))['configure'])" > /tmp/cfg.sh
160bash /tmp/cfg.sh # defconfig + fragmento + olddefconfig
161takana kernel diff-back --plan ../../kernel-plans/plan.json --config .config
162```
163
164Necesita `gcc`, `make`, `flex`, `bison` y `perl` en el host (`bc` sólo hace falta para compilar).
165
166⚠ **Para medir la clausura contra la verdad de campo, la base tiene que ser `defconfig` pelado**, no
167el config de `linux.toml`: esa receta ya apaga wifi/audio/fs a mano, así que el lado `disable` del
168plan no apagaría nada y la predicción no se pondría a prueba. Es el error que cometí la primera vez.
169
170## 4.ter Caso resuelto: el kernel a medida de gioser
171
172`docs/state/kernel-plans/` guarda el plan y la receta derivada del primer kernel que salió del
173armador de punta a punta. Reproducirlo:
174
175```sh
176takana kernel plan --recipe recipes/linux-metal.toml \
177 --bundle sin-wifi --bundle sin-bluetooth --bundle sin-nfc --bundle sin-radios \
178 --bundle sin-audio --bundle sin-camaras-tv --bundle sin-infiniband --bundle sin-bus-industrial \
179 --bundle sin-thunderbolt-firewire --bundle sin-lector-de-tarjetas --bundle sin-virtualizacion \
180 --bundle sin-gpu-intel --bundle solo-ext4 \
181 --knob invitado-virtio --knob plataforma-pc --knob jaula-y-eio-moderna \
182 --out plan.json --recipe-out linux-gioser.toml
183```
184
185Resultado: `bzImage` de **11,1 MB** contra los 14,5 MB de `linux-metal` (−23%), 1647 símbolos
186encendidos contra 1805, diff-back limpio y gate en verde.
187
188⚠ **La receta derivada NO se deja en `recipes/`.** `build-state.py` y yupana barren `recipes/` y
189`recipes/incoming-*/`, así que dejarla ahí la mete en el grafo compartido como deuda. Para
190construir, copiala a `recipes/incoming-kernel/` **temporalmente** (sus `deps.build` resuelven contra
191el catálogo padre) y sacala después. El artefacto sellado sobrevive; la receta se regenera con el
192comando de arriba.
193
194⚠ La copia guardada **ya no lleva** el `-j1` de la primera vuelta: al pagar SDD 25 H1 (MEMCG+PSI)
195hubo que regenerarla contra la base nueva, y ese re-hasheo obligado fue el momento barato para
196soltar la concesión y volver al `-j"$(nproc)"` que emite `plan` — capar el paralelismo en la receta
197cambia el `ArtifactHash` aunque el binario sea idéntico (SDD 22 §13), y el throttling va por
198entorno. Historia de los hashes: `b3:8ffd5710…` era la variante `-j1` sin MEMCG; la vigente es
199`b3:b026fe14…`.
200
201⚠ **Regenerar la derivada NO alcanza para que el MEMCG de la base llegue acá**: la receta derivada
202**inlinea** la fase `configure` de su base. Cambiar `recipes/linux-metal.toml` y no regenerar deja
203la derivada con el configure viejo, y el gate lo ve (el sellado sale como vigente sin la
204capacidad).
205
206## 5. Diff-back — después de construir
207
208Sin esto la UI miente: un `-e FOO` cuya dependencia no se cumple se pierde en silencio.
209
210```sh
211takana kernel diff-back --plan work/kernel-plans/plan.json \
212 --config <artefacto>/boot/config-6.16.12
213```
214
215Clasifica cada símbolo pedido en cumplido / **incumplido** (el `.config` dice otra cosa) / **ausente**
216(el kernel ni lo menciona: la bandera fue un no-op), con la procedencia de quién lo pidió. Sale
217distinto de cero si el config no honra el plan.
218
219## 6. El contrato de capacidades — antes de dar un kernel por bueno
220
221`diff-back` contesta «¿el `.config` honra el PLAN?». Falta la otra mitad: **«¿el kernel trae lo que
222el userland usa por nombre?»**. Son preguntas distintas — un kernel puede honrar su plan al pie de
223la letra y no tener `CONFIG_MEMCG`, que nunca estuvo en ningún plan porque nadie lo pidió (SDD 25
224§4: por eso `memory.max` no existe y arje escribe al vacío dejando sólo un `warn!`).
225
226```sh
227takana --store ./store kernel contract --sealed # todos los kernels sellados del store
228takana kernel contract --config <art>/boot/config-7.1.2 # uno suelto (el perfil sale del nombre)
229takana kernel contract --profile anfitrion-cards # el kernel VIVO de esta máquina
230takana kernel contract --list # qué promete takana, y quién lo consume
231```
232
233Sale distinto de cero si falta una capacidad **exigida por el perfil**. Tres cosas que conviene
234saber antes de leer su salida:
235
236- **Corre por perfil.** `docs/state/kernel-contract.toml` mapea artefacto → perfil. `linux` es
237 `qemu-serial` (no hospeda Cards, y su hash es el baseline del selfhost-verify); los demás son
238 `anfitrion-cards`. Un artefacto **sin fila** sale **SIN COMPROBAR**, que no es lo mismo que
239 aprobado.
240- **Se mira contra el `.config` producido**, nunca contra la receta: entre el `scripts/config -e X`
241 y el `.config` está `olddefconfig`.
242- **Hoy sale rojo a propósito**: 2 de 11 configs sellados cumplen. Es H1 de SDD 25 sin pagar
243 (`-e MEMCG -e PSI` re-hashea los kernels ⇒ va en tanda). Cuando se pague, este comando es la
244 prueba.
245
246**Una capacidad nueva entra al contrato con consumidor —ruta y símbolo— o no entra.** «Estaría
247bueno tenerlo» pertenece a un bundle del catálogo, no acá.
248
249---
250
251## Lo que todavía NO está
252
253- **Sonda de arranque en VM** (#4 del handoff): construir → bootear en QEMU con el perfil de la
254 máquina destino → recién ahí dar de alta en el árbol de generaciones.
255- **Atestación por huella** (#3): `takana kernel hw` ya da la huella y el plan ya da el
256 `ArtifactHash`; falta el eje `fingerprint` en `PackageEntry` y el quórum de `minga atestar`. Y
257 ojo: «booteó» no basta para atestar — la atestación tiene que llevar **qué se comprobó**, que es
258 el mismo dato que calcula el gate.
259- **Bisección** (#5): un kernel completo tarda 35–60 min ⇒ la bisección ingenua son ~4,5 h. La salida
260 propuesta (SDD 22 §4) es un kernel «superset» con lo bisecable como módulo: 6 **reinicios** en vez
261 de 6 rebuilds.
262- **Curación del delta con modelo** (#2): hoy `--check` marca lo no clasificado; falta que alguien
263 proponga a qué bundle va cada símbolo nuevo.
264- Aplicar las perillas `side = "recipe"`: hoy se **declaran** y no se aplican.
armador-de-kernel.md
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.
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:
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>).
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.
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:
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
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:
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):
takana kernel hw --out work/kernel-plans/hw.json
En la de build:
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:
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:
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.
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!).
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 35–60 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.