brush: la causa era el desescapado de barras dentro de comillas invertidas — reportada, y adelantar el pin no sirve
Bajando al config.status que genera el libtool apareció el mecanismo, y es de una línea: brush aplica a `...` las reglas de $( ). POSIX manda que dentro de comillas invertidas la barra se ELIMINE cuando precede a $, ` o \ (2.6.3). Que el caso con $( ) sí esté bien es lo que delata la causa. Por eso rompe libtool: el case que decide si cada variable necesita escapado evalúa a cadena vacía bajo brush, así que todas caen en la rama sin escapar. El configure termina bien; el fichero sale mal escrito. Se construyó main de hoy (737dd57e, cuatro meses y medio sobre nuestro pin) y reproduce idéntico los cuatro casos ⇒ adelantar el pin no arregla nada. Reportado: https://github.com/reubeno/brush/issues/1394 Y la recomendación: no pelear el shell. Los pasos 1, 2 y 5 del plan no dependen de él y valen 129 applets y dos piezas de userland. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Semantic delta
No definition changed: this change is text, documentation or data.
1 files · +65 −8
- docs/plan-botar-busybox.md +65 −8
diff --git a/docs/plan-botar-busybox.md b/docs/plan-botar-busybox.md
index f3ec566f..4b2b8a7f 100644
--- a/docs/plan-botar-busybox.md
+++ b/docs/plan-botar-busybox.md
@@ -249,11 +249,43 @@ Ese fichero ya no lo parsea **nadie** — ni brush ni busybox lo aceptan (`rc=2`
368). Y el contraprueba cierra el caso: **el `libtool` del control lo parsea brush sin quejarse
(`rc=0`)**. ⇒ El parser de brush está bien; lo que difiere es lo que su shell PRODUCE.
-⚠ **El mecanismo exacto no está determinado, y conviene decirlo así en vez de inventarlo.** Las tres
-sospechas obvias se probaron aisladas y **las tres dan idéntico** en ambos shells: `echo` con barras
-invertidas, sustitución de comando preservando `\\`, y el propio `func_quote_for_eval` de libtool
-(`sed_quote_subst`) aplicado a mano. Lo medido es el diff de 25 líneas y que el `configure` termina
-bien; el resto es hipótesis.
+### El mecanismo, encontrado (2026-09-21, después de escribir lo de arriba)
+
+Primero se escribió acá que la causa no estaba determinada, tras descartar las tres sospechas
+obvias —`echo` con barras invertidas, sustitución de comando preservando `\\`, y el
+`func_quote_for_eval` de libtool aplicado a mano—, que dan **idéntico** en ambos shells. La causa
+apareció bajando al `config.status` que genera el `libtool`. **Es de una línea:**
+
+```sh
+V=hola
+echo "a: [`echo \\$V`]" # POSIX: $V · brush: \hola
+echo "b: [`echo \\\\`]" # POSIX: \ · brush: \\
+echo "c: [`echo \$V`]" # POSIX: hola · brush: $V
+echo "d: [$(echo \\$V)]" # POSIX: \hola · brush: \hola ✅
+```
+
+**brush aplica a las comillas invertidas las reglas de `$( )`.** POSIX manda otra cosa: dentro de
+`` `…` `` la barra invertida conserva su significado literal **salvo** cuando precede a `$`,
+`` ` `` o `\`, donde se ELIMINA antes de parsear el texto del comando (POSIX.1-2024, 2.6.3). Que el
+caso **d** sí esté bien es lo que delata la causa.
+
+Por qué rompe libtool: el `config.status` generado trae este bucle, que decide si cada variable
+necesita escapado antes de escribirla en el `libtool`:
+
+```sh
+for var in SHELL ECHO PATH_SEPARATOR SED GREP …; do
+ case `eval \\$ECHO \\""\\$$var"\\"` in
+ *[\\\`\"\$]*) eval "lt_$var=\\\"\`\$ECHO \"\$$var\" | \$SED \"\$sed_quote_subst\"\`\\\"" ;;
+ *) eval "lt_$var=\\\"\$$var\\\"" ;;
+ esac
+done
+```
+
+Bajo brush ese `case` evalúa a **cadena vacía**, así que **todas** las variables caen en la rama sin
+escapar. De ahí las 25 líneas y de ahí que el `configure` termine bien: no falla nada, sólo sale mal
+escrito.
+
+**Reportado upstream: [reubeno/brush#1394](https://github.com/reubeno/brush/issues/1394).**
## Y el otro dato, que no es un fallo pero decide
@@ -267,9 +299,34 @@ vuelve medible más lento. Es un argumento independiente del bug, y no se arregl
2. **El frente del producto sigue abierto**: las cards, la getty y la shell interactiva no generan
libtool. Ahí brush no tiene ninguna evidencia en contra — pero tampoco se probó, y el
`product-boot-test.sh` es la prueba que falta.
-3. **Hay un bug upstream que reportar** (reubeno/brush): `configure` de autotools genera un `libtool`
- con un nivel de escapado de menos. El reproductor es una receta autotools cualquiera, y el
- artefacto es el diff de 25 líneas de arriba.
+3. **Reportado upstream: [reubeno/brush#1394](https://github.com/reubeno/brush/issues/1394)**, con
+ el reproductor de una línea, la tabla de los cuatro casos y el fragmento de `config.status`.
4. **El paso 5 del plan (grep/sed POSIX en Rust) sube de prioridad**, porque no depende del shell.
5. **No se tocó el lab compartido**: copia con hardlinks y stores tirables. El worker siguió
compilando su cola en paralelo, serializado por el `flock`.
+
+## ¿Parchar, o buscar otra opción?
+
+**Primero lo que cierra la pregunta barata: adelantar el pin NO sirve.** Nuestra receta apunta a
+`96a26d0c` (tag `brush-shell-v0.4.0`, mayo 2026), que sigue siendo el último release. Se construyó
+**`main` de hoy** (`737dd57e`, 2026-09-21 — cuatro meses y medio de trabajo encima) y **reproduce
+idéntico**, los cuatro casos. No hay nada que esperar de la rama.
+
+Lo que queda, en orden de coste:
+
+| opción | qué cuesta | qué deja |
+|---|---|---|
+| **Esperar a #1394** | cero | el upstream es activísimo (issue #1393 de anteayer) y el bug es acotado, pero no es nuestro calendario |
+| **Parchar brush en la receta** | un `.patch` en el árbol, como ya hacen 13 recetas | ⚠ es un parche **al parser de un shell**, que es el componente más cargado de todos. Y el bug apareció en el PRIMER build real: no es prueba de que sea el único |
+| **dash** | receta nueva, C, ~200 KB. Es el `sh` de referencia POSIX | resuelve el sandbox YA — y no avanza nada la soberanía en Rust |
+| **bash como `/bin/sh` del producto** | cero: ya está sellada Y en los 7 perfiles | tapa el frente B sin escribir nada. No sirve para el sandbox sin medirlo antes |
+| **Dejar busybox donde está** | cero | es lo que dice el plan: el shell NO es por donde se empieza |
+
+**La recomendación es la última, y no por conservadurismo.** El plan ya ordenaba los pasos 1, 2 y 5
+—recortar el defconfig, declarar lo sellado, escribir `grep`/`sed` POSIX— **sin depender del shell**,
+y valen 129 applets y dos piezas de userland. Gastar el turno en pelear el shell es empezar por el
+único frente que está bloqueado por terceros.
+
+Lo que sí conviene hacer ya, porque es gratis: **suscribirse a #1394** y **medir brush como `/bin/sh`
+del PRODUCTO** (`product-boot-test.sh` + las cards), que es un consumidor que no genera libtool y
+donde brush no tiene ninguna evidencia en contra.