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…
fix(simi,perfiles): NINGÚN perfil proveía `/bin/sh` — funcionaba por la capa de debajo
1# simi — el `/bin/sh` del producto. Código takana-PROPIO, no una adopción.
2#
3# Mismo patrón de receta que netup/hammerd/arje-zero: construye un crate del workspace a un commit
4# fijado, deps vendoreadas para build hermético `--offline`, link estático con zig cc. Sólo depende
5# de libc ⇒ vendor mínimo.
6#
7# Por qué propio y no adoptado (ver `docs/plan-botar-busybox.md`): el único candidato Rust maduro es
8# brush, y se midió — 6,9 MB contra 1,17 del busybox entero, 4,1× la latencia de arranque, y dos
9# divergencias POSIX medidas (reubeno/brush#1394 y #1396, las dos reportadas por nosotros). simi son
10# 4448 líneas con CERO divergencias contra el ash de busybox en los 159 casos del banco diferencial.
11#
12# ══ MEDICIÓN VIGENTE — 2026-10-22, contra `b3:53f26620…` ══════════════════════════════════════
13# ⚠ Esta nota citaba `b3:46ff6fcf…` y «159 casos», y las dos cifras habían CADUCADO: la receta
14# avanzó (hash vigente `b3:327b31b72d8051e0d86693d16090fb393b774622d120c5c140ad8673986fcc2d`) y el
15# banco creció. Re-medido contra el artefacto vigente, traído del worker al hub:
16#
17# · ELF x86-64 **estático** de verdad: `readelf -d` ⇒ **0 NEEDED**
18# · **529 808 bytes** stripped. La progresión del día, para que se vea qué cuesta cada cosa:
19# 498 736 (base) → 508 992 (+10 KB, las cuatro expansiones no-POSIX) → 529 808 (+21 KB, el
20# editor de línea con historial y Tab). Sigue siendo **la mitad** del busybox entero (0,92 MB)
21# y una fracción de brush (6,9 MB).
22# · banco diferencial vs el ash de busybox: **1339 casos** —1329 de ellos **fases REALES de
23# recetas del corpus**, no escritas a mano— ⇒ **DIVERGE=0, avisos(stderr)=0**, 1,5 ms/caso
24# contra los 1,5 del ash. (bash: 8 divergencias; brush: 35)
25# · latencia de arranque medida EN EL HUB contra el artefacto sellado: **464 µs contra 449 del
26# ash — 1,03×**. (Una corrida anterior dio 1,18×: la cifra es ruidosa, el propio banco avisa de
27# que incluye el fork+exec de Python y sirve para COMPARAR, no como absoluto.) brush: 3,5×.
28#
29# ⚠ **Y desde el 2026-10-22 es TAMBIÉN un shell interactivo** (`src/linea.rs`, a pedido del
30# usuario): historial en memoria y en `~/.simi_historial`, Tab que completa órdenes (builtins +
31# PATH) o rutas según la posición, flechas, Inicio/Fin/Supr, los controles de estilo emacs, y `PS1`
32# con `\u \h \w \$`. Escrito con termios + ANSI y sólo `libc`, por lo mismo que el resto: cada dep
33# de este binario entra en la raíz de confianza del arranque.
34# · **13/13** pruebas con un PTY real, y la TERMINAL queda idéntica al salir (`lflag` byte a
35# byte) — el `Drop` del modo crudo es lo que evita dejar la consola muda tras un panic
36# · lo que NO cubre, dicho en vez de fingido: control de trabajos, `Ctrl-R`, y líneas más largas
37# que el ancho de la terminal se redibujan feas (el contenido es correcto)
38#
39# ⚠ **Y cuatro expansiones de BASH**, a pedido explícito del
40# usuario: `${x/pat/rep}` `${x//pat/rep}` (con anclajes `#`/`%`), `${x:desde:largo}` y
41# `${x^} ${x^^} ${x,} ${x,,}`. Son añadidos PUROS —POSIX deja esa sintaxis sin definir— y está
42# verificado con control, no supuesto: el banco POSIX da los **MISMOS 2 fallos** preexistentes
43# (`while-read`, `getopts`) con el binario viejo y con el nuevo, y el diferencial sigue en
44# DIVERGE=0. `${!x}` NO se añadió: choca con el `$!` del lexer y era la menos usada.
45#
46# (Antes de eso, `wasi-libcxx` usaba `${t/unknown-/}` y era la ÚNICA divergencia de las 1339. Se
47# pasó la receta a POSIX igual, por higiene: una receta no debería depender de una extensión para
48# algo que POSIX expresa.)
49#
50# ⚠ Todavía NO está en ningún perfil de `docs/state/targets.toml`, y es a propósito: declararla es
51# meterla en la raíz de confianza del arranque (frente C) y eso es su propia unidad de trabajo.
52
53name = "simi"
54version = "0.0.1"
55license = "MIT"
56
57[source]
58# El propio takana. El commit es el identificador inmutable; subirlo cuando simi avance.
59repo = "https://git.gioser.net/sergio/takana.git"
60commit = "4bc9be4d066f48c9eaa70736d1f18e01f43da87a"
61
62[build]
63compiler = "zig-cc"
64target = "x86_64-linux-musl"
65link = "static"
66
67# Split de la info de depuración, igual que hammerd. Entra en `hash_inputs`.
68strip_debug = true
69# Sólo el binario `simi` del workspace.
70flags = ["-p", "simi"]
71# ⚠ La receta no instalaba `/bin/sh`, y la consecuencia era GRANDE y silenciosa: medido el
72# 2026-10-23 sobre el cierre de IMAGEN de `cli` (154 nodos), NINGÚN paquete provee `sh`.
73#
74# bin/bash ← bash
75# usr/bin/awk ← akllay
76# usr/bin/doas ← doas
77# bin/login ← shadow
78# sbin/agetty ← util-linux
79# bin/sh ← NADIE
80#
81# O sea que en una imagen hidratada todo script con `#!/bin/sh` falla, y el login de consola
82# —que es `agetty -l /bin/sh`— no tiene qué ejecutar. El rootfs de ARRANQUE sí lo tiene (el
83# bootstrap crea `bin/sh → /usr/bin/simi` desde que se quitó busybox), pero la imagen de un
84# perfil la arma `hydrate-profile.py`, que proyecta SÓLO el cierre del perfil y no parte del
85# rootfs de arranque. Dos fuentes de verdad que discrepaban, que es exactamente el modo de fallo
86# que la cabecera de ese script describe.
87#
88# El enlace lo pone el PROVEEDOR, no el hidratador: quien da la capacidad da su nombre canónico.
89# Es lo mismo que hace `os-release` con `/usr/lib/os-release`.
90[build.phases]
91install = '''
92set -eu
93mkdir -p /out/usr/bin /out/bin
94# Replica lo que la fase derivada de Cargo hace —copiar los ejecutables de `target/*/release`—
95# porque declarar `install` la REEMPLAZA, no la amplía. Se prueban las dos rutas (nativa del
96# sandbox y por triple) en vez de hornear una: cuál aplica depende de `build.target`.
97for rel in target/release target/*/release; do
98 [ -d "$rel" ] || continue
99 find "$rel" -maxdepth 1 -type f -perm -100 -exec cp {} /out/usr/bin/ \;
100done
101ln -sfn /usr/bin/simi /out/bin/sh
102
103# ── guardián ─────────────────────────────────────────────────────────────────────────────────
104[ -x /out/usr/bin/simi ] || { echo "simi: no quedó el ejecutable — NO sello" >&2; exit 1; }
105# `-L` y no `-e`: el enlace es ABSOLUTO y dentro del artefacto apunta fuera, así que `-e` lo
106# sigue hasta un sitio que no existe y dice AUSENTE sobre un enlace correcto. Ya me mordió.
107[ -L /out/bin/sh ] || { echo "simi: falta /bin/sh — la imagen quedaría sin shell; NO sello" >&2; exit 1; }
108[ "$(readlink /out/bin/sh)" = /usr/bin/simi ] || {
109 echo "simi: /bin/sh apunta a $(readlink /out/bin/sh), no a /usr/bin/simi — NO sello" >&2; exit 1; }
110
111# Y que CORRA, con las tres capacidades que el commit pineado tiene que traer. Un binario que
112# sella y no corre ya pasó en este corpus más de una vez.
113/out/usr/bin/simi -c 'alias x=echo; x alias-ok' | grep -q alias-ok || {
114 echo "simi: el binario no trae alias — el commit pineado es viejo; NO sello" >&2; exit 1; }
115/out/usr/bin/simi -c 'set -- -a; while getopts a o; do echo "ok=$o"; done' | grep -q 'ok=a' || {
116 echo "simi: el binario no trae getopts — el commit pineado es viejo; NO sello" >&2; exit 1; }
117/out/usr/bin/simi -c 'v="|a"; [ "${v#|}" = a ]' || {
118 echo "simi: el operador # con patron | sigue roto — el commit pineado es viejo; NO sello" >&2; exit 1; }
119echo "simi: $(stat -c%s /out/usr/bin/simi) B · /bin/sh -> $(readlink /out/bin/sh) · alias+getopts OK"
120'''
121
122
123# Sin `[build.phases]`: igual que hammerd y netup, el BuildSys::Cargo del lab instala el binario del
124# crate. **El symlink `sh` NO lo pone esta receta**, y eso es a propósito: quién gana como `/bin/sh`
125# lo decide el ensamblado del producto (`USERLAND_COMPONENTS` en `crates/takana-bootstrap/src/lib.rs`,
126# que hidrata el userland Rust DESPUÉS de busybox para que ensombrezca sus applets, y `lib.rs:676`,
127# que retira el homónimo de los otros directorios). Ese cableado es frente C y es su propia unidad de
128# trabajo: mete a simi en la raíz de confianza del arranque, y eso no se hace de paso.