gioser

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 1574Shell 204Rust 111Markdown 993763 Definiciones

cosmic: la calculadora del lanzador ANDA — el stdin de qalc era el stdout

=123*456 → 56088 en el lanzador. libqalculate re-sellado b3:edb8d14d.

La causa del «no lee nada de la tubería» no era readline, ni el toolchain, ni la versión:
el binario salía DINÁMICO pese a link="static", porque libtool se come el -static que
inyecta el harness. Y en un musl dinámico stdin, stdout y stderr se resolvieron por copy
relocation a UNA SOLA ranura de BSS compartida —las tres en la misma dirección—, así que
fileno(stdin) daba 1: el stdin del programa era el stdout. Eso explica la asimetría que
parecía absurda, que qalc -f /dev/stdin funcionara (abre el fd por RUTA, sin tocar el
FILE* roto) y qalc leyendo stdin no.

Fix: make LDFLAGS="-all-static -no-pie", el mismo par que ya usa libxml2. Con él los tres
símbolos vuelven a tener direcciones distintas en .rodata. Regla para el runbook:
link="static" en la receta NO garantiza un binario estático — readelf -d y
nm -S | grep ' std' lo contestan en dos segundos.

Segundo muro: qalc pregunta si activar autocalc antes de leer nada, el plugin le manda la
expresión y cierra, la expresión se consume como respuesta y qalc nunca llega a guardar
config ⇒ vuelve a preguntar siempre. Se siembra qalc.cfg desde cosmic-start, política de
imagen como el color de fondo.

Y hay que repetir el -L de los alias de curses en el make: el LDFLAGS del make pisa al
del configure.

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

9665cc29d4438022c8d9a57e4578dc83d5300998 · por sergio · (hace 2 meses) · padre 4d9f17daf · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

5 archivos · +95 −48

diff --git a/docs/evidencia/cosmic-calculadora-qemu-2026-08-04.png b/docs/evidencia/cosmic-calculadora-qemu-2026-08-04.png
new file mode 100644
index 00000000..c45ad4ba
Binary files /dev/null and b/docs/evidencia/cosmic-calculadora-qemu-2026-08-04.png differ
diff --git a/docs/runbooks/cosmic-desktop.md b/docs/runbooks/cosmic-desktop.md
index 08efe24b..d4d0e7fa 100644
--- a/docs/runbooks/cosmic-desktop.md
+++ b/docs/runbooks/cosmic-desktop.md
@@ -108,7 +108,7 @@ Más `cosmic-settings-daemon` y `xdg-desktop-portal-cosmic` fuera de esa lista.
 | **`cosmic-applets`** (19 binarios en uno) | ✅ **sellado** (necesitó `dbus-shared`, ver gotcha 10) | `b3:3e53a325` |
 | `pop-launcher` 1.2.7 (otro repo, ver abajo) | ✅ sellado a la primera | `b3:64a5cbaa` |
 | `gmp` 6.3.0 · `mpfr` 4.2.2 (cadena de la calculadora) | ✅ sellados a la primera | `b3:d99a0a5d` · `b3:13982d20` |
-| `libqalculate` 5.12.0 (`qalc`) | ✅ sellado — **pero el plugin del lanzador no lo puede usar, ver abajo** | `b3:6bb435a7` |
+| `libqalculate` 5.12.0 (`qalc`) | ✅ sellado — la calculadora del lanzador ANDA (ver abajo) | `b3:edb8d14d` |
 
 **La cola está COMPLETA: 30 recetas, `escritorio-cosmic` 68/68.**
 
@@ -481,38 +481,66 @@ plugin, que contesta «qalc command is not installed»**. Esa respuesta es la me
 disponible: el proceso hijo corrió, evaluó la consulta y devolvió una fila. Lo que falta ahora es
 `libqalculate` (el binario `qalc`), que es un paquete, no un problema.
 
-### 🧮 `libqalculate`: empaquetado y andando, e inservible para el plugin
+### 🧮 `libqalculate`: la calculadora del lanzador, y un `stdin` que era `stdout`
 
-`qalc` está sellado y **funciona**: `qalc -t '2+2'` → `4`, `-f fichero`, y el modo interactivo sobre
-una terminal calcula perfecto (`123 × 456 = 56088`). La cadena que baja desde una tecla —`Super` →
-cosmic-launcher → pop-launcher → plugin `calc` → `qalc` → **mpfr → gmp**— está entera en el catálogo.
+**`=123*456` → `56088` en el lanzador** (`cosmic-calculadora-qemu-2026-08-04.png`). La cadena entera
+desde una tecla está en el catálogo: `Super` → cosmic-launcher → pop-launcher → plugin `calc` →
+`qalc` → **mpfr → gmp**.
 
-**Y aun así el lanzador sigue mostrando una fila vacía.** El plugin no le pasa la cuenta por argv:
-arranca `qalc` SIN expresión, con stdin y stdout en tubería, le ESCRIBE la expresión y cierra. Ese
-modo, en nuestro binario, no lee nada. Acotado con dos invocaciones que sólo difieren en cuándo llega
-el EOF:
+Pero el camino hasta ahí tuvo dos muros que vale la pena dejar escritos, porque ninguno se parecía a
+lo que era.
 
-```sh
-{ printf '123*456\n'; sleep 3; } | qalc   # LEE (aparece la pregunta de primer arranque)
-  printf '123*456\n'             | qalc   # NO lee NADA: imprime el prompt y sale con rc=0
+**1) `stdin` apuntaba al descriptor 1.** El plugin no le pasa la cuenta por argv: arranca `qalc` SIN
+expresión, con stdin en tubería, le escribe la expresión y cierra. En ese modo el binario **no leía
+nada** — ni error ni resultado, la fila del lanzador salía en blanco. El síntoma acusaba a readline,
+al toolchain y a la versión, y no era ninguno:
+
+```
+DIAG-main isatty=0 feof=0 ferror=0 fd=1        ← fileno(stdin) == 1
+nm -S qalc | grep -E ' (stdin|stdout|stderr)$'
+  1847148 8 B stderr
+  1847148 8 B stdin                             ← las TRES en la misma dirección
+  1847148 8 B stdout
 ```
 
-O sea: **con el EOF ya pendiente en un stdin que no es terminal, `readline()` devuelve NULL antes de
-entregar la línea que sí está en el búfer** — y así es exactamente como lo invoca el plugin.
+La causa: **el binario salía DINÁMICO aunque la receta dice `link = "static"`, porque libtool se come
+el `-static` que inyecta el harness** ([[link-static-mentira-libtool]] otra vez, cuarto caso). Y en
+un musl dinámico las tres variables de stdio se resolvieron por *copy relocation* a **una sola ranura
+de BSS compartida**: la última que escribe el loader gana, así que el `stdin` del programa era el
+`stdout`. De ahí la asimetría que parecía absurda: `qalc -f /dev/stdin` funcionaba —abre el
+descriptor por RUTA, sin tocar el `FILE*` roto— y `qalc` leyendo `stdin` no.
 
-Lo que ya se DESCARTÓ midiendo, para que nadie lo repita:
-- **No es el toolchain.** Una sonda estática musl del propio sandbox (C y C++) lee la tubería sin
-  problema. El binario no tiene nada raro: un solo `NEEDED`, `libc.so`.
-- **No es readline vs no-readline.** Sin readline es peor (no lee nunca, ni con la tubería abierta);
-  con readline al menos lee cuando el escritor no ha cerrado.
-- **No es la versión de readline.** Se construyó una 8.3 como sombra de cola y el síntoma no se
-  movió; la sombra se descartó en vez de dejar una receta duplicada que no arregla nada.
-- **No es el descriptor 0.** `qalc -f /dev/stdin` —el MISMO pipe, reabierto por ruta— sí funciona.
-  El que no sirve es el `FILE*` `stdin`.
+El fix es el par que ya usa `libxml2`: `make LDFLAGS="-all-static -no-pie"`. Con él el binario queda
+estático de verdad y los tres símbolos vuelven a tener direcciones distintas, en `.rodata`:
+
+```
+  11e71c8 8 R stderr
+  11e71d0 8 R stdin
+  11e71d8 8 R stdout
+```
 
-La pista que queda es justamente esa última asimetría. Y el aviso de método: **empaquetar el binario
-que falta no garantiza que el consumidor lo pueda usar** — «qalc no está instalado» y «qalc está y no
-contesta» son dos estados distintos, y el segundo se ve peor porque la fila sale en blanco.
+**La regla, y es de las caras: `link = "static"` en la receta no garantiza un binario estático.**
+Cuando algo se comporta como si su `stdin`/`stdout` estuvieran cruzados, `readelf -d` y
+`nm -S | grep ' std'` contestan en dos segundos lo que el log no dice nunca.
+
+**2) La pregunta de primer arranque se come la cuenta, SIEMPRE.** Antes de leer nada, `qalc` pregunta
+si activar `autocalc`. El plugin le manda la expresión y cierra: la expresión se consume como
+respuesta, no es un sí/no válido, y qalc **no llega a guardar config** ⇒ la próxima vez vuelve a
+preguntar. No es «falla la primera vez»: no anda nunca. Se siembra
+`~/.config/qalculate/qalc.cfg` con `calculate_as_you_type=0` desde `cosmic-start` — política de la
+imagen, como el color de fondo; una distro de verdad lo pondría en `/etc/skel`.
+
+**Gotchas de empaquetado de la cadena:**
+- **`gmp` va con `--disable-assembly`.** Elige rutinas en ensamblador mirando la CPU de quien
+  compila, o sea hornearía la ISA del laptop en el artefacto y el hash dejaría de significar lo mismo
+  en la granja. Es una calculadora de escritorio: la implementación en C portable sobra.
+- **El configure de libqalculate no encuentra readline** porque prueba `-lreadline` junto a
+  `-lncurses`, `-lcurses`, `-ltermcap` y `-ltinfo`, y el corpus **sólo empaqueta `libncursesw.a`**.
+  Que el paquete se llame `ncurses` no significa que provea `-lncurses`. Los alias van en un
+  directorio LOCAL (`.curses-compat/`) y no en `/usr/lib`: esa vista es la evidencia de qué se
+  declaró, y ensuciarla para que pase un test deja mintiendo justo al mecanismo que existe para no
+  mentir. Ojo: hay que repetir el `-L` en el `make`, porque el `LDFLAGS` del `make` PISA al del
+  `configure`.
 
 ### ⏱ El panel tiene un DEADLINE de arranque: con 4 vCPU sale mutilado
 
@@ -530,9 +558,9 @@ El número de procesos no distingue «arrancó» de «dibujó». El framebuffer
 
 ## Lo que falta, en orden
 
-1. **Aplicaciones.** No hay ni una: los 20 `.desktop` de la imagen son `NoDisplay=true`. El primer
-   candidato barato es `libqalculate` (el binario `qalc`), que es lo único que le falta al plugin de
-   la calculadora para dar un resultado en vez de decir que no está.
+1. **Aplicaciones con ventana.** Sigue sin haber ninguna: los 20 `.desktop` de la imagen son
+   `NoDisplay=true`, así que la biblioteca sale vacía y eso es correcto. La calculadora del lanzador
+   ya da resultados, pero es una fila de texto, no una app. `cosmic-term` sería la primera de verdad.
 2. `xdg-desktop-portal-cosmic` — sin él no hay captura de pantalla, ni file-chooser, ni compartir.
 3. `cosmic-settings` (el panel de control) y `cosmic-files`: la suite tiene aplicaciones y el
    catálogo todavía no.
diff --git a/docs/state/build-state-cosmic.json b/docs/state/build-state-cosmic.json
index c59ff535..6a312856 100644
--- a/docs/state/build-state-cosmic.json
+++ b/docs/state/build-state-cosmic.json
@@ -3,9 +3,8 @@
  "totals": {
   "recipes": 800,
   "nodes": 800,
-  "sealed": 797,
-  "never": 2,
-  "debt": 1
+  "sealed": 798,
+  "never": 2
  },
  "by_class": {
   "c": 160,
@@ -15,7 +14,7 @@
   "rust": 241
  },
  "debt_by_class": {
-  "c": 3
+  "c": 2
  },
  "by_queue": {
   "corpus": {
@@ -23,8 +22,7 @@
    "never": 2
   },
   "incoming-cosmic": {
-   "sealed": 29,
-   "debt": 1
+   "sealed": 30
   }
  },
  "by_profile": {
@@ -41,8 +39,7 @@
   "escritorio-cosmic": {
    "raices": 20,
    "clausura": 68,
-   "sealed": 67,
-   "debt": 1
+   "sealed": 68
   },
   "escritorio-mirada": {
    "raices": 14,
@@ -4898,7 +4895,7 @@
    "hash": "b3:d99a0a5d6fb0e301523c3797d899e7c1002c58f282865df87e221b4e47fd0642",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "escritorio-cosmic"
    ],
@@ -8228,8 +8225,8 @@
    ],
    "cls": "c",
    "queue": "incoming-cosmic",
-   "hash": "b3:fcf9c6c097f9f37235319d52dbe64e42deaa012cc374aee206f9c5d2201ae3ac",
-   "state": "debt",
+   "hash": "b3:edb8d14db8e625168dea50e40073840b29a6800d7c5b027bbf01f17f15fd5b68",
+   "state": "sealed",
    "blocked_by": [],
    "unblocks": 0,
    "perfiles": [
@@ -8452,7 +8449,7 @@
    "hash": "b3:c4c65c4b17b86211c4a7f076e6693aedb9168d72cc8e45a4a9022fdd75f79494",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome"
@@ -8804,7 +8801,7 @@
    "hash": "b3:fbad44acdc76df4b081cccd3cc02666664b6d1a03cd6fa482f85791fa533a602",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 2,
+   "unblocks": 1,
    "perfiles": [
     "base",
     "cli",
@@ -9471,7 +9468,7 @@
    "hash": "b3:13982d20e404affb9ab825c157a50efc2f0370e1befe75cb7ce93b914d55d497",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "escritorio-cosmic"
    ],
@@ -9695,7 +9692,7 @@
    "hash": "b3:a3be97622d20cd8d831a25403e00eccb23f2585ed78670a4e12d6cbe519fb8ec",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "base",
     "cli",
@@ -10617,7 +10614,7 @@
    "hash": "b3:f0b5d0b1cb4321b3c61f24a871b9a58da3ebb7eb903af4015e35bd41a6465437",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "base",
     "cli",
@@ -11053,7 +11050,7 @@
    "hash": "b3:03c0eb9ccaf42a65d876c0778b7ee53695f6389bbe5c4c9bcf606f8aa328ec36",
    "state": "sealed",
    "blocked_by": [],
-   "unblocks": 1,
+   "unblocks": 0,
    "perfiles": [
     "base",
     "cli",
diff --git a/recipes/incoming-cosmic/libqalculate.toml b/recipes/incoming-cosmic/libqalculate.toml
index 621a1dff..dd57e95c 100644
--- a/recipes/incoming-cosmic/libqalculate.toml
+++ b/recipes/incoming-cosmic/libqalculate.toml
@@ -69,8 +69,17 @@ for a in libncurses libcurses libtinfo libtermcap; do ln -sf /usr/lib/libncurses
 	--disable-nls \
 	LDFLAGS="$LDFLAGS -L$PWD/.curses-compat"
 '''
-compile = 'make -j"$(nproc)"'
-install = 'make DESTDIR=/out install'
+# `-all-static` NO ES REDUNDANTE con `link = "static"`, y esa confusión costó todo un frente.
+# El harness inyecta `LDFLAGS=-static`, pero **libtool se lo come**: la línea de enlace real de `qalc`
+# salía dinámica contra `libc.so`. Y un musl DINÁMICO acá no es sólo «pesa distinto»: `stdin`,
+# `stdout` y `stderr` terminaron **en la misma dirección de BSS**, las tres compartiendo una única
+# ranura de copy-relocation, así que el `stdin` del programa era en realidad el `stdout`
+# (`fileno(stdin)` = 1). De ahí que `qalc` no leyera NADA de una tubería mientras `-f /dev/stdin`
+# —que abre el descriptor por ruta, sin tocar el FILE* roto— funcionaba perfecto.
+# `-all-static` es la forma de decírselo a libtool; `-no-pie` va con él (un estático PIE no tiene
+# sentido). Mismo par que usa `libxml2` en el corpus, por la misma razón.
+compile = 'make LDFLAGS="-all-static -no-pie -L$PWD/.curses-compat" -j"$(nproc)"'
+install = 'make DESTDIR=/out install LDFLAGS="-all-static -no-pie -L$PWD/.curses-compat"' 
 
 [deps]
 build = ["make", "gmp", "mpfr", "libxml2", "pkgconf", "readline", "ncurses"]
diff --git a/scripts/cosmic/cosmic-start-qemu.sh b/scripts/cosmic/cosmic-start-qemu.sh
index def6091f..9a8815c4 100755
--- a/scripts/cosmic/cosmic-start-qemu.sh
+++ b/scripts/cosmic/cosmic-start-qemu.sh
@@ -157,6 +157,19 @@ RON
   say "fondo: color liso (el wallpaper por defecto vive en git-lfs y no se empaqueta)"
 fi
 
+# ── SEMILLA DE CONFIG DE qalc, SIN LA CUAL LA CALCULADORA DEL LANZADOR NUNCA ANDA ────────────────
+# En el primer arranque `qalc` hace una PREGUNTA interactiva («¿activar autocalc?») antes de leer
+# nada. El plugin `calc` de pop-launcher le escribe la expresión y cierra el pipe, así que la
+# expresión se consume como respuesta, no es un sí/no válido, y qalc **no llega a guardar config** ⇒
+# la próxima vez vuelve a preguntar. O sea que sin sembrar esto no falla una vez: falla SIEMPRE.
+# Es política de la imagen, no del paquete —igual que el color de fondo—: una distro de verdad lo
+# pondría en el esqueleto de /etc/skel.
+if [ ! -f "$HOME/.config/qalculate/qalc.cfg" ]; then
+  mkdir -p "$HOME/.config/qalculate"
+  printf '[General]\ncalculate_as_you_type=0\n' > "$HOME/.config/qalculate/qalc.cfg"
+  say "qalc: config sembrada (si no, la pregunta de primer arranque se come la cuenta)"
+fi
+
 # ── MODO: bare (default) o session ──────────────────────────────────────────────────────────────
 # `cosmic-session` es el camino de producción, pero HOY no se puede usar: llama a
 # `cosmic-settings-daemon` con `.expect("failed to start settings daemon")` (src/main.rs:255) y