SDD 25 §4 dejó el hallazgo escrito y sin guardián: los kernels de hammer se construyen sin CONFIG_MEMCG, `memory.max` no existe, y arje descarta el error al escribirlo ⇒ una Card pide un tope de memoria, corre SIN tope, y la única huella es un `warn!`. Nadie lo veía porque la máquina de desarrollo SÍ trae MEMCG: el fallo sólo existe del lado del artefacto sellado. `hammer kernel contract` declara qué pedazos de interfaz de kernel usa el userland POR NOMBRE (con consumidor, fichero y CÓMO FALLA HOY si no está) y los comprueba contra un `.config` YA PRODUCIDO — no contra la receta: entre el `scripts/config -e X` y el `.config` hay un `olddefconfig` que puede tragarse el símbolo en silencio. Por perfil, no global — misma lección que el gate de hardware: `linux.toml` es el kernel de QEMU del selfhost-verify, no hospeda Cards, y su hash es load-bearing del baseline `of_tree`. Exigirle contabilidad de memoria sería rechazar una receta sana. Medido sobre el store: **2 de 11 configs sellados cumplen su perfil**; los 9 `anfitrion-cards` fallan por MEMCG (apagado A MANO: `# CONFIG_MEMCG is not set`) y les falta PSI. El kernel vivo de esta máquina pasa las 11 exigidas — que es exactamente por qué el bug sobrevivió. Distingue apagado explícito de ausente (un símbolo que el .config ni nombra puede ser un renombrado entre versiones), y un kernel sin perfil declarado queda SIN COMPROBAR en vez de contar como aprobado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
212 lines
8.6 KiB
TOML
212 lines
8.6 KiB
TOML
# Contrato de capacidades del kernel (SDD 25 §4, §7-H1 y §8-W6).
|
|
#
|
|
# QUÉ ES. La lista de pedazos de interfaz de kernel que el userland de hammer/tawasuyu usa POR
|
|
# NOMBRE, con su consumidor y con **cómo falla hoy si no están**. Se comprueba con
|
|
# `hammer kernel contract` contra un `.config` YA PRODUCIDO (el que la receta instala en
|
|
# `/out/boot/config-<versión>`), nunca contra la receta: entre el `scripts/config -e X` y el
|
|
# `.config` hay un `olddefconfig` que puede tragarse el símbolo en silencio.
|
|
#
|
|
# POR QUÉ EXISTE. SDD 25 encontró que los kernels de hammer se construyen sin `CONFIG_MEMCG`, y
|
|
# que el escritor del otro lado descarta el error: una Card que pide un tope de memoria arranca
|
|
# SIN tope y la única huella es un `warn!`. La máquina de desarrollo no lo ve porque su kernel
|
|
# (Artix) sí trae MEMCG. Este fichero es el guardián de ese punto ciego.
|
|
#
|
|
# POR QUÉ PERFILES. Misma lección que el gate de hardware: `recipes/linux.toml` es el kernel de
|
|
# QEMU con consola serie del selfhost-verify y no hospeda Cards. Un contrato global rechazaría
|
|
# una receta sana.
|
|
#
|
|
# REGLA DE ALTA. Una capacidad entra acá sólo si hay un consumidor con ruta y símbolo. Lo que
|
|
# «estaría bueno tener» no es un contrato.
|
|
|
|
version = 1
|
|
reviewed_against = "7.1.2"
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Capacidades
|
|
# ---------------------------------------------------------------------------------------------
|
|
|
|
[[capability]]
|
|
id = "limite-memoria"
|
|
title = "Tope de memoria por unidad"
|
|
symbols = ["MEMCG"]
|
|
interface = ["memory.max", "memory.high", "memory.current"]
|
|
consumer = """
|
|
arje-incarnate::cgroup::{apply_rlimits_to_cgroup:78, set_memory_max_at:204, set_memory_high_at:210} \
|
|
← sandokan-core::Engine::{set_memory_max,set_memory_high} ← sandokan-monitor-llimphi (govierna memory.high)"""
|
|
silent = """
|
|
Sin MEMCG los ficheros NO EXISTEN. `apply_rlimits_to_cgroup` sólo emite `warn!(memory.max write \
|
|
failed)` y sigue: la Card corre sin tope y el resto del sistema se entera cuando el OOM killer \
|
|
elige a otro. Es el fallo de CLAUDE.md §3 — un ausente falla ruidosamente, un vacío llega hasta \
|
|
el final diciendo que todo fue bien."""
|
|
notes = "Arreglo: `-e MEMCG` en la fase configure. Re-hashea el kernel (SDD 22 §1) ⇒ va en tanda."
|
|
|
|
[[capability]]
|
|
id = "peso-cpu"
|
|
title = "Reparto proporcional de CPU por unidad"
|
|
symbols = ["CGROUPS", "CGROUP_SCHED", "FAIR_GROUP_SCHED"]
|
|
interface = ["cpu.weight", "cpu.stat"]
|
|
consumer = "arje-incarnate::cgroup::set_cpu_weight_at:170 (y :60, que descarta el error)"
|
|
silent = "`let _ = write(cpu.weight)` en :60: el peso no se aplica y no queda ni un warn."
|
|
|
|
[[capability]]
|
|
id = "tope-procesos"
|
|
title = "Tope de procesos por unidad"
|
|
symbols = ["CGROUPS", "CGROUP_PIDS"]
|
|
interface = ["pids.max", "pids.current"]
|
|
consumer = "arje-incarnate::cgroup::apply_rlimits_to_cgroup:85"
|
|
silent = "Mismo patrón que memory.max: sólo un `warn!` y la unidad forkea sin límite."
|
|
|
|
[[capability]]
|
|
id = "peso-io"
|
|
title = "Reparto de E/S por unidad"
|
|
symbols = ["BLK_CGROUP", "BLK_CGROUP_IOCOST"]
|
|
interface = ["io.weight", "io.cost.qos", "io.cost.model"]
|
|
consumer = "arje-incarnate::cgroup::set_io_weight_at:217 (y :64, que descarta el error)"
|
|
silent = "`let _ = write(io.weight)`: silencio total."
|
|
notes = """
|
|
`io.weight` con sintaxis «default N» lo publica iocost, no BFQ — por eso el contrato pide \
|
|
BLK_CGROUP_IOCOST y no IOSCHED_BFQ (que está apagado en las recetas y no hace falta)."""
|
|
|
|
[[capability]]
|
|
id = "cpus-por-unidad"
|
|
title = "Pinneo de CPUs por unidad"
|
|
symbols = ["CPUSETS"]
|
|
interface = ["cpuset.cpus", "cpuset.mems"]
|
|
consumer = "arje-incarnate::cgroup::set_cpuset_at:247"
|
|
|
|
[[capability]]
|
|
id = "jaula-de-rutas"
|
|
title = "Landlock: la jaula de rutas de harkaq"
|
|
symbols = ["SECURITY", "SECURITY_LANDLOCK"]
|
|
interface = ["landlock_create_ruleset(2)", "landlock_restrict_self(2)"]
|
|
consumer = "hammer-build::harkaq (SDD 16) y hammer-build::sandbox"
|
|
silent = """
|
|
Landlock ausente no rompe el build: la jaula simplemente no se aplica y el veredicto por fase \
|
|
sale igual de verde. Evidencia negativa que no prueba nada."""
|
|
|
|
[[capability]]
|
|
id = "jaula-de-syscalls"
|
|
title = "seccomp: el filtro de syscalls de harkaq"
|
|
symbols = ["SECCOMP", "SECCOMP_FILTER"]
|
|
interface = ["seccomp(2) SECCOMP_SET_MODE_FILTER"]
|
|
consumer = "hammer-build::harkaq (fase 1 cerrada, barrido en granja)"
|
|
|
|
[[capability]]
|
|
id = "namespaces-de-build"
|
|
title = "Namespaces para el sandbox de build"
|
|
symbols = ["NAMESPACES", "USER_NS", "PID_NS", "UTS_NS", "IPC_NS"]
|
|
interface = ["unshare(2)", "bwrap"]
|
|
consumer = "hammer-build::sandbox (bwrap sin pre_exec — ver SDD 25 §7-H4)"
|
|
silent = "Sin USER_NS bwrap falla ruidosamente; entra al contrato porque es la base de todo build."
|
|
|
|
[[capability]]
|
|
id = "hidratacion-overlay"
|
|
title = "Overlay con metacopy para hidratar árboles"
|
|
symbols = ["OVERLAY_FS", "OVERLAY_FS_REDIRECT_DIR", "OVERLAY_FS_INDEX", "OVERLAY_FS_XINO_AUTO", "OVERLAY_FS_METACOPY"]
|
|
interface = ["mount -t overlay"]
|
|
consumer = "hammer-overlay / `hammer hydrate` (docs/04-overlay.md)"
|
|
|
|
[[capability]]
|
|
id = "journal-fanotify"
|
|
title = "Journal de accesos por fanotify"
|
|
symbols = ["FANOTIFY", "FANOTIFY_ACCESS_PERMISSIONS"]
|
|
interface = ["fanotify_init(2) FAN_CLASS_CONTENT"]
|
|
consumer = "hammerd::watcher / hammer-journal (handoff arje→hammer, frente 2)"
|
|
|
|
[[capability]]
|
|
id = "io-uring"
|
|
title = "io_uring"
|
|
symbols = ["IO_URING"]
|
|
interface = ["io_uring_setup(2)"]
|
|
consumer = "kikin J4 (PLAN-KIKIN §4.bis) — el sustrato se declara explícito, no «al azar del defconfig»"
|
|
|
|
[[capability]]
|
|
id = "presion-de-recursos"
|
|
title = "PSI: presión de CPU/memoria/E/S"
|
|
symbols = ["PSI"]
|
|
interface = ["/proc/pressure/{cpu,memory,io}", "cpu.pressure", "memory.pressure"]
|
|
consumer = """
|
|
sandokan-monitor-llimphi — PREVISTO, no actual: hoy gobierna por muestreo. Se declara como \
|
|
`wants` para que el día que se use, el kernel ya lo traiga."""
|
|
silent = "Los ficheros no existen y el lector cae al muestreo sin decir que degradó."
|
|
|
|
[[capability]]
|
|
id = "contabilidad-por-tarea"
|
|
title = "taskstats + proc connector (el reemplazo de sondear /proc)"
|
|
symbols = ["TASKSTATS", "TASK_DELAY_ACCT", "CONNECTOR", "PROC_EVENTS"]
|
|
interface = ["netlink NETLINK_CONNECTOR", "TASKSTATS_CMD_GET"]
|
|
consumer = """
|
|
sandokan-local — PREVISTO (SDD 25 T3/W2): hoy sondea `/proc/<pid>` a 3,4 µs por proceso. \
|
|
Se declara `wants` porque el kernel ya lo trae por defconfig y perderlo sería una regresión silenciosa."""
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Perfiles: PARA QUÉ es cada kernel
|
|
# ---------------------------------------------------------------------------------------------
|
|
|
|
[[profile]]
|
|
id = "anfitrion-cards"
|
|
title = "Hospeda Cards de sandokan/arje con límites"
|
|
help = """
|
|
El kernel de un sistema hammer real. Acá vive el contrato entero: si una Card pide un tope, el \
|
|
tope tiene que existir."""
|
|
requires = [
|
|
"limite-memoria",
|
|
"peso-cpu",
|
|
"tope-procesos",
|
|
"peso-io",
|
|
"cpus-por-unidad",
|
|
"jaula-de-rutas",
|
|
"jaula-de-syscalls",
|
|
"namespaces-de-build",
|
|
"hidratacion-overlay",
|
|
"journal-fanotify",
|
|
"io-uring",
|
|
]
|
|
wants = ["presion-de-recursos", "contabilidad-por-tarea"]
|
|
|
|
[[profile]]
|
|
id = "qemu-serial"
|
|
title = "Kernel de QEMU con consola serie (selfhost-verify)"
|
|
help = """
|
|
`recipes/linux.toml`. No hospeda Cards: construye hammer dentro de una VM y su ArtifactHash es \
|
|
LOAD-BEARING del baseline `of_tree 9adefb82` del selfhost-verify. Por eso los límites por unidad \
|
|
son `wants` y no `requires`: subirlos acá cuesta rehacer el baseline, y el consumidor no existe."""
|
|
requires = [
|
|
"namespaces-de-build",
|
|
"hidratacion-overlay",
|
|
"jaula-de-rutas",
|
|
"jaula-de-syscalls",
|
|
"io-uring",
|
|
]
|
|
wants = ["limite-memoria", "peso-cpu", "tope-procesos", "journal-fanotify"]
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# Qué perfil le toca a cada artefacto. Un kernel sin fila acá NO se comprueba y se informa:
|
|
# un portón que calla lo que no pudo mirar no es un portón.
|
|
# ---------------------------------------------------------------------------------------------
|
|
|
|
[[target]]
|
|
artifact = "linux"
|
|
profile = "qemu-serial"
|
|
notes = "6.16.12, QEMU-tuned, INTOCADO (baseline del selfhost-verify)."
|
|
|
|
[[target]]
|
|
artifact = "linux-generic"
|
|
profile = "anfitrion-cards"
|
|
notes = "7.1.2, hardware variado de testers."
|
|
|
|
[[target]]
|
|
artifact = "linux-metal"
|
|
profile = "anfitrion-cards"
|
|
notes = "6.16.12, clavado al TigerLake/Iris-Xe/AX201."
|
|
|
|
[[target]]
|
|
artifact = "linux-metal-dual"
|
|
profile = "anfitrion-cards"
|
|
notes = "7.1.2, USB de escritorio dual."
|
|
|
|
[[target]]
|
|
artifact = "linux-gioser"
|
|
profile = "anfitrion-cards"
|
|
notes = "Derivado de `hammer kernel plan` para gioser (SDD 22, bzImage 11,1 MB)."
|