crun: un contenedor ARRANCÓ de verdad — la hoja de la última familia vacía
`crun` 1.29.1, y la prueba no es `--version`: con el `busybox` del propio corpus como rootfs y un
bundle OCI mínimo, rootless,
$ crun run prueba-takana
HOLA-DESDE-EL-CONTENEDOR
Linux
0
93
Entra como HOJA a propósito de la familia «contenedores», la última que la tabla de `planear.py`
daba vacía. Un runtime OCI es lo que cualquiera de los tres candidatos (docker, podman, containerd)
acaba ejecutando por debajo, así que esto **no presupone cuál se elige arriba** — esa decisión sigue
abierta y no la toma una receta.
crun y no runc: C en vez de Go, ~300 KB contra ~10 MB, y es el runtime por defecto de Alpine ⇒ musl
es objetivo probado. No son excluyentes.
Tres deps que NO se deducen del proyecto, y las tres abortaban el `configure`:
· `--disable-systemd` no es preferencia: `AC_CHECK_HEADERS([systemd/sd-bus.h], [], [AC_MSG_ERROR…])`
lo hace OBLIGATORIO salvo que se apague. Esta distro no lleva systemd ⇒ el cgroup manager será
`cgroupfs`. Coherente con el resto del sistema, y mejor decidido acá que descubierto después.
· **`argp-standalone`**: crun parsea flags con `argp_parse(3)`, extensión de glibc que musl no tiene.
La receta YA estaba en el corpus y sellada — sólo faltaba que alguien la pidiera.
· **`python3`** aunque crun sea C puro: su `AM_PATH_PYTHON` no está marcado opcional. Misma figura
que meson.
**Y el vigía de enlace estático se ganó el sueldo.** El primer sellado pasó todo —corría, 0 deuda—
pero `static-audit.sh` cantó: `✗ crun dice static, es DINÁMICO → libc.so`. crun enlaza con libtool,
que lee el `-static` del lab como «preferí los `.a` de libtool» y no como flag al linker. El arreglo
es `LDFLAGS="-all-static -no-pie"` en compile **y en install** — libtool RELINKEA al instalar, así
que sólo en compile deja bueno el árbol de build y dinámico lo que se sella. Ahora: 0 NEEDED, y
`+CAP +SECCOMP +EBPF +JSON_C` intactos.
libcap y libseccomp se declaran y NO se apagan, que es la decisión contraria a los `--disable-*` de
arriba y es deliberada: son las dos piezas con las que un runtime OCI acota de verdad al contenedor.
Un crun sin ellas compila igual, aísla mucho menos, y el binario no lo diría.
**Promoción**: `libseccomp` pasa de `recipes/incoming-gnome/` al corpus, porque ya tiene dos
consumidores independientes (el stack de GNOME y crun) y no hay variante homónima con la que colisionar.
Control en los dos sentidos antes de moverla: su hash y los de `gnome-shell`/`mutter` son idénticos
antes y después, y coinciden con los que el grafo ya tenía registrados ⇒ **cero re-hasheo**. La
resolución sibling→padre de `resolve_dep_path` hace que los consumidores de la cola la sigan viendo.
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
5 archivos · +165 −41
- docs/state/build-state.json +61 −14
- docs/state/repro-verificado.tsv +1 −0
- recipes/crun.toml +76 −0
- recipes/incoming-gnome/libseccomp.toml +0 −27
- recipes/libseccomp.toml +27 −0
diff --git a/docs/state/build-state.json b/docs/state/build-state.json
index 09ee7fd0..1904e7ab 100644
--- a/docs/state/build-state.json
+++ b/docs/state/build-state.json
@@ -1,14 +1,14 @@
{
"schema": "hammer-build-state/1",
"totals": {
- "recipes": 886,
- "nodes": 888,
- "sealed": 886,
+ "recipes": 888,
+ "nodes": 890,
+ "sealed": 888,
"ajeno": 2
},
"by_class": {
"ajeno": 2,
- "c": 242,
+ "c": 244,
"go": 363,
"gui": 41,
"kernel": 6,
@@ -17,7 +17,7 @@
"debt_by_class": {},
"by_queue": {
"corpus": {
- "sealed": 886,
+ "sealed": 888,
"ajeno": 2
}
},
@@ -43,7 +43,7 @@
"sealed": 101
}
},
- "sin_perfil": 648,
+ "sin_perfil": 649,
"orphan_deps": [],
"topo_ok": true,
"nodes": {
@@ -514,7 +514,7 @@
"blocked_by": [],
"unblocks": 0,
"perfiles": [],
- "dependientes_total": 2
+ "dependientes_total": 3
},
"arje-absorb": {
"iname": "arje-absorb",
@@ -1026,7 +1026,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 56
+ "dependientes_total": 57
},
"biome": {
"iname": "biome",
@@ -1303,7 +1303,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 16
+ "dependientes_total": 17
},
"bwrap": {
"iname": "bwrap",
@@ -2937,6 +2937,32 @@
"perfiles": [],
"dependientes_total": 0
},
+ "crun": {
+ "iname": "crun",
+ "version": "1.29.1",
+ "link": "static",
+ "compiler": "zig-cc",
+ "deps": [
+ "argp-standalone",
+ "binutils",
+ "busybox",
+ "json-c",
+ "libcap",
+ "libseccomp",
+ "make",
+ "pkgconf",
+ "python3"
+ ],
+ "cls": "c",
+ "queue": "corpus",
+ "foreign": false,
+ "hash": "b3:ecf33acabf992acf1f204e7e942699abb179ff50e912e02c993e309cc1146a0f",
+ "state": "sealed",
+ "blocked_by": [],
+ "unblocks": 0,
+ "perfiles": [],
+ "dependientes_total": 0
+ },
"csview": {
"iname": "csview",
"version": "1.3.4",
@@ -8098,7 +8124,7 @@
"perfiles": [
"escritorio-sway"
],
- "dependientes_total": 2
+ "dependientes_total": 3
},
"json-glib": {
"iname": "json-glib",
@@ -9366,7 +9392,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 4
+ "dependientes_total": 5
},
"libdisplay-info": {
"iname": "libdisplay-info",
@@ -9961,6 +9987,27 @@
],
"dependientes_total": 1
},
+ "libseccomp": {
+ "iname": "libseccomp",
+ "version": "2.6.1",
+ "link": "static",
+ "compiler": "zig-cc",
+ "deps": [
+ "gperf",
+ "make"
+ ],
+ "cls": "c",
+ "queue": "corpus",
+ "foreign": false,
+ "hash": "b3:1910d00c4c59f3a9d339ff0aac6c5f31dacdc156719777f24b1a2e94eec6a9b5",
+ "state": "sealed",
+ "blocked_by": [],
+ "unblocks": 0,
+ "perfiles": [
+ "escritorio-gnome"
+ ],
+ "dependientes_total": 6
+ },
"libsndfile": {
"iname": "libsndfile",
"version": "1.2.2",
@@ -10825,7 +10872,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 132
+ "dependientes_total": 133
},
"mako": {
"iname": "mako",
@@ -13214,7 +13261,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 412
+ "dependientes_total": 413
},
"plow": {
"iname": "plow",
@@ -13736,7 +13783,7 @@
"escritorio-sway",
"servidor"
],
- "dependientes_total": 320
+ "dependientes_total": 321
},
"qrcp": {
"iname": "qrcp",
diff --git a/docs/state/repro-verificado.tsv b/docs/state/repro-verificado.tsv
index 3c004169..047c2cb9 100644
--- a/docs/state/repro-verificado.tsv
+++ b/docs/state/repro-verificado.tsv
@@ -80,3 +80,4 @@ abseil-cpp a0d2ad6f771ecb64471a7e25db365cfcfb785574d75d4161ee2423beb7f35b5f 2026
protobuf c01f39d4a566e48f09d077ac20a5c6249c6a36fe3c53196c1c6e5e4ce0bc5991 2026-09-11 reproduce
kubectl ca95c5292688c838715727f9b909a0722d9fc98a778edefb2a4864e01678056e 2026-09-11 reproduce
opensmtpd 51b185ef7e032a4ee10a6ac31799a7baf2b9ea0b95a1fd8f7fcc5239fd287a5b 2026-09-11 reproduce
+crun ecf33acabf992acf1f204e7e942699abb179ff50e912e02c993e309cc1146a0f 2026-09-11 reproduce
diff --git a/recipes/crun.toml b/recipes/crun.toml
new file mode 100644
index 00000000..5ff3bbf8
--- /dev/null
+++ b/recipes/crun.toml
@@ -0,0 +1,76 @@
+# crun 1.29.1 — el runtime OCI. Es la HOJA de la única familia que la tabla de
+# `scripts/mudanza/planear.py` sigue dando vacía: «contenedores». Y entra como hoja a propósito: un
+# runtime OCI es lo que **cualquiera** de los tres candidatos de esa familia acaba ejecutando por
+# debajo, así que construirlo no presupone cuál se elige arriba (podman, containerd o nada). La
+# decisión de qué gestor lleva la distro sigue abierta y no la toma esta receta.
+#
+# **Por qué crun y no runc**, que es lo mismo pero en Go: crun está escrito en C, pesa ~300 KB contra
+# los ~10 MB de runc, y arranca contenedores en menos memoria — que importa en una caja de 7 GiB. Es
+# además el runtime por defecto de Alpine y de Fedora, o sea que musl es un objetivo probado y no una
+# aventura. runc sigue siendo una opción válida si alguien la necesita; no son excluyentes.
+#
+# ⚠ **`--disable-systemd` NO ES UNA PREFERENCIA: SIN ÉL EL `configure` ABORTA.** Su `configure.ac`
+# hace `AC_CHECK_HEADERS([systemd/sd-bus.h], [], [AC_MSG_ERROR(*** Missing libsystemd headers)])` —
+# o sea que systemd es obligatorio salvo que se apague explícitamente. Esta distro no lleva systemd
+# (el init es arje-zero), así que el cgroup manager de crun va a ser el suyo propio (`cgroupfs`),
+# no el de systemd. Es coherente con el resto del sistema, no una limitación que se descubra después.
+#
+# **Todo lo demás está pineado por la razón de siempre**: `configure` PRUEBA el entorno, y el lab no
+# entra en `hash_inputs` ⇒ lo que encuentre instalado cambiaría el binario sin mover el hash.
+#
+# --enable-embedded-blake3 el default es «autodetect»: si el lab tuviera `libblake3` la usaría, y
+# si no, cae a la copia embebida. Dos labs ⇒ dos binarios distintos en
+# la MISMA dirección del store. Se fija en `yes` (embebida), que además
+# es lo único posible: `libblake3` no está en el corpus.
+# --disable-criu checkpoint/restore necesita CRIU, que tampoco está. El `configure` lo
+# probaría con pkg-config y seguiría en silencio si no lo encuentra.
+#
+# `libcap` y `libseccomp` SÍ entran como deps y NO se apagan, que es la decisión contraria y
+# deliberada: son las dos piezas con las que un runtime OCI acota de verdad al contenedor. Un crun
+# con `--disable-caps --disable-seccomp` compila igual y aísla mucho menos — y el binario no lo
+# diría. Las dos están selladas en el corpus.
+#
+# **`argp-standalone` es la otra dep que no se deduce del proyecto**: crun parsea sus flags con
+# `argp_parse(3)`, que es una **extensión de glibc** y musl NO tiene. Sin ella `configure` muere con
+# `*** argp functions not found`. El corpus ya traía la receta (`recipes/argp-standalone.toml`,
+# sellada) — sólo faltaba que alguien la pidiera.
+#
+# **`python3` es dep de BUILD aunque crun sea C puro**, y el fallo lo dice sin ayudar:
+# `configure: error: no suitable Python interpreter found`. Su `configure.ac` llama a `AM_PATH_PYTHON`
+# sin marcarlo opcional, así que aborta aunque no se pidan las bindings de Python. Es la misma figura
+# que meson ([[receta-meson-exige-python3]]): una dep invisible que no se deduce del lenguaje del
+# proyecto.
+#
+# `json-c` es la librería de JSON con la que crun lee el `config.json` del bundle OCI. crun ≤1.2x
+# usaba yajl; 1.29 ya no (`grep -c yajl configure` → 0). Vale decirlo porque las recetas de otras
+# distros todavía arrastran yajl y uno la busca sin necesidad.
+name = "crun"
+version = "1.29.1"
+license = "GPL-2.0-or-later"
+
+[source]
+tarball = "https://github.com/containers/crun/releases/download/1.29.1/crun-1.29.1.tar.gz"
+sha256 = "b6be9fd9613efe5df414c568ddfaf09857c72ec17a00999f15c132a3d9e120fd"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+configure = './configure --build=$CBUILD --host=$CHOST --prefix=/usr --disable-systemd --disable-criu --enable-embedded-blake3 --disable-shared --enable-static'
+# ⚠ **`-all-static` EN LAS DOS FASES, Y NO ES REDUNDANCIA.** crun enlaza con **libtool**, que lee el
+# `-static` que el lab exporta por `link = "static"` como «preferí los `.a` de libtool» y NO como
+# flag al linker ⇒ el binario sale DINÁMICO y la receta jura que es estático. Lo cazó
+# `scripts/static-audit.sh`, que existe justo para esta mentira:
+#
+# ✗ crun dice static, es DINÁMICO (usr/bin/crun) → libc.so
+#
+# Va también en `install` porque **libtool RELINKEA al instalar**: ponerlo sólo en `compile` deja un
+# binario correcto en el árbol de build y uno dinámico en `/out`, que es el que se sella.
+compile = 'make -j"$(nproc)" LDFLAGS="-all-static -no-pie"'
+install = 'make install DESTDIR=/out LDFLAGS="-all-static -no-pie" && find /out -name "*.la" -delete'
+
+[deps]
+build = ["binutils", "busybox", "make", "pkgconf", "python3", "json-c", "libcap", "libseccomp", "argp-standalone"]
diff --git a/recipes/incoming-gnome/libseccomp.toml b/recipes/libseccomp.toml
similarity index 100%
rename from recipes/incoming-gnome/libseccomp.toml
rename to recipes/libseccomp.toml