Los llamadores EJECUTABLES: scripts/ (incluida toda la granja), los runbooks y CLAUDE.md. Seguro porque la 3a ya garantiza que el worker emite los dos binarios, y porque en farm-lab-sync.sh el cargo build remoto precede a la invocación remota en el mismo script. Verificado: sintaxis de los 49 (bash -n / py_compile — ojo que why-differs-barrido.sh es Python con extensión .sh) y `takana hash` devuelve hash real sobre el store. NO se toca en esta etapa, a propósito: - La variable de entorno HAMMER=. Es interfaz de los scripts entre sí y hay llamadores que la fijan; renombrarla va con la etapa 4. - docs/evidencia/ y los HANDOFF: son REGISTRO de lo que se corrió ese día. Reescribir un comando dentro de una evidencia la falsifica. - docs/state/: es generado, se regenera solo. - Los ADR y los docs de diseño: texto, y `hammer` sigue funcionando. Van con la etapa 5, que es la de churn de texto.
13 KiB
Runbook — el armador de kernel (hammer kernel)
Implementa docs/22-configurador-kernel.md (SDD 22), que contesta
tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md.
La regla que no se cruza: hammer 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 HAMMER_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:
hammer 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>).
hammer 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 hammer calcula.
hammer kernel bundles # el catálogo con las clausuras resueltas
hammer kernel bundles --check # falla si envejeció respecto de este árbol
--check es el control de frescura y hace dos cosas:
- Símbolos idos: un
-dsobre un símbolo que ya no existe es un no-op silencioso. Así se descubrió que las cuatro recetas de kernel del repo apagabanTHUNDERBOLT(hoyUSB4) yREISERFS_FS(retirado), o sea que no apagaban nada. Corregido en2602218. - Fugas
selectsin declarar: cadaselectnuevo 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:
hammer 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
hammer 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, hammer build va SIEMPRE bajo el lock compartido:
flock work/.farm-build.lock ./target/release/takana --store ./store build recipes/linux-derivada.toml
hammer 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 hammer build: los
scripts de la granja ya lo toman por fuera y hammer se bloquearía contra ellos.
Nada más del armador toca el store: plan calcula el ArtifactHash con hammer_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.
hammer 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):
hammer kernel hw --out work/kernel-plans/hw.json
En la de build:
hammer 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
hammer 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:
hammer 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.
hammer 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!).
hammer --store ./store kernel contract --sealed # todos los kernels sellados del store
hammer kernel contract --config <art>/boot/config-7.1.2 # uno suelto (el perfil sale del nombre)
hammer kernel contract --profile anfitrion-cards # el kernel VIVO de esta máquina
hammer kernel contract --list # qué promete hammer, 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.tomlmapea artefacto → perfil.linuxesqemu-serial(no hospeda Cards, y su hash es el baseline del selfhost-verify); los demás sonanfitrion-cards. Un artefacto sin fila sale SIN COMPROBAR, que no es lo mismo que aprobado. - Se mira contra el
.configproducido, nunca contra la receta: entre elscripts/config -e Xy el.configestáolddefconfig. - Hoy sale rojo a propósito: 2 de 11 configs sellados cumplen. Es H1 de SDD 25 sin pagar
(
-e MEMCG -e PSIre-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):
hammer kernel hwya da la huella y el plan ya da elArtifactHash; falta el ejefingerprintenPackageEntryy el quórum deminga 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
--checkmarca 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.