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 1483Shell 181Rust 107Markdown 963619 Definiciones

ncurses-shared: cierra la fuga de pw-top al lab, que llevaba desde agosto

recipes/pipewire.toml:85-88 afirma que quitando la dep de ncurses «meson saltea pw-top
y el resto construye». ES FALSO, y llevaba así desde al menos el 2026-08-29. Quitar la
dep no desactivó nada: el guardián de upstream es `if ncurses_dep.found()` y meson lo
encontró igual, en el sysroot Alpine DEL LAB. pw-top se construye, se sella y sale con
NEEDED libncursesw.so.6, que ningún artefacto del cierre publica porque el ncurses del
catálogo es --without-shared. Una herramienta rota dentro de las CUATRO imágenes,
invisible para el store porque el lab no entra en hash_inputs.

LA LECCIÓN, que es más grande que pw-top: QUITAR UNA DEP DE [deps] NO APAGA LA FUNCIÓN.
Cambia de dónde sale. Para apagarla de verdad hay que decírselo a la perilla del
proyecto; si no la hay, la dep tiene que estar declarada y satisfecha desde el store.

POR QUÉ UNA RECETA NUEVA Y NO RE-SELLAR PIPEWIRE. El arreglo limpio es declarar la dep y
reconstruir; cuesta 10 dependientes directos y 7 sellados que caen a deuda, entre ellos
xdg-desktop-portal-cosmic y cosmic-settings-daemon, que son Rust —el portal murió por
OOM tres veces y el que selló tardó 48 min con pico de 5,4 GiB de swap—. No se paga eso
hoy por una herramienta de diagnóstico, y menos con otro agente compilando en la misma
máquina. Lo que cruza acá es un SONAME, y para eso el repo ya tiene regla escrita:
enlazar contra una variante y CORRER contra otra es legítimo cuando lo que cruza es un
SONAME. Cero rebuilds.

⚠ QUEDA DEUDA ANOTADA: pipewire sigue enlazando contra el lab en BUILD. El día que se
re-selle por cualquier otro motivo hay que declarar ncurses-shared en sus [deps] y
corregir el comentario mentiroso. Esto tapa el síntoma en runtime, no la causa.

DOS COSAS QUE COSTARON, y están escritas en la receta:
- `-stats` es un flag de GNU ld que lld rechaza, y viene DENTRO del token
  `-Wl,-soname,...,-stats,-lc` que arma el configure de ncurses. No se puede pasar por
  LDFLAGS ni hay perilla MK_SHARED_LIB, y el patrón .zwrap de poppler tampoco sirve tal
  cual porque filtra argumentos completos y acá hay que reescribir uno.
- Quitando `-stats`, el siguiente en caer es el `-lc` del mismo token. El driver de zig
  valida lo que va dentro de `-Wl,` y no deja pasar ninguno de los dos; libc la liga él
  solo, así que la cola `,-stats,-lc` se va entera. Se quita del Makefile GENERADO, con
  un grep previo que FALLA si el configure deja de emitirlo (para no seguir en silencio).

EVIDENCIA, con control discriminante:
  sin ncurses-shared -> Error relocating /lib/libncursesw.so.6: __vfprintf_chk: symbol
                        not found   (símbolo de _FORTIFY_SOURCE de glibc que musl no
                        tiene: la fuga al lab, reproducida)
  con ncurses-shared -> carga y corre; sólo falla en el runtime de PipeWire por no haber
                        demonio ni plugins SPA en la raíz de prueba

vigia-sonames en los cuatro perfiles: libncursesw.so.6 Y libpanelw.so.6 desaparecen.
Lo que queda son herramientas de build (go, python3, perl) más tres de RUNTIME que NO
son de este cambio y quedan anotadas: spidermonkey pide libgcc_s/libstdc++ en gnome,
libadwaita pide liblzma, y sqlite-shared pide libreadline.

Grafos: 820/820, 999/999, 883/883, 856/856, 833/833.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo

6c37df6fa41ec3faa8e1440f18e861b078a06104 · por Sergio · (hace 1 mes) · padre 68274d6c2 · ver el árbol en este commit

Delta semántico

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

7 archivos · +311 −46

diff --git a/docs/state/build-state-cosmic.json b/docs/state/build-state-cosmic.json
index 8a247bd7..c235e5e1 100644
--- a/docs/state/build-state-cosmic.json
+++ b/docs/state/build-state-cosmic.json
@@ -1,14 +1,14 @@
 {
  "schema": "hammer-build-state/1",
  "totals": {
-  "recipes": 855,
-  "nodes": 856,
-  "sealed": 855,
+  "recipes": 856,
+  "nodes": 857,
+  "sealed": 856,
   "ajeno": 1
  },
  "by_class": {
   "ajeno": 1,
-  "c": 202,
+  "c": 203,
   "go": 362,
   "gui": 35,
   "kernel": 6,
@@ -17,7 +17,7 @@
  "debt_by_class": {},
  "by_queue": {
   "corpus": {
-   "sealed": 817,
+   "sealed": 818,
    "ajeno": 1
   },
   "incoming-cosmic": {
@@ -36,9 +36,9 @@
    "sealed": 75
   },
   "escritorio-cosmic": {
-   "raices": 38,
-   "clausura": 132,
-   "sealed": 132
+   "raices": 39,
+   "clausura": 133,
+   "sealed": 133
   },
   "escritorio-mirada": {
    "raices": 14,
@@ -852,7 +852,7 @@
     "escritorio-mirada",
     "escritorio-sway"
    ],
-   "dependientes_total": 43
+   "dependientes_total": 44
   },
   "biome": {
    "iname": "biome",
@@ -4493,6 +4493,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 15
@@ -4613,6 +4614,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 17
@@ -8889,6 +8891,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 16
@@ -9027,6 +9030,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 23
@@ -10799,6 +10803,28 @@
    ],
    "dependientes_total": 13
   },
+  "ncurses-shared": {
+   "iname": "ncurses-shared",
+   "version": "6.5",
+   "link": "dynamic",
+   "compiler": "zig-cc",
+   "deps": [
+    "binutils"
+   ],
+   "cls": "c",
+   "queue": "corpus",
+   "hash": "b3:05cc7afa98796a42298b95fa932996d9d34678b99cfa545c3e3918e5a2613593",
+   "state": "sealed",
+   "blocked_by": [],
+   "unblocks": 0,
+   "perfiles": [
+    "escritorio-cosmic",
+    "escritorio-gnome",
+    "escritorio-kde",
+    "escritorio-sway"
+   ],
+   "dependientes_total": 0
+  },
   "nebula": {
    "iname": "nebula",
    "version": "1.10.3",
diff --git a/docs/state/build-state-gnome.json b/docs/state/build-state-gnome.json
index 04a06f95..230e1c7a 100644
--- a/docs/state/build-state-gnome.json
+++ b/docs/state/build-state-gnome.json
@@ -1,14 +1,14 @@
 {
  "schema": "hammer-build-state/1",
  "totals": {
-  "recipes": 882,
-  "nodes": 883,
-  "sealed": 882,
+  "recipes": 883,
+  "nodes": 884,
+  "sealed": 883,
   "ajeno": 1
  },
  "by_class": {
   "ajeno": 1,
-  "c": 242,
+  "c": 243,
   "go": 362,
   "gui": 42,
   "kernel": 6,
@@ -17,7 +17,7 @@
  "debt_by_class": {},
  "by_queue": {
   "corpus": {
-   "sealed": 808,
+   "sealed": 809,
    "ajeno": 1
   },
   "incoming-gnome": {
@@ -36,9 +36,9 @@
    "sealed": 74
   },
   "escritorio-gnome": {
-   "raices": 17,
-   "clausura": 155,
-   "sealed": 155
+   "raices": 18,
+   "clausura": 156,
+   "sealed": 156
   },
   "escritorio-mirada": {
    "raices": 14,
@@ -939,7 +939,7 @@
     "escritorio-mirada",
     "escritorio-sway"
    ],
-   "dependientes_total": 43
+   "dependientes_total": 44
   },
   "biome": {
    "iname": "biome",
@@ -4244,6 +4244,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 15
@@ -4364,6 +4365,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 17
@@ -9394,6 +9396,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 16
@@ -9532,6 +9535,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 23
@@ -11516,6 +11520,28 @@
    ],
    "dependientes_total": 13
   },
+  "ncurses-shared": {
+   "iname": "ncurses-shared",
+   "version": "6.5",
+   "link": "dynamic",
+   "compiler": "zig-cc",
+   "deps": [
+    "binutils"
+   ],
+   "cls": "c",
+   "queue": "corpus",
+   "hash": "b3:05cc7afa98796a42298b95fa932996d9d34678b99cfa545c3e3918e5a2613593",
+   "state": "sealed",
+   "blocked_by": [],
+   "unblocks": 0,
+   "perfiles": [
+    "escritorio-cosmic",
+    "escritorio-gnome",
+    "escritorio-kde",
+    "escritorio-sway"
+   ],
+   "dependientes_total": 0
+  },
   "nebula": {
    "iname": "nebula",
    "version": "1.10.3",
diff --git a/docs/state/build-state-kde.json b/docs/state/build-state-kde.json
index 2e40b9ca..f78bf2b5 100644
--- a/docs/state/build-state-kde.json
+++ b/docs/state/build-state-kde.json
@@ -1,14 +1,14 @@
 {
  "schema": "hammer-build-state/1",
  "totals": {
-  "recipes": 998,
-  "nodes": 999,
-  "sealed": 998,
+  "recipes": 999,
+  "nodes": 1000,
+  "sealed": 999,
   "ajeno": 1
  },
  "by_class": {
   "ajeno": 1,
-  "c": 363,
+  "c": 364,
   "go": 362,
   "gui": 38,
   "kernel": 6,
@@ -17,7 +17,7 @@
  "debt_by_class": {},
  "by_queue": {
   "corpus": {
-   "sealed": 801
+   "sealed": 802
   },
   "incoming-kde": {
    "sealed": 197,
@@ -36,9 +36,9 @@
    "sealed": 70
   },
   "escritorio-kde": {
-   "raices": 40,
-   "clausura": 259,
-   "sealed": 258,
+   "raices": 41,
+   "clausura": 260,
+   "sealed": 259,
    "ajeno": 1
   },
   "escritorio-mirada": {
@@ -969,7 +969,7 @@
     "escritorio-mirada",
     "escritorio-sway"
    ],
-   "dependientes_total": 43
+   "dependientes_total": 44
   },
   "biome": {
    "iname": "biome",
@@ -15077,6 +15077,28 @@
    ],
    "dependientes_total": 13
   },
+  "ncurses-shared": {
+   "iname": "ncurses-shared",
+   "version": "6.5",
+   "link": "dynamic",
+   "compiler": "zig-cc",
+   "deps": [
+    "binutils"
+   ],
+   "cls": "c",
+   "queue": "corpus",
+   "hash": "b3:05cc7afa98796a42298b95fa932996d9d34678b99cfa545c3e3918e5a2613593",
+   "state": "sealed",
+   "blocked_by": [],
+   "unblocks": 0,
+   "perfiles": [
+    "escritorio-cosmic",
+    "escritorio-gnome",
+    "escritorio-kde",
+    "escritorio-sway"
+   ],
+   "dependientes_total": 0
+  },
   "nebula": {
    "iname": "nebula",
    "version": "1.10.3",
diff --git a/docs/state/build-state-wlr.json b/docs/state/build-state-wlr.json
index 9f8b1aaa..98b96a19 100644
--- a/docs/state/build-state-wlr.json
+++ b/docs/state/build-state-wlr.json
@@ -1,14 +1,14 @@
 {
  "schema": "hammer-build-state/1",
  "totals": {
-  "recipes": 832,
-  "nodes": 833,
-  "sealed": 832,
+  "recipes": 833,
+  "nodes": 834,
+  "sealed": 833,
   "ajeno": 1
  },
  "by_class": {
   "ajeno": 1,
-  "c": 200,
+  "c": 201,
   "go": 362,
   "gui": 35,
   "kernel": 6,
@@ -17,7 +17,7 @@
  "debt_by_class": {},
  "by_queue": {
   "corpus": {
-   "sealed": 818,
+   "sealed": 819,
    "ajeno": 1
   },
   "incoming-wlr": {
@@ -41,9 +41,9 @@
    "sealed": 33
   },
   "escritorio-sway": {
-   "raices": 68,
-   "clausura": 171,
-   "sealed": 171
+   "raices": 69,
+   "clausura": 172,
+   "sealed": 172
   }
  },
  "sin_perfil": 644,
@@ -852,7 +852,7 @@
     "escritorio-mirada",
     "escritorio-sway"
    ],
-   "dependientes_total": 43
+   "dependientes_total": 44
   },
   "biome": {
    "iname": "biome",
@@ -4027,6 +4027,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 15
@@ -4147,6 +4148,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 17
@@ -8413,6 +8415,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 16
@@ -8551,6 +8554,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 23
@@ -10258,6 +10262,28 @@
    ],
    "dependientes_total": 13
   },
+  "ncurses-shared": {
+   "iname": "ncurses-shared",
+   "version": "6.5",
+   "link": "dynamic",
+   "compiler": "zig-cc",
+   "deps": [
+    "binutils"
+   ],
+   "cls": "c",
+   "queue": "corpus",
+   "hash": "b3:05cc7afa98796a42298b95fa932996d9d34678b99cfa545c3e3918e5a2613593",
+   "state": "sealed",
+   "blocked_by": [],
+   "unblocks": 0,
+   "perfiles": [
+    "escritorio-cosmic",
+    "escritorio-gnome",
+    "escritorio-kde",
+    "escritorio-sway"
+   ],
+   "dependientes_total": 0
+  },
   "nebula": {
    "iname": "nebula",
    "version": "1.10.3",
diff --git a/docs/state/build-state.json b/docs/state/build-state.json
index 06c0addd..da0d0232 100644
--- a/docs/state/build-state.json
+++ b/docs/state/build-state.json
@@ -1,14 +1,14 @@
 {
  "schema": "hammer-build-state/1",
  "totals": {
-  "recipes": 819,
-  "nodes": 820,
-  "sealed": 819,
+  "recipes": 820,
+  "nodes": 821,
+  "sealed": 820,
   "ajeno": 1
  },
  "by_class": {
   "ajeno": 1,
-  "c": 187,
+  "c": 188,
   "go": 362,
   "gui": 35,
   "kernel": 6,
@@ -17,7 +17,7 @@
  "debt_by_class": {},
  "by_queue": {
   "corpus": {
-   "sealed": 819,
+   "sealed": 820,
    "ajeno": 1
   }
  },
@@ -844,7 +844,7 @@
     "escritorio-mirada",
     "escritorio-sway"
    ],
-   "dependientes_total": 43
+   "dependientes_total": 44
   },
   "biome": {
    "iname": "biome",
@@ -4019,6 +4019,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 15
@@ -4139,6 +4140,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 17
@@ -8294,6 +8296,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 16
@@ -8432,6 +8435,7 @@
    "perfiles": [
     "escritorio-cosmic",
     "escritorio-gnome",
+    "escritorio-kde",
     "escritorio-sway"
    ],
    "dependientes_total": 23
@@ -10139,6 +10143,28 @@
    ],
    "dependientes_total": 13
   },
+  "ncurses-shared": {
+   "iname": "ncurses-shared",
+   "version": "6.5",
+   "link": "dynamic",
+   "compiler": "zig-cc",
+   "deps": [
+    "binutils"
+   ],
+   "cls": "c",
+   "queue": "corpus",
+   "hash": "b3:05cc7afa98796a42298b95fa932996d9d34678b99cfa545c3e3918e5a2613593",
+   "state": "sealed",
+   "blocked_by": [],
+   "unblocks": 0,
+   "perfiles": [
+    "escritorio-cosmic",
+    "escritorio-gnome",
+    "escritorio-kde",
+    "escritorio-sway"
+   ],
+   "dependientes_total": 0
+  },
   "nebula": {
    "iname": "nebula",
    "version": "1.10.3",
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 1292b996..2fa176f6 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -145,7 +145,17 @@ paquetes = [
   # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
   # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
   # partes ⇒ un solo artefacto, cero rebuilds).
-  "expat-shared", "libffi-shared", "bzip2-shared",
+  # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
+  # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
+  # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
+  # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
+  # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
+  # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
+  # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
+  # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
+  # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
+  # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
+  "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
 ]
 
 [perfil.escritorio-gnome]
@@ -268,7 +278,17 @@ paquetes = [
   # a todo lo que la pide por nombre, empezando por foot. Se enchufa por fontconfig: su
   # `59-nerd-monospace.conf` hace que el alias `monospace` la prefiera. Ver su receta.
   "dejavu-fonts-nerd",
-  "expat-shared", "libffi-shared", "bzip2-shared",
+  # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
+  # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
+  # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
+  # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
+  # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
+  # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
+  # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
+  # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
+  # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
+  # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
+  "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
 ]
 
 [perfil.escritorio-cosmic]
@@ -395,7 +415,17 @@ paquetes = [
   # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
   # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
   # partes ⇒ un solo artefacto, cero rebuilds).
-  "expat-shared", "libffi-shared", "bzip2-shared",
+  # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
+  # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
+  # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
+  # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
+  # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
+  # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
+  # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
+  # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
+  # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
+  # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
+  "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
 ]
 
 [perfil.escritorio-sway]
@@ -458,7 +488,17 @@ paquetes = [
   # visor: el plugin de PDF no hacía dlopen («Error relocating /usr/lib/libbz2.so.1»). La receta
   # existía sólo en `incoming-kde`; promovida al corpus con el mismo hash (b3:1e404254 desde las dos
   # partes ⇒ un solo artefacto, cero rebuilds).
-  "zlib-shared", "expat-shared", "libffi-shared", "bzip2-shared",
+  # `ncurses-shared` se suma el 2026-09-03 por el ÚLTIMO soname sin proveedor que quedaba pedido por
+  # un componente de RUNTIME: `pw-top` (pipewire) sale con `NEEDED libncursesw.so.6` y el `ncurses`
+  # canónico del corpus es `--without-shared`. La receta de pipewire AFIRMA que quitando la dep
+  # «meson saltea pw-top» — es falso y llevaba así desde agosto: el guardián de upstream es
+  # `if ncurses_dep.found()` y meson lo encontró en el sysroot Alpine DEL LAB. Reproducido: sin esto
+  # el binario muere en el loader con `__vfprintf_chk: symbol not found` (símbolo de
+  # _FORTIFY_SOURCE de glibc que musl no tiene); con esto carga y corre.
+  # ⚠ Esto tapa el SÍNTOMA en runtime, no la causa: pipewire sigue enlazando contra el lab en BUILD.
+  # Arreglarlo de verdad pide re-sellar pipewire, que arrastra 7 artefactos —entre ellos dos Rust
+  # que ya murieron por OOM— así que se paga el día que pipewire se re-selle por otro motivo.
+  "zlib-shared", "expat-shared", "libffi-shared", "bzip2-shared", "ncurses-shared",
   "swaybg",        # fondo de escritorio
   "swaylock",      # bloqueo de pantalla (con PAM: linux-pam SÍ está en el corpus)
   "swayidle",      # apagado por inactividad
diff --git a/recipes/ncurses-shared.toml b/recipes/ncurses-shared.toml
new file mode 100644
index 00000000..a1f15188
--- /dev/null
+++ b/recipes/ncurses-shared.toml
@@ -0,0 +1,99 @@
+# ncurses-shared 6.5 — la MISMA ncurses de `recipes/ncurses.toml` pero `.so`. Existe por una fuga
+# concreta, y conviene contar cuál porque el modo de fallar es de manual.
+#
+# ══ POR QUÉ HACE FALTA: `pw-top` ENLAZA CONTRA EL LAB DESDE AGOSTO ═════════════════════════════
+# `recipes/pipewire.toml:85-88` dice, textualmente, que quitando la dep de ncurses «meson saltea
+# pw-top y el resto construye». **Es falso, y llevaba así desde al menos el 2026-08-29.** Quitar la
+# dep no desactivó nada: el guardián de upstream es `if ncurses_dep.found()`, y meson lo encontró
+# igual — en el **sysroot Alpine DEL LAB**. Resultado: `pw-top` se construye, se sella, y sale con
+#
+#     NEEDED libncursesw.so.6
+#
+# que **ningún artefacto del cierre publica**, porque la ncurses del catálogo es `--without-shared`.
+# O sea una herramienta rota dentro de las CUATRO imágenes, invisible para el store porque el lab no
+# entra en `hash_inputs`. Lo reporta `scripts/vigia-sonames.py` en los cuatro perfiles.
+#
+# **La lección, que es más grande que pw-top: quitar una dep de `[deps]` NO apaga la función.**
+# Cambia de dónde sale. Para apagarla de verdad hay que decírselo a la perilla del proyecto; si no
+# la hay, la dep tiene que estar declarada y satisfecha desde el store.
+#
+# ══ POR QUÉ ESTA RECETA Y NO RE-SELLAR pipewire ════════════════════════════════════════════════
+# El arreglo LIMPIO es declarar la dep en pipewire y reconstruir. Cuesta: `yupana radio pipewire` da
+# **10 dependientes directos y 7 sellados que caen a deuda**, y entre ellos están
+# `xdg-desktop-portal-cosmic` y `cosmic-settings-daemon`, que son Rust —el portal murió por OOM tres
+# veces y el que selló tardó 48 min con un pico de 5,4 GiB de swap—. No se paga eso hoy por una
+# herramienta de diagnóstico, y menos con otro agente compilando en la misma máquina.
+#
+# Lo que cruza acá es un **SONAME**, y para eso el repo ya tiene regla escrita:
+#
+#   > Enlazar contra una variante y CORRER contra otra es legítimo cuando lo que cruza es un SONAME
+#   > (misma ABI). Lo que no se puede es hidratar dos artefactos distintos en la misma ruta.
+#
+# `libncursesw.so.6` es la misma ABI en 6.5 que en la 6.x de Alpine, y pw-top usa un puñado de
+# llamadas de curses. Poniendo esta receta de raíz en los perfiles, la imagen provee el SONAME y
+# pw-top corre. Cero rebuilds.
+#
+# ⚠ **QUEDA DEUDA, y está anotada a propósito:** pipewire sigue enlazando contra el lab en BUILD.
+# El día que pipewire se re-selle por cualquier otro motivo, hay que declarar `ncurses-shared` en
+# sus `[deps]` y corregir el comentario mentiroso. Esto tapa el síntoma en runtime, no la causa.
+#
+# ══ QUÉ CAMBIA RESPECTO DE LA ESTÁTICA ═════════════════════════════════════════════════════════
+# `--with-shared --without-normal` en vez de `--without-shared --with-normal`. TODO lo demás se deja
+# idéntico —widec, los mismos `--with-fallbacks`, sin ada/tests/debug— para que el terminfo empotrado
+# sea el mismo: `install.libs` no instala la base de terminfo, así que lo único que hace funcionar a
+# un binario curses sin `/usr/share/terminfo` son esos fallbacks compilados adentro.
+# No hay colisión con la estática: los ficheros son `libncursesw.so*` frente a `libncursesw.a`.
+# El `.pc` SÍ se llama igual en las dos ⇒ **no declarar las dos en la misma receta**.
+#
+# ══ ⚠ EL `sed` DEL `-stats`, QUE ES POR QUÉ ESTA RECETA NO ES DE TRES LÍNEAS ════════════════════
+# La regla de link compartido que genera el `configure` de ncurses es, literalmente:
+#
+#     -Wl,-soname,`basename $@ .6.5`.6,-stats,-lc
+#
+# y `-stats` es de GNU ld: el lld que trae zig lo rechaza con `error: unsupported linker arg:
+# -stats` y la `.so` no se liga. No se puede pasar por `LDFLAGS` porque no es un argumento suelto,
+# viene DENTRO del token `-Wl,...` que el propio Makefile arma, y `MK_SHARED_LIB` no es una perilla
+# de `configure`. Tampoco sirve el patrón `.zwrap` de poppler tal cual, que filtra argumentos
+# COMPLETOS y acá hay que reescribir uno.
+# Y no es sólo `-stats`: quitándolo, el siguiente en caer es el `-lc` del mismo token
+# (`error: unsupported linker arg: -lc`). El driver de zig valida lo que va dentro de `-Wl,` y no
+# deja pasar ninguno de los dos; libc la liga él solo, así que la cola `,-stats,-lc` se va ENTERA.
+# Se quita del Makefile GENERADO, después de configure: es una sustitución exacta, acotada al
+# fichero que la produce, y no toca el árbol de fuentes (no hay `.patch` que mantener contra
+# upstream). El `grep` de antes es la parte que importa: si algún día el `configure` deja de
+# emitirlo o lo emite distinto, esto falla en vez de seguir en silencio.
+name    = "ncurses-shared"
+version = "6.5"
+license = "MIT"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/ncurses/ncurses-6.5.tar.gz"
+sha256  = "136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "dynamic"
+
+[build.phases]
+configure = '''
+set -e
+./configure --prefix=/usr --with-shared --without-normal --enable-widec --without-debug --without-ada --without-tests --enable-pc-files --with-pkg-config-libdir=/usr/lib/pkgconfig --with-fallbacks=linux,vt100,vt220,xterm,xterm-256color,screen,tmux,tmux-256color,ansi
+# Ver la cabecera: la cola `,-stats,-lc` del token `-Wl,...` la rechaza el driver de zig entera.
+grep -q ',-stats,-lc' ncurses/Makefile || { echo "ERROR: no aparece ',-stats,-lc' — ¿cambió el configure de ncurses?" >&2; exit 1; }
+find . -name Makefile -exec sed -i 's/,-stats,-lc//g' {} +
+'''
+compile   = "make"
+install   = '''
+set -e
+make install.libs install.includes DESTDIR=/out
+# La receta EXISTE para publicar este SONAME: si no está, el artefacto no sirve para nada y hay que
+# enterarse acá, no en la imagen.
+test -e /out/usr/lib/libncursesw.so.6 || {
+  echo "ERROR: no se generó libncursesw.so.6 — ¿--with-shared no tomó?" >&2
+  ls -l /out/usr/lib >&2; exit 1
+}
+'''
+
+[deps]
+build = ["binutils"]