docs: SDD 22 — respuesta verificada al handoff de kernel-config de tawasuyu
El handoff avisa que nada suyo está verificado contra hammer («si un dato de hammer lo
contradice, manda hammer»). Esto es esa verificación: contesta sus seis preguntas del §9 con
evidencia del código, corrige lo que estaba mal y añade dos objeciones que cambian el orden.
NO es aprobación ni plan de implementación.
EL HECHO QUE REORDENA TODO, y que el handoff no podía conocer: las FASES entran en
hash_inputs, y el config del kernel vive en la fase `configure` ⇒ **el config ES la identidad
del artefacto**. Corta en los dos sentidos. A favor: §2.2 (atestar un config por huella de
hardware) sale casi gratis, porque cada config ya es un artefacto direccionado por contenido,
reproducible y firmable — no hay que inventar el mecanismo. En contra: la explosión de builds
no es hipotética, es aritmética visible en el disco, que ya crece 24 G por ciclo. Y una
«perilla» de la UI no es un parámetro de runtime: es una edición de receta.
LAS SEIS RESPUESTAS:
P1 fragmentos — YA es como §6 prescribe: defconfig + `scripts/config -e/-d` + olddefconfig,
o sea que la app nunca escribe un .config. Media implementación está en producción. Falta
el diff-back, sin el cual un símbolo pedido que no sobrevive se pierde EN SILENCIO.
P2 repro con config variable — sí, por construcción: no hay un kernel cuya reproducibilidad
dependa de congelar el config; hay N kernels, cada uno congelado. Se pueden PREFIRMAR
artefactos, no sólo configs.
P3 tiempo — 35/45/60 min según receta ⇒ la bisección de §5 son ~4,5 h de la máquina que
acaba de no arrancar. Mata la bisección ingenua.
P4 harkaq — el patrón sí, el código no: su clausura enumera fichero a fichero sobre
artefactos del store, semántica de rutas. Kconfig es otro grafo.
P5 eje huella en el índice — sí y compatible hacia atrás: PackageEntry usa campos serde
opcionales. Mismo truco que hizo pagable el campo `license` hoy.
P6 PLAN-KIKIN §4.bis — DADO POR PENDIENTE Y ESTÁ HECHO EN DOS DE TRES: linux-metal y
linux-generic ya declaran los cuatro símbolos; sólo `linux` deja IO_URING y BPF_SYSCALL
al azar del defconfig. Y ojo al método: `grep IO_URING` da positivo por un COMENTARIO;
hay que mirar los -e/-d dentro del bloque configure. Mismo error que infló el conteo de
licencias hoy y que hace mentir la regla del `.a` no-PIC.
OBJECIÓN a §2.1: «clausura» sobre Kconfig no está definida todavía. Hay tres tipos de arista
(`depends on`, `select`, `imply`) con semánticas incompatibles, y `select` fuerza símbolos
IGNORANDO los `depends on` del destino. «Alcanzable desde CONFIG_WIRELESS» da conjuntos
distintos según la arista y ninguno es la respuesta. No lo invalida: lo convierte en la parte
con contenido técnico real. Y hay con qué validarlo GRATIS: el config actual de linux.toml
YA es un juego de bundles N1 hecho a mano («-d WLAN -d WIRELESS -d CFG80211 -d MAC80211
-d RFKILL» es literalmente «no necesito wifi») sobre un kernel que arranca.
LO QUE AGREGO:
· Bisección SIN recompilar: kernel «superset» con lo bisecable como módulo ⇒ los bundles de
hardware se prueban con lista de bloqueo en la línea de arranque. 6 reinicios en vez de 6
rebuilds. No cubre lo que debe ser =y (buena parte de N2), y el superset es el kernel gordo
que N1 quiere evitar: es diagnóstico, no el de todos los días.
· El gate de no-regresión debe ser POR OBJETIVO: linux.toml apaga USB/HID/INPUT a propósito
(es el kernel de QEMU con consola serie) y un gate global rechazaría una receta sana.
· «Booteó» no basta para atestar: un kernel arranca y te deja sin red, que es el desastre que
el propio gate quiere evitar. La atestación debe llevar QUÉ se comprobó — el mismo dato que
calcula el gate: una implementación, dos usos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,223 @@
|
|||||||
|
# SDD 22 — Configurador de kernel por niveles: respuesta al handoff de tawasuyu
|
||||||
|
|
||||||
|
Escrito 2026-08-07, en respuesta a `tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md`.
|
||||||
|
|
||||||
|
El handoff avisa de su propio método: *«nada de lo que sigue está verificado contra el código de
|
||||||
|
hammer — si un dato de hammer lo contradice, manda hammer»*. Este documento es esa verificación. **No
|
||||||
|
es una aprobación ni un plan de implementación**: contesta las seis preguntas de su §9 con evidencia,
|
||||||
|
corrige lo que estaba mal, y señala dos objeciones técnicas que cambian el diseño.
|
||||||
|
|
||||||
|
**Veredicto corto:** la idea se sostiene y el sustrato está más listo de lo que el handoff supone —
|
||||||
|
el mecanismo de §6 (*«la app nunca escribe un `.config`»*) **ya es exactamente como funciona hoy**.
|
||||||
|
Pero hay **un hecho estructural que el handoff no podía conocer y que reordena todo**, y **una
|
||||||
|
objeción técnica a §2.1** que hay que resolver antes de escribir una línea.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. El hecho que reordena todo: **el config ES la identidad del artefacto**
|
||||||
|
|
||||||
|
En hammer, `Recipe::hash_inputs` es una lista blanca, y **las fases de build entran en ella**. El
|
||||||
|
config del kernel vive dentro de la fase `configure` de `recipes/linux*.toml`. Por lo tanto:
|
||||||
|
|
||||||
|
> **Cambiar un solo símbolo de config cambia el `ArtifactHash` del kernel.**
|
||||||
|
|
||||||
|
Esto no es un detalle de implementación: es la bisagra del diseño, y corta en los dos sentidos.
|
||||||
|
|
||||||
|
**A favor, y fuerte:** §2.2 (config atestado por huella de hardware) **sale casi gratis**. No hay que
|
||||||
|
inventar un mecanismo para «atestar un config»: cada config ya *es* un artefacto direccionado por
|
||||||
|
contenido, reproducible bit a bit y firmable con la maquinaria que existe. Un «config que booteó en
|
||||||
|
esta huella» es, literalmente, un `ArtifactHash` + una firma. El handoff intuyó que hammer tenía el
|
||||||
|
sustrato; lo tiene más de lo que creía.
|
||||||
|
|
||||||
|
**En contra, y también fuerte:** la explosión de builds que §2.2 quiere evitar **no es hipotética,
|
||||||
|
es aritmética visible en el disco**. Cada variante de config es un artefacto nuevo en el store, y el
|
||||||
|
store ya crece 24 G por ciclo de trabajo sólo con las variantes que producimos hoy sin querer. Con
|
||||||
|
N1 (~40–60 bundles) el espacio combinatorio es 2⁴⁰; con «perfiles publicados» —que es lo que el
|
||||||
|
handoff propone en §8— es un puñado. **La restricción de §8 no es una preferencia de diseño: es una
|
||||||
|
condición de supervivencia del disco**, y conviene decirlo así en la UI.
|
||||||
|
|
||||||
|
**Consecuencia práctica que hay que aceptar desde el día uno:** una «perilla» de la UI **no es un
|
||||||
|
parámetro de runtime, es una edición de receta**. No hay forma de tener un config variable sin
|
||||||
|
generar recetas. El diseño correcto es que `hammer kernel plan` **emita una receta derivada** (con su
|
||||||
|
propio hash) en vez de fingir que el kernel es un binario parametrizable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Las seis preguntas del §9, contestadas con evidencia
|
||||||
|
|
||||||
|
### P1 — ¿Las recetas de kernel aceptan fragmentos o sólo defconfig completo?
|
||||||
|
**Ya son fragmentos, y con la forma que §6 prescribe.** `recipes/linux.toml`, fase `configure`:
|
||||||
|
|
||||||
|
```
|
||||||
|
make ARCH=x86_64 defconfig && \
|
||||||
|
scripts/config -d MODULE_SIG -d DEBUG_INFO_BTF -e SECURITY_LANDLOCK -e AUDIT \
|
||||||
|
-d WLAN -d WIRELESS -d CFG80211 -d MAC80211 -d RFKILL … && \
|
||||||
|
make ARCH=x86_64 olddefconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
Base + deltas por símbolo + resolución por el `olddefconfig` **del propio kernel**. La app nunca
|
||||||
|
escribe un `.config`. La única diferencia con el handoff es el vehículo: `scripts/config -e/-d` en
|
||||||
|
vez de ficheros de fragmento y `merge_config.sh`. Son equivalentes y `scripts/config` es más fácil de
|
||||||
|
generar programáticamente. **Media implementación de §6 ya está en producción.**
|
||||||
|
|
||||||
|
Falta la otra mitad, que es la que da la UI veraz: **el diff-back**. Hoy nada comprueba que los
|
||||||
|
símbolos pedidos hayan sobrevivido a `olddefconfig`. Un `-e FOO` cuya dependencia no se cumple se
|
||||||
|
pierde en silencio — el mismo patrón de fallo que el `license` que caía dentro de `[deps]`.
|
||||||
|
|
||||||
|
### P2 — ¿El build es bit-reproducible con config variable?
|
||||||
|
**Sí, y por construcción.** El config no es un parámetro externo: es parte de la entrada hasheada
|
||||||
|
(§1). Cada config es su propia receta con su propio hash y su propia garantía de reproducción. No hay
|
||||||
|
un «kernel» cuya reproducibilidad dependa de congelar el config; hay N kernels, cada uno congelado.
|
||||||
|
⇒ **§2.2 puede prefirmar artefactos, no sólo configs.**
|
||||||
|
|
||||||
|
### P3 — ¿Cuánto tarda un kernel completo? 🚨
|
||||||
|
Declarado en las propias recetas: **`linux` ~35 min, `linux-metal` ~45 min, `linux-generic` ~60 min**
|
||||||
|
en la máquina de referencia. En la más lenta del parque, más.
|
||||||
|
|
||||||
|
**Esto mata la bisección ingenua de §5.** ~6 rebuilds × 45 min = **4,5 horas** de la máquina del
|
||||||
|
usuario para encontrar un bundle culpable. Nadie espera eso, y menos con la máquina que acaba de no
|
||||||
|
arrancar. El handoff ya preveía el riesgo («si es caro, tope duro de iteraciones»); la respuesta es
|
||||||
|
que **es caro**. Ver §4, donde propongo una bisección que no recompila.
|
||||||
|
|
||||||
|
### P4 — ¿harkaq puede prestar su motor de clausura?
|
||||||
|
**El patrón sí; el código no.** `crates/hammer-build/src/harkaq.rs` deriva su política enumerando
|
||||||
|
**fichero a fichero** los artefactos de las deps declaradas — es una clausura sobre el grafo de
|
||||||
|
paquetes materializado en el store, con semántica de rutas de filesystem. Kconfig es otro grafo, con
|
||||||
|
otra semántica y ~15 000 símbolos. Reusar la *idea* («política = clausura, no lista») es correcto y
|
||||||
|
está validado; reusar la *implementación* no. Hace falta un lector de Kconfig — y ahí está la
|
||||||
|
objeción del §3.
|
||||||
|
|
||||||
|
### P5 — ¿El índice firmado admite un eje más (la huella)?
|
||||||
|
**Sí, y sin romper nada.** `PackageEntry` (`crates/hammer-core/src/repo.rs`) es un struct serde cuyos
|
||||||
|
campos opcionales llevan `#[serde(default, skip_serializing_if = ...)]`. Añadir `fingerprint:
|
||||||
|
Option<String>` es compatible hacia atrás: los índices viejos siguen parseando y los clientes viejos
|
||||||
|
ignoran el campo. **Es exactamente el truco que hizo pagable el campo `license` hoy mismo.**
|
||||||
|
|
||||||
|
### P6 — El prerrequisito de PLAN-KIKIN §4.bis
|
||||||
|
**Estaba dado por pendiente y está hecho en dos de tres.** Medido:
|
||||||
|
|
||||||
|
| receta | `SECURITY_LANDLOCK` | `AUDIT` | `IO_URING` | `BPF_SYSCALL` |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `linux-metal` | ✅ `-e` | ✅ `-e` | ✅ `-e` | ✅ `-e` |
|
||||||
|
| `linux-generic` | ✅ `-e` | ✅ `-e` | ✅ `-e` | ✅ `-e` |
|
||||||
|
| `linux` | ✅ `-e` | ✅ `-e` | ❌ sólo en un comentario | ❌ sólo en un comentario |
|
||||||
|
|
||||||
|
⚠️ Y la forma de medirlo importa: `grep IO_URING recipes/linux.toml` **da positivo** porque la palabra
|
||||||
|
aparece en un comentario. Hay que mirar los `-e`/`-d` **dentro del bloque `configure`**. Es el mismo
|
||||||
|
error que hoy infló el conteo de licencias (`grep -l license` contaba comentarios y nombres de
|
||||||
|
paquete) y el que hace mentir a la regla del `.a` no-PIC. **En este repo, `grep` sobre ficheros con
|
||||||
|
comentarios densos no es una medición.**
|
||||||
|
|
||||||
|
⇒ Queda pendiente sólo `linux`, que es el kernel de QEMU/serial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Objeción técnica a §2.1: «clausura» sobre Kconfig no está bien definida todavía
|
||||||
|
|
||||||
|
La propuesta —«todo lo alcanzable desde `CONFIG_WIRELESS` menos lo alcanzable desde lo que sí
|
||||||
|
quiero»— asume un grafo con aristas de un solo tipo. Kconfig tiene **al menos tres, con semánticas
|
||||||
|
incompatibles**:
|
||||||
|
|
||||||
|
- `depends on` — arista hacia arriba: *no puedo existir sin X*.
|
||||||
|
- `select` — arista hacia abajo y **forzada**: *si yo entro, X entra*, ignorando los `depends on` de
|
||||||
|
X. Es la fuente clásica de configs inválidos.
|
||||||
|
- `imply` — sugerencia blanda.
|
||||||
|
|
||||||
|
«Alcanzable» por `depends on` y «alcanzable» por `select` dan conjuntos distintos y **ninguno de los
|
||||||
|
dos es la respuesta**. Un driver de otro subsistema puede `select` un helper de wireless sin ser
|
||||||
|
wireless: cae dentro de la clausura ingenua y apagarlo rompe algo que el usuario sí quería. Y al
|
||||||
|
revés, `select` puede meter símbolos wireless desde fuera de la clausura.
|
||||||
|
|
||||||
|
**Esto no invalida §2.1 — lo convierte en la parte con contenido técnico real**, tal como el propio
|
||||||
|
handoff dice en su §10. Pero hay que definir la semántica de arista antes de escribir el predicado, y
|
||||||
|
la definición hay que **validarla contra algo**.
|
||||||
|
|
||||||
|
### Y tenemos contra qué validarla, hoy y gratis
|
||||||
|
El config actual de `linux.toml` **ya es un juego de bundles N1 escrito a mano**. Literalmente:
|
||||||
|
|
||||||
|
```
|
||||||
|
-d WLAN -d WIRELESS -d CFG80211 -d MAC80211 -d RFKILL ← el bundle «no necesito wifi»
|
||||||
|
-d SOUND -d SND ← «no necesito audio»
|
||||||
|
-d DRM -d DRM_I915 -d DRM_AMDGPU -d DRM_NOUVEAU -d FB ← «sin gráficos»
|
||||||
|
-d XFS_FS -d BTRFS_FS -d F2FS_FS -d JFS_FS -d REISERFS_FS ← «sólo ext4»
|
||||||
|
```
|
||||||
|
|
||||||
|
Es un corpus de verdad-de-campo: alguien decidió a mano qué símbolos componen «no necesito wifi», y
|
||||||
|
ese kernel **arranca**. La prueba de §2.1 es directa y barata: *¿la clausura calculada de
|
||||||
|
`CONFIG_WIRELESS` reproduce ese conjunto?* Si sobra o falta mucho, la semántica de arista está mal y
|
||||||
|
nos enteramos en una tarde, sin compilar nada. **Refuerza el §10 del handoff: eso se hace antes que
|
||||||
|
nada, junto al modo reversa.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Lo que agrego: bisección **sin recompilar**
|
||||||
|
|
||||||
|
Dado P3 (35–60 min por kernel), la bisección de §5 cuesta horas. Pero la mayoría de los bundles N1
|
||||||
|
son **drivers**, y un driver puede ser `=m` en vez de `=y`.
|
||||||
|
|
||||||
|
Propuesta: **un kernel «superset» con todo lo bisecable como módulo**. Los bundles N1 de tipo
|
||||||
|
hardware no se resuelven entonces recompilando, sino con una **lista de bloqueo de módulos en la
|
||||||
|
línea de arranque**. La bisección deja de ser 6 rebuilds (4,5 h) y pasa a ser **6 reinicios**
|
||||||
|
(minutos), sobre un kernel que ya está en el árbol de generaciones.
|
||||||
|
|
||||||
|
Consecuencias:
|
||||||
|
- El «kernel a medida» pasa a ser una optimización de tamaño/arranque, no un requisito de
|
||||||
|
funcionalidad ⇒ el usuario puede probar bundles antes de pagar un build.
|
||||||
|
- Encaja con el árbol de generaciones y el rollback E4, que ya existen.
|
||||||
|
- **Limitación honesta:** no cubre lo que debe ser `=y` (LSM, preempción, unwinder, `-march`, LTO,
|
||||||
|
o sea buena parte de N2). Para esos sí hay rebuild y sí hay que topar iteraciones.
|
||||||
|
- **Y un coste que hay que decir:** el superset es exactamente el kernel gordo que el nivel 1 quiere
|
||||||
|
evitar. Es una herramienta de diagnóstico, no el kernel de todos los días.
|
||||||
|
|
||||||
|
## 5. Lo que agrego: el gate de no-regresión (§6) debe ser **por objetivo**, no global
|
||||||
|
|
||||||
|
El handoff lo llama la mejor relación valor/esfuerzo y estoy de acuerdo. Pero el config actual de
|
||||||
|
`linux.toml` apaga `USB_SUPPORT`, `HID`, `HID_SUPPORT`, `INPUT_MOUSE`, `INPUT_TOUCHSCREEN`. Ese
|
||||||
|
kernel **no puede recibir teclado en metal** — y es correcto, porque es el kernel de QEMU con consola
|
||||||
|
serie. Un gate global «todo dispositivo en uso debe seguir teniendo driver» **rechazaría una receta
|
||||||
|
sana**.
|
||||||
|
|
||||||
|
⇒ El gate necesita el objetivo declarado (`qemu-serial` / `metal` / `servidor`) y una noción de
|
||||||
|
«dispositivos en uso **de la máquina destino**», que no siempre es la máquina donde se construye.
|
||||||
|
Sigue siendo la propuesta más barata de las nueve; sólo que el portón se pone en el sitio correcto.
|
||||||
|
|
||||||
|
## 6. Dos huecos del handoff que conviene cerrar antes
|
||||||
|
|
||||||
|
**«Booteó» no es una aserción suficiente para atestar (§2.2).** Un kernel puede arrancar y dejarte
|
||||||
|
sin red, sin wifi o sin teclado — que es justo el desastre que §6 quiere evitar. Si la atestación
|
||||||
|
dice sólo «booteó en esta huella», estamos firmando la clase de fallo que más duele. La atestación
|
||||||
|
debería llevar **qué se comprobó**: qué dispositivos de la huella quedaron con driver bindeado
|
||||||
|
después de arrancar. Es el mismo dato que ya calcula el gate de §6 ⇒ una implementación, dos usos.
|
||||||
|
|
||||||
|
**El catálogo de bundles necesita las tres etiquetas de nivel, no dos.** El handoff avisa en §5 que
|
||||||
|
«la mitad de N2 no es Kconfig sino variables de receta». Con §1 en la mano eso es más grave de lo que
|
||||||
|
parece: las perillas de receta (LTO, `-march`, `zig_version`) **cambian el hash igual que los
|
||||||
|
símbolos**, pero se aplican en otra fase y fallan de otra forma. La UI tiene que saber de qué lado
|
||||||
|
cae cada perilla o prometerá diffs que no puede explicar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Qué contesto al reparto (§7)
|
||||||
|
|
||||||
|
De acuerdo con el reparto tal como está. Dos precisiones desde este lado:
|
||||||
|
|
||||||
|
- El contrato JSON: de acuerdo, y el precedente que cita (`/run/hammer/boot-graph.json`) es el
|
||||||
|
correcto. Añadiría que el JSON de `hammer kernel plan` **debe incluir el `ArtifactHash` resultante**,
|
||||||
|
porque por §1 el plan *determina* el artefacto: la UI puede decir «esto ya está construido y
|
||||||
|
firmado» sin construir nada.
|
||||||
|
- Los verbos propuestos me parecen bien salvo `hammer kernel build --plan`, que esconde dos cosas muy
|
||||||
|
distintas (construir, y dar de alta en el árbol de generaciones). Separarlos deja que la sonda en
|
||||||
|
VM de §4 se meta entre medio como un paso propio y auditable.
|
||||||
|
|
||||||
|
## 8. Orden que propongo (modifica el §10 del handoff)
|
||||||
|
|
||||||
|
1. **Modo reversa (#9)** — tal cual lo propone: cero riesgo, valida el catálogo contra un kernel real.
|
||||||
|
2. **Validar la clausura contra el config actual** (§3 de este documento) — sale gratis, usa un corpus
|
||||||
|
que ya existe, y decide si §2.1 es viable. **Va antes que el gate**, porque si la clausura no
|
||||||
|
reproduce los bundles hechos a mano, el diseño de N1 cambia.
|
||||||
|
3. **Gate de no-regresión (#6), por objetivo** (§5 de este documento).
|
||||||
|
4. **Diff-back** (la mitad que falta de §6 del handoff): barato, y sin él la UI miente.
|
||||||
|
5. Recién después, #1 (clausuras) con la semántica ya decidida.
|
||||||
|
|
||||||
|
**No implementar todavía.** Este documento existe para que la decisión se tome con los seis datos
|
||||||
|
verificados, no con las suposiciones del handoff — que su autor, correctamente, marcó como tales.
|
||||||
Reference in New Issue
Block a user