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…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

util-linux entra al núcleo: el stage1 pasa de 4 a 5 y la consola la abre agetty

Decisión del usuario. Con util-linux en STAGE1_COMPONENTS, la card `console-getty`
—que vive en la seed del NÚCLEO— ya puede apuntar a un binario que existe en ESE
rootfs, que era exactamente lo que faltaba: util-linux estaba en los 7 perfiles de
targets.toml y eso no lo ponía en el rootfs, porque el producto se ensambla de
estas listas y no del catálogo.

Verificado en los artefactos, no en el código:

  stage1-rootfs b3:b9eb52a3
    /sbin/agetty                      1.292.208 bytes
    card console-getty → /sbin/agetty  argv=[agetty, -n, -l, /bin/sh, 115200, console]

  product-attested-rootfs b3:f3e1ce16
    /sbin/agetty presente · card console-getty → /sbin/agetty
    takana attest → ✓ 7 binarios críticos casan su BLAKE3, agetty entre ellos

`sbin/agetty` entra también en ATTEST_PATHS, y la regla que eso obedece queda
escrita: lo que un card EJECUTA tiene que estar atestado. Cambiar un `exec` sin
tocar esa lista deja el binario de arranque fuera de la raíz de confianza — un
hueco que no rompe nada y que ninguna prueba ve, porque el producto bootea igual.

El argv no cambia y eso no es casualidad: agetty documenta las dos formas de orden
—`<line> [<baud>]` y `<baud>,... <line>`— y acepta -n (--skip-login) y -l
(--login-program) con la misma semántica que el applet. Comprobado ejecutando el
binario sellado, no leyendo su manual.

⚠ LO QUE ESTO CUESTA, y va escrito en la constante para que no se pierda:
  · el of_tree del stage1 cambia ⇒ el baseline del selfhost-verify
    (9adefb82/7fa6cb4e) es de cuando el núcleo eran CUATRO y hay que re-verificar;
  · el núcleo que la VM reconstruye bit a bit pasa a incluir un paquete C grande;
  · la raíz de confianza crece ~119 binarios, de los que el producto usa UNO. Si
    algún día molesta, lo que hace falta es agetty, no util-linux entero.

⚠ Y falta la prueba que de verdad decide: arrancar. Un getty no se valida con
pruebas unitarias —las 42 del crate pasaban también con la card rota apuntando a
un binario inexistente—; la prueba es product-boot-test.sh en QEMU, que esta
jaula no tiene.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

97738117b6e4fbb90f6be9b4d77996c42739abdf · ruwaq Sergio · (29 p'unchaw ñawpaq) · tayta 0ecabf428 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 5

crates/takana-bootstrap/src/lib.rs

1 qillqakuna · +46 −13

diff --git a/crates/takana-bootstrap/src/lib.rs b/crates/takana-bootstrap/src/lib.rs
index d2f65fd5..5e48cf76 100644
--- a/crates/takana-bootstrap/src/lib.rs
+++ b/crates/takana-bootstrap/src/lib.rs
@@ -166,10 +166,26 @@ pub fn seed_build_config(
 pub type RootfsHash = ArtifactHash;
 
 /// Los componentes del userland mínimo, en orden de ensamblado: `musl` (libc), `busybox`
-/// (coreutils + sh), `hammerd` (daemon, receta Cargo) y `arje-zero` (el init PID 1, ADR 0007).
-/// arje-zero es PID 1 vía el symlink `/sbin/init`; supervisa a `hammerd` y a la getty por la
-/// seed card ([SDD 12](../../docs/12-init-real.md)) — su muerte es el `CRASHED` real.
-const STAGE1_COMPONENTS: &[&str] = &["musl", "busybox", "hammerd", "arje-zero"];
+/// (coreutils + sh), `util-linux` (la consola), `hammerd` (daemon, receta Cargo) y `arje-zero`
+/// (el init PID 1, ADR 0007). arje-zero es PID 1 vía el symlink `/sbin/init`; supervisa a `hammerd`
+/// y a la getty por la seed card ([SDD 12](../../docs/12-init-real.md)) — su muerte es el `CRASHED`
+/// real.
+///
+/// ⚠ **`util-linux` entró el 2026-09-22 y el núcleo pasó de 4 a 5** (decisión del usuario, frente
+/// de botar busybox). Entra por UNA razón concreta y conviene que se lea antes de añadir nada más
+/// aquí: **la card `console-getty` de abajo vive en esta seed, o sea en el núcleo, y su `exec` tiene
+/// que existir en ESTE rootfs.** Mientras el núcleo tuvo cuatro componentes, el único getty posible
+/// era el applet de busybox: `util-linux` está en los 7 perfiles de `targets.toml` y eso **no lo
+/// pone en el rootfs** — el producto se ensambla de estas listas, no del catálogo
+/// ([[catalogo-no-es-imagen]]). Apuntar la card a `/sbin/agetty` sin esto deja el arranque MUDO,
+/// con `supervision: Restart` reintentando un binario inexistente para siempre.
+///
+/// ⚠ **Lo que esto cuesta, dicho entero:** el `of_tree` del stage1 cambia, así que el baseline del
+/// selfhost-verify (`9adefb82/7fa6cb4e`) deja de valer y hay que re-verificar; el núcleo que se
+/// reconstruye bit a bit dentro de la VM pasa a incluir un paquete C grande; y la raíz de confianza
+/// crece en ~119 binarios, de los cuales el producto usa uno. Ese último punto es el que hay que
+/// revisar si algún día molesta: lo que hace falta es `agetty`, no util-linux entero.
+const STAGE1_COMPONENTS: &[&str] = &["musl", "busybox", "util-linux", "hammerd", "arje-zero"];
 
 /// La **seed card** que arje-zero lee en `/ente/seed.card.json` al boot ([SDD 12 §4](../../docs/12-init-real.md)):
 /// un `Card` `Virtual` cuyos `genesis` son `hammerd` (servicio `Native` con `Restart` — el
@@ -177,6 +193,14 @@ const STAGE1_COMPONENTS: &[&str] = &["musl", "busybox", "hammerd", "arje-zero"];
 /// (`seeds/arje-qemu.card.json`) para que `Card::validate()` la acepte; los ULID son fijos para que
 /// el `RootfsHash` sea reproducible. Template autocontenido (opción A del SDD): takana no depende de
 /// `card-core` como librería; el pin a arje-zero + el boot en VM cubren el drift de schema.
+/// ⚠ **La consola la abre `agetty` de util-linux, no el applet `getty`** (2026-09-22). El argv es
+/// EL MISMO y eso no es casualidad: agetty documenta las dos formas —`agetty [opts] <line>
+/// [<baud>]` y `agetty [opts] <baud>,... <line>`— y acepta `-n` (--skip-login) y `-l`
+/// (--login-program) con la misma semántica que el applet. Verificado ejecutando el binario
+/// sellado, no leyendo su manual.
+///
+/// ⚠ Este literal es **JSON**: los comentarios van ACÁ, no dentro. Meter `//` en la cadena la deja
+/// inválida y caen siete pruebas de golpe, ninguna con un mensaje que hable de comentarios.
 const STAGE1_SEED_CARD: &str = r#"{
     "schema_version": 1,
     "id": "01HQAR53D4M2NBV8KZTYXFQA00",
@@ -237,8 +261,8 @@ const STAGE1_SEED_CARD: &str = r#"{
                 "cpu_affinity": null
             },
             "payload": { "Native": {
-                "exec": "/bin/busybox",
-                "argv": ["getty", "-n", "-l", "/bin/sh", "115200", "console"],
+                "exec": "/sbin/agetty",
+                "argv": ["agetty", "-n", "-l", "/bin/sh", "115200", "console"],
                 "envp": []
             }},
             "supervision": { "Restart": { "initial": 200, "max": 10000 } },
@@ -389,20 +413,22 @@ fn assemble_rootfs(
 // ── Capa de servicios: el PRODUCTO (Separación Mecanismo/Política) ──────────────────────────────
 //
 // `STAGE1_COMPONENTS` + `STAGE1_SEED_CARD` son el **mecanismo base**: el núcleo atómico que el
-// selfhost-verify reconstruye bit a bit (`of_tree` 9adefb82/7fa6cb4e). Inmutable y blindado. Los
+// selfhost-verify reconstruye bit a bit. ⚠ El baseline `of_tree` 9adefb82/7fa6cb4e es de cuando
+// el núcleo eran CUATRO componentes: `util-linux` entró el 2026-09-22 y hay que re-verificar. Los
 // **servicios** (sshd, …) son **política de producto**: NO entran al núcleo (no contaminan el
 // `of_tree` ni obligan a un rebuild pesado de openssl/zlib dentro de la VM). Se inyectan por
 // **hidratación tardía** sobre el rootfs base ya sellado, produciendo un `product-rootfs` APARTE.
 // Así, meter más servicios mañana no toca jamás el núcleo verificado.
 
-/// Componentes de servicio inyectados sobre el 4/4 verificado: `netup` (red) + `openssh` (sshd/ssh).
+/// Componentes de servicio inyectados sobre el 5/5 verificado (era 4/4 hasta que
+/// `util-linux` entró al núcleo el 2026-09-22): `netup` (red) + `openssh` (sshd/ssh).
 /// Daemons ⇒ llevan card en la seed de producto. Crecer esta lista NO toca `STAGE1_COMPONENTS`.
 const SERVICE_COMPONENTS: &[&str] = &["netup", "openssh"];
 
 /// Userland Rust-nativo ADOPTADO (Etapa C): multicall `uutils`/coreutils + `find`/`xargs` + `diff`/`cmp`
 /// + `rg`. Se hidrata sobre la base DESPUÉS de busybox, así que sus symlinks en `/usr/bin` ENSOMBRECEN
 /// los applets de busybox (con `PATH` `/usr/bin:/bin` gana la tool Rust). Es el "adelgazar busybox" del
-/// producto SIN tocar el núcleo: busybox sigue en el 4/4 (self-host) y queda de fallback para lo aún no
+/// producto SIN tocar el núcleo: busybox sigue en el núcleo (self-host) y queda de fallback para lo aún no
 /// reemplazado (`sh`/ash, `tar`, …). NO son daemons ⇒ no llevan card en la seed (sólo ficheros).
 const USERLAND_COMPONENTS: &[&str] =
     &["uutils", "findutils", "findutils-xargs", "diffutils", "ripgrep"];
@@ -462,7 +488,8 @@ const SSHD_CONFIG: &str = "Port 22\nListenAddress 0.0.0.0\nPermitRootLogin prohi
 const ATTEST_PATHS: &[&str] = &[
     "usr/bin/arje-zero", // PID 1 — la raíz
     "usr/bin/hammerd",   // diario + bus
-    "bin/busybox",       // shell/getty + exec de varios cards
+    "bin/busybox",       // shell (`/bin/sh`) + exec del card de sshd
+    "sbin/agetty",       // la consola: lo ejecuta el card `console-getty`
     "usr/sbin/sshd",     // daemon SSH (superficie de red)
     "usr/bin/netup",     // configurador de red
     "usr/bin/coreutils", // userland Rust multicall
@@ -2117,7 +2144,11 @@ mod tests {
         assert_eq!(hammerd["payload"]["Native"]["exec"], "/usr/bin/hammerd");
         assert!(hammerd["supervision"]["Restart"]["initial"].is_number(), "hammerd es Restart");
         let getty = &v["genesis"][1];
-        assert_eq!(getty["payload"]["Native"]["exec"], "/bin/busybox");
+        assert_eq!(getty["payload"]["Native"]["exec"], "/sbin/agetty");
+        // El argv es el mismo que tenía con el applet: agetty acepta `<baud> <line>` y las dos
+        // opciones. Si alguien lo cambia, que sea a sabiendas.
+        assert_eq!(getty["payload"]["Native"]["argv"][0], "agetty");
+        assert_eq!(getty["payload"]["Native"]["argv"][3], "/bin/sh");
     }
 
     // --- Capa de servicios (producto): composición de seed + hidratación tardía ---
@@ -2460,8 +2491,10 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
         let by_label = |l: &str| bins.iter().find(|(x, _)| x == l).map(|(_, p)| p.clone()).unwrap();
         assert_eq!(by_label("arje-zero"), staging.join("usr/bin/arje-zero"));
         assert_eq!(by_label("hammerd"), staging.join("usr/bin/hammerd"));
-        // console-getty y sshd ejecutan ambos /bin/busybox ⇒ misma ruta, labels distintos.
-        assert_eq!(by_label("console-getty"), staging.join("bin/busybox"));
+        // ⚠ Desde 2026-09-22 la consola la abre `agetty` y sshd sigue en busybox: dos rutas
+        // distintas donde antes había una. Este test existe para eso — que el derivado siga al
+        // `exec` de cada card y no a una lista escrita a mano.
+        assert_eq!(by_label("console-getty"), staging.join("sbin/agetty"));
         assert_eq!(by_label("sshd"), staging.join("bin/busybox"));
     }