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…

⎇ main docs/runbooks/armador-de-kernel.md Markdown · 264 lines · 13.1 KB History raw

takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4 476168bb0 · Sergio ·

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:

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:

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á