Commit Graph
2172 Commits
Author SHA1 Message Date
Sergio 1647310894 estado: cosecha granja 2026-09-22T04:01:44Z — avance del árbol KDE 2026-09-22 04:01:45 +00:00
Sergio 2c882bdd65 merge: trabajo concurrente del otro frente 2026-09-22 03:48:36 +00:00
SergioandClaude Opus 5 ae6f0b45d9 qillqa: el awk propio — 3531 líneas, sólo libc, y CERO divergencias contra dos oráculos
«awk soberano» (usuario, 2026-09-22), que era la tercera opción del ADR pendiente
y la única que no adoptaba la semántica de otro: gawk es C con extensiones GNU,
goawk es Go y rompe la premisa de cadena de la Etapa C, frawk es otro dialecto.

Banco diferencial contra DOS oráculos a la vez —el awk de busybox 1.36.1 (el
applet que hoy viaja) y gawk 5.3.2 --posix— con 109 casos: campos y separadores,
el modelo de valores, aritmética, expresiones regulares, las funciones de cadena,
printf, control de flujo, arrays, funciones de usuario, E/S y los rincones del
parser. Resultado: 104 iguales a los dos, 0 divergencias, y 5 casos donde los
ORÁCULOS discrepan entre sí — que es justo para lo que sirve tener dos.

De esos 5, cuatro los tenemos del lado correcto por POSIX: `2^3^2` es 512 (la
potencia asocia a la derecha; busybox da 64), `index(s,"")` es 1, el `%*d` de
printf existe, y `length(array)` funciona. El quinto era nuestro y se arregló:
una coincidencia VACÍA pegada al final de la anterior no sustituye, así que
`gsub(/a*/,"-")` sobre "xax" da 3 y `-x-x-`, no 4 y `-x--x-`.

El motor de expresiones regulares es propio y esa es la razón de fondo para no
adoptar un crate: POSIX es leftmost-LONGEST y la familia Perl —el crate `regex`
en su modo por defecto— es leftmost-first. Para /a|ab/ sobre "ab", POSIX devuelve
`ab` y Perl devuelve `a`. En un awk eso decide qué borra un sub(), dónde parte un
split() y cuánto valen RSTART y RLENGTH. Construcción de Thompson con conjunto de
hilos: sin retroceso, así que `(a*)*b` contra sesenta `a` no explota.

Las cinco cosas que el banco destapó, todas dando resultados plausibles y ninguna
reventando —quedan como pruebas de regresión en tests/divergencias_medidas.rs:

  · leer NF no forzaba el partido en campos ⇒ `{print NF}` daba 0 hasta que
    alguien tocara un campo. El partido es perezoso a propósito (es lo que hace
    barato asignar a $0) y el precio es acordarse de forzarlo.
  · `length` sin paréntesis se leía como una VARIABLE llamada length, que vale
    vacío. Es el único builtin al que POSIX permite omitirlos.
  · los arrays se pasan por referencia, y eso se decide mirando si la función usa
    el parámetro como array — `a["k"]=1` tiene el nombre en el LUGAR de la
    asignación, y sólo se miraba el valor.
  · substr con inicio ≤ 0 recorta a 1 SIN ajustar el largo, y los no enteros se
    truncan. La lectura estricta de POSIX daría otra cosa; ni busybox ni gawk la
    hacen, y lo que hay que ejecutar son los guiones escritos contra ellos.
  · la regla del vacío en gsub, arriba.

Verificado además contra este repo: de los 61 programas awk únicos que usan sus
guiones y recetas, qillqa acepta los 59 reales (los 2 restantes son f-strings de
Python que mi extractor tomó por awk). 29 pruebas propias en verde.

El nombre es quechua («escritura») y sigue el precedente de simi: lo registra el
comentario de cabecera y lo DECIDE un ADR. El comando instalado será `awk` — eso
es contrato y lo llaman los configure de medio corpus.

⚠ Lo medido es el build NATIVO del hub. Falta la receta, el estático musl con zig
cc, y la prueba que de verdad decide: un `configure` de autotools con qillqa como
/usr/bin/awk. Es la lección de brush — pasó 56/57 casos POSIX y no construía una
sola receta.

Va también recipes/ncurses-tools.toml (clear, reset, tput, tset, infocmp, tic),
declarada en `base`: la canónica instala sólo lib y cabeceras, así que clear y
reset en una imagen eran el applet de busybox. Instala los binarios y NO la base
terminfo: resuelven con los --with-fallbacks compilados en la librería, que
cubren la consola del metal, QEMU y cualquier ssh moderno.

⚠ Y ncurses-tools selló DINÁMICO dos veces antes de salir bien: ncurses usa
`-static`/`-dynamic` como MARCADORES de tramo alrededor de las librerías —su
forma portable de -Wl,-Bstatic … -Wl,-Bdynamic—, no como decisión global. El
`-dynamic` llega al final de la línea de enlace y gana por orden. Se quita
sobreescribiendo LIBS_TIC y LIBS_TINFO en la línea de make. Tercer sabor del
agujero que documenta scripts/static-audit.sh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 03:48:25 +00:00
Sergio 0ff269a7d6 estado: cosecha granja 2026-09-22T03:31:36Z — avance del árbol KDE 2026-09-22 03:31:36 +00:00
Sergio 03ea9ef615 estado: cosecha granja 2026-09-22T03:01:45Z — avance del árbol KDE 2026-09-22 03:01:45 +00:00
SergioandClaude Opus 5 c8fcd04945 paso 2 verificado: se hidrata y se mira, no se declara
El criterio de terminado del paso 2 decía «y la imagen los lleva», y eso no se
comprueba declarando. Hidratación real con el orden del ensamblado (busybox
primero, el userland encima, como USERLAND_COMPONENTS): ningún binario de los
declarados lo sigue ganando busybox — grep, sed, find, xargs, diff, cmp, gzip,
zcat, xz, bc, dc, vi, cpio, killall, pstree, fuser y los de coreutils salen todos
del paquete que corresponde.

Y la prueba que faltaba: hidratar la xz canónica ENCIMA de xz-tools —el escenario
que motivó quitarle lib/ e include/ a la variante— deja liblzma.a de la canónica,
usr/bin/xz de la variante, y xz --version sigue contestando. Conviven, que es lo
que la receta afirmaba y ahora está medido en vez de razonado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 02:56:43 +00:00
Sergio e5485d6505 merge: trabajo concurrente del otro frente 2026-09-22 02:54:07 +00:00
SergioandClaude Opus 5 6b33d1a435 el frente kmod no existe: los kernels de takana no tienen módulos
Fui a escribir el kmod en Rust que el plan pide —la pieza más grande que
quedaba— y lo primero fue buscar contra qué módulos probarlo. No hay ninguno:

  · 0 ficheros .ko en los artefactos sellados de los cuatro kernels (sólo
    boot/bzImage y el .config)
  · 0 recetas del corpus hacen modules_install
  · 0 drivers `=m` en los .config sellados (contra 1809 `=y` en linux-metal)
  · `# CONFIG_MODULES is not set` en los tres kernels que están en el store
  · nadie en el repo enciende CONFIG_MODULES

Sin CONFIG_MODULES el kernel ni siquiera implementa init_module/delete_module: un
insmod escrito en Rust recibiría ENOSYS. Los 6 applets de kmod no son escritura,
son 6 applets a retirar. Y con ellos otros 3: DEVTMPFS_MOUNT=y hace que el kernel
cree y monte /dev, no hay UEVENT_HELPER, y libudev-zero ya viaja en los 8
perfiles ⇒ mdev, makedevs y uevent tampoco tienen consumidor.

De los 23 applets que el plan llamaba «el trabajo real» queda: un ADR (awk), una
migración de card (getty → agetty, que ya está en los 7 perfiles), y decidir
nslookup (ningún candidato publica ese nombre; su único llamador es
scripts/drive-netup.py y migrarlo sale más barato que traer BIND).

Y la corrección que me toca: hace dos horas escribí en la salida del vigía, como
advertencia, que «el kernel llama a /sbin/modprobe por call_usermodehelper ⇒ esos
applets salen libres y no lo son». Es plausible, es la forma correcta de razonar,
y es falsa para este producto. La escribí sin medirla, en el mismo commit donde
presumía de haber arreglado dos falsos negativos. Corregida en el script.

NO se recorta ahora: editar RETIRADOS re-hashea busybox y las 24 recetas que lo
declaran build-dep, y 15 de esas 24 siguen en deuda por el recorte del 21/09. El
siguiente recorte junta lo ya decidido y se hace una vez: 6 de kmod + 3 de /dev +
6 alias internos de iproute2 (ipaddr iplink ipneigh iproute iprule iptunnel, que
no son comandos de nadie) = 15 applets, 277 → 262, sin escribir una línea.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 02:53:58 +00:00
Sergio 2c723c7493 estado: cosecha granja 2026-09-22T02:31:54Z — avance del árbol KDE 2026-09-22 02:31:54 +00:00
Sergio 55160d3f06 estado: cosecha granja 2026-09-22T02:02:12Z — avance del árbol KDE 2026-09-22 02:02:12 +00:00
Sergio 6af8547e94 estado: cosecha granja 2026-09-22T01:31:38Z — avance del árbol KDE 2026-09-22 01:31:38 +00:00
Sergio 0f051c51f2 estado: cosecha granja 2026-09-22T01:01:23Z — avance del árbol KDE 2026-09-22 01:01:23 +00:00
Sergio a2159048f9 estado: cosecha granja 2026-09-22T00:31:28Z — avance del árbol KDE 2026-09-22 00:31:28 +00:00
Sergio 720b72099c estado: cosecha granja 2026-09-22T00:01:47Z — avance del árbol KDE 2026-09-22 00:01:47 +00:00
Sergio 66db5484e9 estado: cosecha granja 2026-09-21T23:31:35Z — avance del árbol KDE 2026-09-21 23:31:35 +00:00
Sergio e08ee8ba0e estado: cosecha granja 2026-09-21T23:01:45Z — avance del árbol KDE 2026-09-21 23:01:45 +00:00
Sergio 9941ab3cea estado: cosecha granja 2026-09-21T22:31:22Z — avance del árbol KDE 2026-09-21 22:31:22 +00:00
Sergio a88d9e5b77 estado: cosecha granja 2026-09-21T22:01:37Z — avance del árbol KDE 2026-09-21 22:01:37 +00:00
SergioandClaude Opus 5 8a109400b5 vigía --uso: de los 64 applets que sólo da busybox, 12 tienen llamador
Es lo que el plan dejó sin medir tres veces («no se midió qué applets usa el
producto de verdad; eso abarataría el paso 1»). El modo lee del TSV los applets
con proveedor_medido = SOLO-BUSYBOX y los busca en el repo con las reglas
anti-ruido que el vigía ya tenía.

12 invocados: sh (28 sitios, lo cierra simi), ifconfig/route/udhcpc/netstat
(todos con ip/ss como primera opción y el applet como respaldo), setuidgid (3
cards: gitea, pacha-secretos, tejido), getty (la card de la consola), adduser,
nc, nslookup, poweroff. Y uno que es falso positivo del grep y conviene tener
escrito: los `insmod` de iso-image.sh/install-image.sh son de GRUB —`insmod
part_gpt` dentro de un grub.cfg—, no del busybox.

Los otros 52 no los llama nadie EN EL REPO, y la salida lo dice sin dejar que se
lea como una lista de borrado: un grep ve a quien invoca por nombre, y hay dos
consumidores que no aparecen nunca — el KERNEL (llama a /sbin/modprobe por
call_usermodehelper y al gestor de /dev por hotplug, así que modprobe/lsmod/
depmod/rmmod/modinfo/mdev/uevent/makedevs salen «libres» y no lo son) y el
USUARIO de la máquina, que espera clear/reset/vi/traceroute al entrar por ssh.

La primera versión del modo decía 10 y estaba mal en dos casos, los dos del lado
peligroso — dan permiso para retirar algo que se usa:

  · setuidgid se invoca como `exec /usr/bin/setuidgid pacha`, con RUTA ABSOLUTA,
    y el patrón de comando pide el nombre pegado al exec.
  · getty vive como `"argv": ["getty", …]`, elemento de un array JSON, y la regla
    que filtra cadenas por «parece una línea de comandos» —la que bajó el ruido
    de 91 hits a 0— descarta un nombre suelto.

Las reglas que quitan ruido son las que crean ceguera; lo que decide es hacia
dónde se equivoca cada una. Ahora busca tres formas: nombre desnudo, ruta
absoluta y primer elemento de argv. El vigía sigue verde en su modo original
(123 applets retirados, nadie los invoca) y se salta su propio fichero, que
nombra applets en la documentación y se denunciaba a sí mismo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 21:53:06 +00:00
SergioandClaude Opus 5 4357b29cb7 busybox 69 → 64: psmisc y bc, las dos piezas que el plan marcaba TRAER
psmisc 23.7 (killall pstree fuser prtstat pslog) y gavinhoward/bc 7.0.3 (bc Y dc
del mismo árbol). Ninguna estaba en el corpus, y lo de psmisc es lo que el plan
daba por cubierto sin estarlo: procps-ng NO trae killall ni pstree ni fuser —
procps-ng es ps/top/free/kill/pgrep/pkill/pidof/uptime/vmstat/w/watch.

bc es el de gavinhoward y no el de GNU por la cadena: C99 sin dependencias, bc y
dc en un árbol, y es el bc de sistema de Alpine y FreeBSD. El de GNU arrastra ed
y flex, y su dc va en otro paquete.

⚠ killall5 NO lo trae psmisc: es de sysvinit. Acá lo cubre arje-zero por FUNCIÓN
(es PID 1 y apaga él) ⇒ se retira con los indultos, no se reemplaza. Son 3 de los
4 que la tabla prometía, dicho así para que no vuelva a prometer lo que no hay.

Dos pisones de psmisc, los dos escritos en la receta para no repetirlos:

1. `-lncurses` no existe acá (la nuestra es widec ⇒ libncursesw.a). No sirve
   LIBS=-lncursesw —el AC_CHECK_LIB compila un programa de prueba CON -lncurses,
   no pregunta si el símbolo resuelve— ni la variable de caché con
   `make TERMCAP_LIB=…`, porque src_pstree_LDADD = @TERMCAP_LIB@ se sustituye en
   tiempo de configure y el Makefile queda con -lncurses LITERAL. Lo que funciona
   es un enlace libncurses.a → libncursesw.a en el scratch y un -L.

2. Y ese -L se comió el -static. El harness inyecta LDFLAGS=-static para las
   recetas link="static" (takana-build/src/lib.rs:285); reemplazar LDFLAGS lo
   pierde. Los seis binarios sellaron DINÁMICOS sin que nada fallara: killall
   respondía bien y sólo pstree moría en ejecución con `Error relocating
   /lib/libncursesw.so.6`, tomando la librería del anfitrión. Un `file` lo dice en
   un segundo; ninguna prueba del build lo iba a decir.

Los dos van a `base` en targets.toml, y el censo baja de 69 a 64.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 21:49:57 +00:00
Sergio 5fd8746445 atuq §7.terdecies: «no hay identidad» era también «no pude preguntar» — y en pacha-cli eso guardaba los dotfiles EN CLARO
Contesta el segundo pendiente del §7.duodecies: `pacha`/`pacha-secretos` en
`perfil.servidor`. La respuesta a la pregunta de origen es **NO**, y es la
CONTRARIA a la del escritorio.

Premisa medida, no supuesta: la Card de `pacha-secretos` es `scope=system` y
entra al `genesis`, o sea que arje arranca el daemon EN EL ARRANQUE. De ahí se
sigue que un sembrador en la imagen no llega a tiempo por construcción — en el
arranque no hay nadie logueado; `abrir_almacen()` decide una vez en `main()` y
no vuelve a mirar; y `agora-cli unlock` de una sesión ssh siembra en el llavero
de ESA sesión. Declararlo dejaría la función PARECIENDO cerrada, que es peor
que el hueco. Queda escrito al lado de las dos raíces, en `targets.toml`, para
que nadie copie la decisión del escritorio.

Y al ir a medirlo, el control POSITIVO salió rojo — con la seed en `/proc/keys`
el daemon decía que no había identidad — y eso destapó el defecto de verdad:
los CINCO lectores de la seed hacían `.ok().flatten()` (o un `_ =>`), que
convierte «el llavero no se pudo consultar» en «no hay identidad desbloqueada».
La causa del rojo la mide la sonda de syscalls: en el LXC `add_key` funciona y
`keyctl(KEYCTL_SEARCH)` da `ENOSYS` ⇒ se puede sembrar y no cosechar.

El peor de los cinco no era un mensaje feo: `pacha-cli` guardaba los dotfiles
SIN CIFRAR, en silencio, con la identidad del usuario desbloqueada.

Arreglado en tawasuyu (`4f2b5eac7`) con `pacha_llavero::SeedDeSesion` —tres
estados, tres textos— y `Reason` en `net.tawasuyu.Secretos1`. Pin de las dos
recetas `23a292863` → `cd9acd0d9` ⇒ `b3:e8038352` (4,6 M) y `b3:a9fb8c17`
(8,5 M), mirados por dentro y probados como artefacto: el daemon sellado ahora
dice «motivo=el llavero de sesión NO se pudo consultar (… os error 38) — esto
no es «no hay identidad»».

Tres cosas más que quedaron medidas por el camino:
- la guarda pegada al build **se disparó sola por primera vez**: el latido
  revirtió `pacha.toml` entre las dos construcciones de la misma tanda;
- el muro del `Cargo.lock`, quinta vez, y la arista que faltaba era la mía de
  esa mañana: se la llevó un «merge de git en «main» (import)». Bisecado con
  `git log -S`;
- el índice Y el árbol compartidos de tawasuyu tenían una versión de
  `pacha-boveda-llimphi` ANTERIOR al arreglo de `7917fbb96`. Lo delató el test
  de regresión, no el diff.
2026-09-21 21:41:43 +00:00
Sergio c020c2af19 merge: trabajo concurrente del otro frente 2026-09-21 21:34:58 +00:00
SergioandClaude Opus 5 466972e301 busybox: 79 → 69 applets sin otro dueño, y ninguno de los diez costó escribir userland
Cuatro artefactos sellados y el paso 2 del plan cerrado. Los diez applets que
dejan de depender de busybox salieron de arreglar recetas, no de escribir código:

  xz unxz xzcat lzma unlzma lzcat  → recipes/xz-tools.toml (nueva, b3:194c7d69…)
  vi                               → un symlink en recipes/vim.toml
  cpio                             → un symlink en recipes/libarchive.toml
  arch hostname                    → los trajo el feature de uutils (b3:614ff653…)

xz-tools es el TERCER caso de la familia de zstd-cli, y eso ya estaba escrito en
targets.toml: «la receta canónica construye sólo lib/ y su artefacto no tiene
usr/bin; declararla no habría arreglado nada». Igual que musl-shared, zlib-shared
y libffi-shared. Cuatro veces la misma forma — la receta publica la lib, la
imagen declara el paquete, y el binario no está. Va como variante porque ampliar
la canónica re-hashearía a sus 14 consumidores.

⚠ xz-tools selló DINÁMICO en la primera corrida pese al link = "static", y
funcionaba: comprimía y descomprimía sin una queja. Es el relink de libtool, que
libarchive.toml y bluez.toml ya documentan — hace falta LDFLAGS=-all-static en
compile Y en install. Lo delató el `file` del binario, no una prueba que fallara.

Paso 2 del plan: uutils, findutils, findutils-xargs, diffutils, gzip, grep y
xz-tools declarados en el perfil `base` — seis recetas que llevaban meses
`sealed` con `perfiles: []`. Hasta hoy, `find`, `xargs`, `diff`, `cmp`, `gzip`,
`zcat` y `grep` en una imagen de takana eran el applet de busybox, no porque
faltara escribirlos sino porque nadie los declaró. `grep` entra como PUENTE
declarado: ripgrep ya viaja pero publica `rg` y no es grep POSIX.

El censo gana modo --guardian, y vigila PÉRDIDAS, no un umbral: un umbral hay
que subirlo cada vez que se retira un applet y a la tercera nadie lo sube con
criterio; que un applet con proveedor medido deje de tenerlo es siempre una
regresión. Probado en los dos sentidos — rc=0 contra /store, rc=1 contra un store
mutilado, nombrando applet y proveedor perdido.

Los cuatro sellaron con el mismo hash en el store tirable y en /store: dos
work_root distintos, bytes idénticos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 21:34:46 +00:00
Sergio 077e5acc21 estado: cosecha granja 2026-09-21T21:32:11Z — avance del árbol KDE 2026-09-21 21:32:11 +00:00
Sergio e0e390c282 repo list/verify: hablan HTTP — un repo remoto con 173 paquetes se leía como vacío
`repo list --repo <url>` tomaba un `PathBuf`, así que trataba la URL como una ruta
relativa que no existe y respondía **«repo vacío» con salida 0**. No es que no
soportara el caso: es que afirmaba lo contrario de lo que pasaba, y con rc=0, que
es la forma de fallo que un script no puede detectar. Salió al probar el repo del
dominio nuevo.

`install --repo` ya sabía hablar HTTP con `RepoSource` (ADR 0014: lista de
orígenes separada por comas, probados en orden). `list` y `verify` ahora usan el
MISMO resolvedor, así que la misma cadena de espejos vale en los tres verbos.
`sign` se queda local a propósito: firmar es escribir.

Y `verify` remoto no es un extra — es el caso de uso del ADR 0014. Verificar el
release de un espejo ANTES de instalarle nada era imposible sin construir.

De paso, tres estados que se daban por iguales y ahora se distinguen (CLAUDE.md
§3: un ausente falla ruidosamente, un vacío llega hasta el final diciendo que todo
fue bien):

  dir que NO EXISTE        → error, rc=1, y sugiere que quizá era una URL
  dir sin index.json       → «repo por crear» (estado válido: lo crea `pack --repo`)
  índice con 0 paquetes    → «índice publicado y SIN paquetes»
  origen que no sirve      → error del fetch, con los orígenes probados

Y el índice se atribuye al origen que REALMENTE lo sirvió, no a la lista entera:
con `<espejo-caído>,<bueno>` la salida dice el bueno. Es la misma regla que el
propio `fetch_desde_algun_origen` aplica a los `.swm` —«decir bajado de la lista
entera cuando sólo uno respondió es una media verdad»— que `read_index` tiraba.

Medido contra el repo vivo: `list` 173 paquetes [release firmado por release],
`verify` trusted. 93+93+6+1+3+4 tests del crate en verde.
2026-09-21 21:28:49 +00:00
Sergio 3573fb0057 merge: cosecha de la granja 21:02Z 2026-09-21 21:02:47 +00:00
SergioandClaude Opus 5 64005efcbd busybox: los 23 applets sin dueño eran 79 — el reparto era juicio, y medido no da
El plan de botar busybox decía de sí mismo que el reparto applet→dueño era
«juicio, no medida». Medido contra los 1407 artefactos del store: de los 277
applets vivos, 167 los provee el dueño que el TSV nombra, 31 los provee OTRO
paquete, y 79 no los provee NADIE salvo busybox — 56 más de los 23 previstos.

El hallazgo transversal es que una receta de LIBRERÍA no es un paquete de
HERRAMIENTAS, y build-state no distingue: `xz` figura en SEIS perfiles con el
`usr/bin` VACÍO (su receta pasa --disable-xz --disable-xzdec --disable-scripts,
o sea liblzma y nada más), y `ncurses` instala sólo install.libs/install.includes
⇒ ni clear, ni reset, ni tput. Las dos viajan, las dos dicen `sealed`, y el único
binario que hay adentro de la imagen es el applet de busybox.

Y al revés: `switch_root` y `sulogin` los trae util-linux, `partprobe` lo trae
parted, y `getty` tiene sustituto — `agetty` de util-linux, en los 7 perfiles ⇒
deja de ser ESCRIBIR y pasa a ser migrar el exec de la card. Seis de los 18
huecos de iproute2 (ipaddr iplink ipneigh iproute iprule iptunnel) no son
comandos de nadie: son nombres internos de busybox y se borran del defconfig.

uutils traía 78 applets, no los 90 que el plan le atribuía: el multicall
contestaba `coreutils: unknown program 'chmod'`. Construía con las default
features (feat_common_core) y el resto vive detrás de feat_os_unix_musl, que
cubre exactamente los 25 que faltaban. Construido y verificado contra un store
tirable: 79 → 106 applets, 27 nuevos y CERO perdidos, chmod/uname/id ejecutan, y
`stdbuf` correctamente ausente (por eso la variante _musl y no feat_os_unix:
upstream lo excluye en musl porque necesita un cdylib).

Importaba más de lo que parece: USERLAND_COMPONENTS hidrata uutils DESPUÉS de
busybox para ensombrecerlo, y lo que no existe no ensombrece nada — chmod, chown,
id, uname y stat los seguía dando busybox con el userland Rust instalado encima.

⚠ Dicho en la receta y en el documento: who/users/uptime/pinky compilan contra
los stubs de utmpx de musl y contestan vacío. No es regresión, tampoco es
sustitución.

El censo queda como script (scripts/busybox-censo-proveedores.py) y como dos
columnas del TSV, no como una tabla escrita a mano que vuelva a envejecer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 21:02:07 +00:00
Sergio 8f0391dbc0 estado: cosecha granja 2026-09-21T21:02:05Z — avance del árbol KDE 2026-09-21 21:02:05 +00:00
Sergio bf6037c832 ⚠ un overlay abierto se traga TODA la máquina: el /etc/shells y el chsh no estaban donde parecía
Mientras se escribían el /etc/shells y el `chsh -s /bin/zsh sergio` había un overlay del FHS
abierto —de un `takana install` sin --prefix sin commitear— y las DOS escrituras cayeron dentro,
aunque no tenían nada que ver con ese paquete ni pasaron por takana (un `cat >` y el chsh de
shadow). Medido con `mount --bind /` no recursivo: en el /etc REAL no había shells y sergio seguía
en /bin/bash, mientras la pantalla mostraba las dos cosas hechas. Un discard o un reinicio lo
borraba y nadie se habría enterado hasta el siguiente login.

Resuelto con commit (5 ficheros promocionados: passwd, passwd-, shells, zsh, zsh-5.9). El
desmontaje perezoso del §5.10 se ganó el sueldo ahí mismo: saltó en /bin y en /usr/bin.

La lección, que es más grande que el caso: `install` sin --prefix NO es un experimento acotado a
ese paquete — cambia la semántica de escritura de la máquina entera hasta que alguien commitea o
descarta, y el overlay no avisa por ningún lado. Como mínimo `takana status` debería salir al
entrar, y el `overlay listo` del install no debería leerse como «terminé». Anotado en SDD 28 §5.11.
2026-09-21 20:43:22 +00:00
Sergio b6e20829e4 chsh: faltaba /etc/shells — y debajo, que la distro no puede expresar un binario setuid
Pedido tras instalar zsh: «no me funciona chsh». Dos capas, y sólo la primera es un arreglo.

1. /etc/shells NO EXISTÍA en la caja, y shadow rechaza cualquier shell que no esté listado (para un
   usuario normal es rechazo duro, no aviso). Escrito con lo que existe, comprobado -x uno por uno,
   en vez de copiar la lista de otra distro: un shell listado que no está deja a quien lo elija sin
   poder entrar, y eso no se ve hasta el siguiente login. Con eso `chsh -s /bin/zsh sergio` va, y
   `su - sergio` entra con zsh 5.9.

2. ⚠ Pero sergio sigue sin poder cambiárselo él mismo: `su sergio -c "chsh …"` ⇒ «Cannot change ID
   to root». Los cinco binarios de shadow que necesitan setuid (chsh, passwd, su, chfn, newgrp)
   salen del store r-xr-xr-x, porque la receta lleva --disable-account-tools-setuid heredado del
   APKBUILD de Alpine (allá los hace setuid el empaquetado, no el configure). Y debajo hay otra
   capa: of_tree sólo hashea el bit de EJECUCIÓN (mode & 0o111), así que el modelo de artefactos
   hoy no puede ni expresar ni garantizar un setuid — aunque la receta lo pusiera, el hash no lo
   cubriría y nadie notaría si se pierde o aparece.

Quitar la bandera no alcanza y no es un arreglo de receta: es una decisión de seguridad. Anotada
en la propia receta y en SDD 28 §5.11, sin decidir.

El comentario va en la cabecera de la receta y NO mueve su hash: comprobado antes y después,
b3:843e9098… las dos veces (los comentarios fuera de [build.phases] no entran en hash_inputs).
2026-09-21 20:37:41 +00:00
Sergio d154ce02a2 simi: REPRODUCE bit a bit — y dos trampas operativas que costaron dos corridas
scripts/verificar-repro.sh simi en el worker: «✓ simi REPRODUCE», 0
no-determinismos. Queda anotado en docs/state/repro-verificado.tsv con su hash,
que es la clave del libro: si la receta o alguna dep cambia, la entrada deja de
casar y hay que volver a verificar.

Las dos trampas, anotadas en el SDD porque las dos fallan en silencio:

1. --store <tirable> NO sirve para verificar reproducibilidad. Un store vacío da
   «Error: io: No such file or directory» a secas —sin decir qué falta— porque las
   deps del build no están ahí. El método correcto ya estaba escrito: apartar el
   artefacto dentro del MISMO store y reconstruir. Dos corridas perdidas por
   inventar un instrumento en vez de leer el que existía.

2. La cosecha BORRA una receta sembrada a mano: siembra hub→worker con
   rsync --delete, y el hub del que siembra es gioser, no el clon donde se
   trabaja. El worker perdió recipes/simi.toml entre el build y la verificación
   (el artefacto sellado sobrevivió, la receta no) y el verificador informó
   «REPRODUCEN: 0 · sin artefacto: 0» — o sea nada, porque su primer [ -f ] falla
   y salta en silencio.

Comprobado de paso: editar los comentarios de la receta NO cambia el hash
(hash_inputs es una lista explícita de campos). Y el artefacto vive sólo en el
store del worker: sella con PROMOTE=0 y la cosecha baja el manifiesto, no el
store. Promoverlo es otro paso.
2026-09-21 20:35:53 +00:00
Sergio c2586b131c estado: cosecha granja 2026-09-21T20:31:34Z — avance del árbol KDE 2026-09-21 20:31:34 +00:00
Sergio 4621381050 simi: SELLADA en el worker — estática de verdad, y la latencia del artefacto es 1,04x el ash
b3:46ff6fcf67bd23dece4d130c92855908520200f88ca85981317f81fcc56bb834

Construida con flock -o en dev.gioser.net (el LXC prestado, coste 0). El
artefacto trae dos ficheros: usr/bin/simi y su .hammer/recipe.toml.

Medido sobre el ARTEFACTO, no sobre el build del hub:
- ELF x86-64 estático, stripped, 498.736 bytes
- scripts/static-audit.sh simi: «estáticos de verdad: 1 · MIENTEN: 0», o sea que
  el link=static no es una declaración sino un hecho (ese guardián existe porque
  libtool lo ignora en silencio en otras recetas)
- banco diferencial, 159 casos contra el ash de busybox: 0 divergencias
  (bash: 8, brush: 35)
- latencia 468 us contra 449 us del ash: 1,04x. brush: 1569 us, 3,5x

Y la lección que vale más que el número: el build NATIVO del hub (glibc,
dinámico) daba 865 us, 1,4x el ash. El estático musl baja a 1,04x. Medir el
binario que VIAJA, no el que se compila a mano — la diferencia fue del 35 % y
habría quedado escrita como si fuera del shell.

Sigue sin estar en ningún perfil de targets.toml a propósito: declararla es
meterla en ATTEST_PATHS y eso es frente C, su propia unidad de trabajo.
2026-09-21 20:30:49 +00:00
Sergio 136594828c simi: la receta pineada y el veredicto en el SDD — sin construir todavía, y dicho así
recipes/simi.toml sigue el patrón de hammerd/netup: crate del workspace a un
commit fijado, zig cc, musl estático, strip_debug. SIN [build.phases]: el
BuildSys::Cargo instala el binario, y el symlink `sh` NO lo pone la receta a
propósito — quién gana como /bin/sh lo decide USERLAND_COMPONENTS en
takana-bootstrap, que es frente C y su propia unidad de trabajo.

El SDD queda con el veredicto (simi 0 divergencias, bash 8, brush 35), el alcance
por consumidor, los siete bugs del camino y las seis cosas que faltan. Con dos
avisos explícitos: lo medido es el build NATIVO del hub (glibc, dinámico) y el
estático musl no se construyó, así que simi no entra en ningún perfil de
targets.toml; y la shell interactiva de getty no está cubierta.
2026-09-21 20:24:24 +00:00
Sergio 26c153c149 overlay: el commit no podía desmontar /bin en una máquina viva — y el código prometía el perezoso sin hacerlo
La primera instalación real de un paquete en la caja (`takana install zsh`, sin --prefix) dejó el
sistema A MEDIAS: `commit` desmontó 5 de 7 targets y murió con `target is busy` en /bin, con dos
overlays montados y el estado sin promocionar. La causa no es un fd abierto: los procesos tienen su
EJECUTABLE mapeado desde /bin y /usr/bin —empezando por PID 1, /usr/bin/arje-zero— y un ejecutable
mapeado pin-ea el montaje. En un FHS vivo eso no se puede evitar esperando.

El comentario de `do_umount_lenient` prometía el desmontaje perezoso «desde la Fase 2» y el código
NUNCA pasaba `-l`. Ahora, ante `busy`, reintenta perezoso: desengancha el montaje ya mismo (las
rutas vuelven a resolver al directorio real, que es lo que el promote necesita) y libera cuando el
último proceso que lo miraba se muere; los vivos ven el mismo contenido porque el commit copia el
upper al lower justo después.

Probado en la caja con un paquete nuevo: install tree ⇒ overlay; commit ⇒ WARN del perezoso sobre
/usr/bin y «commit OK — 1 archivo(s) promocionados»; 0 overlays, /usr/bin/tree real, tree v2.3.2.

El SDD 28 §5.10 anota las otras cuatro cosas que ese `sudo takana install zsh` destapó: el repo por
defecto que no existía, el lab que no se encontraba desde /root, el binario del sistema incapaz de
leer los .tkn nuevos, y —la peor— que la instalación queda PARTIDA porque el overlay cubre siete
directorios y /usr/share no es uno: 1296 de los 1331 ficheros fueron directos al FHS real mientras
2 binarios esperaban el commit, y el mensaje decía `apply OK` igual.
2026-09-21 20:20:54 +00:00
Sergio a63af7b531 el aviso, enchufado en la caja — y «al día» con cero instalados era una mentira cómoda
Cron horario de root → /usr/local/bin/avisar-actualizaciones, un envoltorio que sólo fija las rutas
de la caja y llama al guion del repo. La lógica no se copia: comprobado en vivo que arreglar el
guion en el clon cambia la salida del cron sin volver a desplegar nada.

⚠ La primera corrida real destapó un defecto del aviso: con la DB de instalados vacía decía «al
día». Es verdad y no dice nada — peor, se lee como «comprobado y correcto» cuando lo cierto es que
no había NADA que comprobar. Un aviso que suena igual cuando vigila que cuando no vigila nada es
cómo se deja de mirar un aviso. Ahora distingue el caso y lo nombra.

Control en la caja con una DB de juguete (un zsh a otro hash): grita «1 con novedad · hash
distinto» y en la segunda corrida calla.

Y dos piezas que faltaban allá: /var/lib/hammer/trust/release.ed25519.pub (el almacén de confianza
por defecto de la CLI, que no existía ⇒ ahora install/outdated verifican sin pasar --trust) y
/usr/local/bin/takana con el arreglo del §5.5. Ojo: el PATH de la caja no incluye /usr/local/bin,
así que un `takana` a secas sigue siendo el del store; el cron usa rutas absolutas.
2026-09-21 20:03:34 +00:00
Sergio 07867ba23e estado: cosecha granja 2026-09-21T20:01:39Z — avance del árbol KDE 2026-09-21 20:01:39 +00:00
Sergio a4105cba11 scripts: la superficie medida — 0 guiones POSIX roto bajo ash, y los 28 bash lo son por ARRAYS
Pregunta distinta a la de las cards, y el doc lo dice: scripts/ corre en el hub
bajo el bash del anfitrión, así que su superficie sólo obliga el día del
auto-alojamiento. Lo que importa hoy es si algún guion se declara POSIX y no lo
es, porque ése se rompe en la imagen, donde /bin/sh es el ash de busybox.

- 81 POSIX (10.167 líneas), 76 bash (10.269), 21 EMPOTRADOS por heredoc (los
  /init que viajan a la imagen, y que hoy nadie parsea), 6 que se hacen source
- los 81 POSIX y los 21 empotrados los parsea el ash: CERO roto
- 48 de los 76 bash parsean tal cual bajo ash; de los 28 que no, 26 lo son por
  arrays y nada más. Ése es el presupuesto entero de un sh propio para correr la
  herramienta del proyecto
- 27 builtins, 257 órdenes por nombre desnudo y 35 por ruta absoluta; 0 applets
  retirados, o sea que este inventario y busybox-vigia.py ahora coinciden

El escáner se extrajo a scripts/lib/sh_analisis.py para no tener dos copias que
divergan (ADR 0019 un piso más abajo). Comprobado que la extracción no cambió la
salida de sh-superficie-cards.py, byte a byte, y que el orden de los empates es
determinista para que sea diffable.

Van anotados los SIETE bugs del instrumento, que es la parte que enseña: los
comentarios sin quitar (89 backticks de prosa en cosecha-cron.sh), shlex
desincronizándose sobre el fichero entero, los cuerpos de heredoc (uno era
Python y aportaba la orden 'p' — sacarlos bajó las externas de 680 a 257), los
patrones de case dando 'init', los cuerpos de $(( )) dando 'i+1', los
descriptores dando '2', y la ruta absoluta confundida con applet. Ninguno se
veía en la salida: los siete daban números plausibles.
2026-09-21 19:54:12 +00:00
Sergio a66346fb05 puerta 5 cerrada: repo.gioser.net sirve el repo FIRMADO, y el guion aprende cómo se recarga esta caja
`takana install zsh --repo https://repo.gioser.net --trust ./trust --require-signed` ⇒
`release: trusted (by release)`, 1331 ficheros en 0,286 s, zsh 5.9 corriendo. 173 paquetes
anclados, 0 sin ancla, certificado CN=repo.gioser.net de Let's Encrypt.

Tres cosas que sólo se supieron al poder entrar como root, y que el guion ahora sabe:

· `caddy reload` NO funciona acá: el Caddyfile lleva `admin off` y el reload habla por ese admin
  (de ahí que el :2019 estuviera siempre cerrado). La recarga real es `arjectl restart caddy` —
  caddy es un Ente de arje con Restart{1000,30000}, no un proceso suelto.
· Un restart levanta los 19 dominios o ninguno, así que el guion saca un TESTIGO de la config
  previa (acá gitea.gioser.net), espera a que responda, y si no vuelve restaura y revierte.
  Comprobado después: los 8 dominios vivos siguen en pie.
· El certificado tarda más que la comprobación: el guion dio «todavía no responde» y la misma URL
  daba 200 segundos después. El mensaje ahora manda reintentar antes de ir a leer logs.

Y dos fallos míos, medidos: backticks dentro de comillas y de un heredoc sin entrecomillar se
EJECUTAN (salió un `journalctl: not found` en mitad de un mensaje de ayuda), y `[ -n "$X" ] && echo`
como última orden mata el guion entero bajo `set -e`.

El espejo provisional sin firmar de takana.gioser.net/repo se borró: con el firmado en pie, un
origen sin firma no es un origen.
2026-09-21 19:45:41 +00:00
Sergio e8524eab93 montar-trabajo: las variables desnudas se comían un usuario, y los echo ya van entre comillas
Tercer bug de la misma card, de la misma familia y el de peor diente: con un
directorio llamado «juan perez» en /home, el $u desnudo se partía en dos
campos, id -u juan perez fallaba, y el || continue saltaba a ese usuario EN
SILENCIO — se quedaba sin su /run/user/<uid>.

- u=$(basename "$h"), chown "$u", "/run/user/$uid"
- id -u izado a uid=$(id -u "$u" 2>/dev/null) || continue: se llamaba cuatro
  veces por usuario (una para el test y tres para las órdenes), ahora una. No
  era el bug, era la forma limpia de entrecomillarlo — y se ve en la medición,
  que pasó de id×4 a id×1
- los dos echo de error, ahora entre comillas. Inocuo hoy (ningún glob en el
  texto) y se comprobó que el mensaje y el rc=78 salen idénticos

Medido con el mismo banco de stubs sobre un /home de prueba con «juan perez»,
root y nadie: antes recibía chown sólo root, ahora root y «juan perez». «nadie»
no recibe en ninguno, que es lo correcto — no está en passwd. Parsea en
busybox, bash y brush.
2026-09-21 19:36:00 +00:00
Sergio 76885a93fe estado: cosecha granja 2026-09-21T19:31:38Z — avance del árbol KDE 2026-09-21 19:31:38 +00:00
Sergio 972eb6619d espejo provisional del repo en takana.gioser.net/repo, sin firmar y diciéndolo
Mientras repo.gioser.net espera su vhost (root), el catálogo queda servido bajo el sitio que Caddy
ya atiende: /work/sergio/gioser-web/takana/repo, escribible sin root y con certificado válido. 173
paquetes; `install zsh` desde la URL pública tarda 0,22 s (1331 ficheros) y el binario corre.

El índice va SIN FIRMA a propósito. La clave de release está en /root/.config/takana/keys y desde la
jaula no se alcanza; firmarlo con otra habría sido peor que no firmarlo, porque una autoría
inventada se lee igual que una real — es la «firma sin gestión de claves» que repo-perfil.sh se
niega a hacer. Se le quitó la firma de la clave de prueba con la que se ensayó. Control:
--require-signed lo rechaza.

Dos cuidados, porque vive dentro del clon de git de otra web: repo/.gitignore con `*` (el directorio
se ignora a sí mismo, así un `git add -A` en gioser-web no se lleva 173 .tkn) y hermanas.py no lo
borra (sólo escribe index.html). Comprobado que el clon de la web sigue limpio.

Se quita con un rm -rf el día que repo.gioser.net sirva el firmado. No cuenta como el segundo
origen del ADR 0014: un origen sin firma no es un origen.
2026-09-21 19:28:17 +00:00
Sergio 4b7ef7d5a1 montar-trabajo: las comillas que faltaban — el awk fallaba en cada arranque y el grep podía saltarse el mount
Los dos los encontró scripts/sh-superficie-cards.py al inventariar las órdenes
externas de las cards. La card nació así en su único commit (3b305642): no fue
un accidente de edición posterior.

- awk NR==2{print }  → awk 'NR==2{print $4}'
  Sin comillas, awk daba error de sintaxis SIEMPRE y el echo final imprimía
  «montar-trabajo: /vvv  libres», sin el número, en cada arranque.
- grep -q  /vvv  /proc/mounts  → grep -q " /vvv " /proc/mounts   (y lo mismo
  para /work/sergio)
  El patrón desnudo matchea cualquier línea que CONTENGA la subcadena, así que
  la comprobación «¿ya está montado?» podía dar verdadero por un /vvv/algo y
  saltarse el mount en silencio. Latente: hace falta otra línea con esa
  subcadena para que muerda.

Probado corriendo el fragmento entero con /proc/mounts sustituido por un
fichero de prueba y mkdir/chmod/chown/mount/blkid stubbeados — nada mutó la
máquina. Antes: en el escenario del falso positivo NO montaba /vvv. Después:
monta, y el echo trae el número. Parsea en busybox, bash y brush.
2026-09-21 19:27:35 +00:00
Sergio 0d32ec87b1 cards: la superficie de shell del producto, MEDIDA — 23 de 33 cards, 8 builtins, y ningún backtick
Contesta qué tendría que saber hacer un /bin/sh propio para que el producto
arranque, contado sobre el árbol y no sobre POSIX.

- 33 cards en cuatro poblaciones (recetas, bootstrap, servidor, mudanza); 23
  ejecutan shell, 10 son exec directo
- el idioma es uno y se repite 18 veces: test … || { echo >&2; exit 78; }; exec …
- NO aparece nunca: if, funciones, ${…}, posicionales, $?, here-docs, subshells,
  segundo plano — ni comillas invertidas, que es lo que tumbó a brush en el sandbox
- 8 builtins y 14 órdenes externas; cruzadas con busybox-applets.tsv, CERO
  retiradas por el recorte 401 → 277: es el guardián que el plan pedía, y ahora
  corre en un segundo
- las 23 parsean idéntico en busybox, bash y brush (banco diferencial, --files)

Hallazgo que hacía falta para medir bien: en una Card el argv va SIN argv[0] —
arje lo pone desde exec (lo dice la card de hammerd). Sin eso, el idioma de la
mudanza (exec=/bin/sh, argv=[-c, …]) desaparecía de la cuenta.

La detección respeta comillas con un escáner de estados: la primera versión
contaba 11 backticks que eran prosa dentro de mensajes de error.
2026-09-21 19:22:59 +00:00
Sergio 2e3b8101c0 publicar-repo: como root, el scratch de build va aparte — si no, envenena el árbol del usuario
`work_root` sale de `dirname(store)/work`, o sea `/work` con el store en `/store`, y ahí `sources`
y `out` son ENLACES a `/work/sergio/work/…`, que es del usuario. Publicar como root dejaría
directorios de root en su árbol y su siguiente build moriría con un permiso denegado que no
menciona la causa. No es hipotético: root ya congeló el clon de tawasuyu del dueño dejándole 24
entradas suyas dentro del `.git`, y del lado de root no falló nada (ver publicar-webs.sh).

Si corre como root y nadie fijó TAKANA_WORK, lo manda a /var/tmp/takana-publicar-work y lo dice.
Comprobado que el override no mueve el hash: zsh sella en b3:0be3630d… con y sin él.

Y el mensaje final deja de decir «falta el vhost» cuando lo acaba de poner: si se instaló y aun así
no responde, lo que hay que mirar es el certificado, el DNS y el repo, en ese orden.
2026-09-21 19:16:05 +00:00
Sergio 208d545e68 sh: el trap del subshell, reportado — y la lección de que upstream ya lo tenía en sus PRUEBAS
brush#1396 abierto. Antes de escribirlo apareció que reubeno/brush ya marcaba
el fallo como known_failure en trap.yaml:226 (y :269, :323 para las otras dos
variantes), sin issue que lo siguiera.

Queda anotado porque es método, no anécdota: un known_failure en la suite del
candidato es evidencia de primera y cuesta un gh api mirarla. Buscar en las
PRUEBAS del candidato, no sólo en sus issues, antes de decir «hallazgo nuevo».

Lo que el reporte sí aporta sobre esos TODOs: que las tres variantes son
probablemente un mismo bug (on_exit() sólo lo llaman los puntos de entrada de
nivel superior; el subshell de interp.rs:662-683 clona el shell y devuelve el
exit code sin pasar por ahí), que también pega en pipeline y en segundo plano,
y que falla en silencio con rc intacto.
2026-09-21 19:11:08 +00:00
Sergio b7d30f2f15 publicar-repo: el vhost, con validación y vuelta atrás — y publicar deja de ser compilar
Dos cosas que el ensayo del guion destapó, las dos medidas:

1. `repo-perfil.sh` NO acotaba el build. `pack --build` es instantáneo con cache-hit y una
   compilación entera si el artefacto no está sellado, así que «publicá el repo» se convertía en
   silencio en «ponete a compilar el perfil»: la corrida se puso a moler `libnftnl` en una caja de
   4 cores que ya estaba a load 46, y después venía `rust`. `build-repo.sh` llevaba `timeout` desde
   siempre; el que se corre en producción, no. Ahora BUILD_TIMEOUT (120 s) y SKIP_UNSEALED=1, con
   el vencimiento tratado como «no estaba sellado» y no como error. Resultado del perfil entero:
   173 anclados, 0 sin ancla, 2 saltados (os-release, rust), sin compilar nada.

2. `--install-vhost` pone el bloque de Caddy sin que haya que editar a mano, pero el orden es lo
   único que lo vuelve seguro: copia → añade → `caddy validate` → SÓLO ENTONCES `reload`. Si
   validate o reload fallan, restaura la copia y recarga con ella. Un reload con la config rota no
   tira el sitio nuevo: tira los 19 de la caja.

Probado en los dos caminos contra un Caddy de juguete (el de la caja no tiene el admin abierto):
con reload bueno, el admin muestra el server nuevo sirviendo el repo y el sitio previo intacto; con
reload fallido, el Caddyfile vuelve byte a byte y no queda rastro del bloque.

Y `repo-perfil.sh` pasa a respetar TAKANA del entorno: la guarda de publicar-repo.sh comprobaba un
binario y se publicaba con otro, que es peor que no tener guarda.
2026-09-21 19:03:08 +00:00
Sergio 7de281a79f estado: cosecha granja 2026-09-21T19:01:40Z — avance del árbol KDE 2026-09-21 19:01:40 +00:00
Sergio ddc6e4845b sh: el banco DIFERENCIAL — compara el ÁRBOL producido, no sólo stdout, y el generador saca el bug de brush sin saber que existe
El banco POSIX de 57 casos lo pasa brush 56/57 y aun así no construye una
receta autotools: mide lo que a uno se le ocurrió preguntar. Este pregunta si
el candidato hace LO MISMO que el control sobre el material que hay.

- compara (rc, stdout, árbol de ficheros producido); stderr es aviso, no
  divergencia — el texto de error diverge legítimamente
- el árbol es la columna que faltaba: el caso 10 diverge SÓLO ahí, que es la
  clase de bug del libtool mal escapado (rc=0, stdout idéntico)
- toda divergencia se confirma re-corriendo control y candidato: separa caso
  no determinista de shell inestable de divergencia real
- fuentes: fixtures de regresión, generador de tortura de comillas, las 1130
  fases del corpus y configure/libtool reales en sh -n

Medido, 1273 casos en 5,5 s: brush 35 duras, bash 8, y CERO en los 1156 casos
de parseo. El generador saca el #1394 entero sin conocerlo. Hallazgo nuevo:
brush no ejecuta nunca el trap EXIT de un subshell — y eso golpea el frente
del PRODUCTO, donde el veredicto del paso 3 decía que no había evidencia en
contra.
2026-09-21 18:58:09 +00:00
SergioandClaude Opus 5 0db85d51f8 el busybox recortado ARRANCA: Stage 1 en QEMU, contra una línea base
Se ensamblaron dos Stage 1 completos y se arrancaron los dos, para que cualquier
diferencia se atribuya al recorte y no al azar:

  base      b3:1a3823b4...  400 applets  qemu rc=0
  recortado b3:3aa8cd2d...  278 applets  qemu rc=0

Los dos dan shell y se apagan limpio. El diff de las dos consolas son 35 líneas y
ninguna es funcional: TSC, direcciones del RAMDISK, BogoMIPS, hora del RTC. La única
diferencia real es el conteo de applets desde dentro de la imagen: 410 contra 288.

Verificado en el recortado: arje-zero como PID 1, la seed card carga (la getty llegó
a incarnar), getty -l /bin/sh da prompt y responde, y poweroff -f apaga de verdad
-- uno de los cinco indultados, probado en vivo.

Corrido en el worker con QEMU en TCG: el LXC no expone /dev/kvm. Hubo que instalar
qemu-system-x86-core y reingerir la semilla de Stage 0, que no estaba en ese store y
salió con el mismo hash que el manifiesto ya anotaba.

Es Stage 1, no el producto: product-boot-test.sh sigue pendiente porque al store del
worker le faltan netup, openssh, uutils, findutils-xargs, diffutils y ripgrep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:50:52 +00:00