Commit Graph
55 Commits
Author SHA1 Message Date
SergioandClaude Opus 5 9a31857eaa targets: zstd en base — un takana recién instalado no podía desempacar su propio laboratorio
La distro comprime TODO con zstd: la imagen del lab (`lab-image.tar.zst`), el respaldo al Storage
Box, el `dd` remoto de una instalación. Y la imagen no traía el binario.

Medido sembrando el lab en la caja de producción: el `tar` del rootfs es el de busybox y contesta
`tar: unrecognized option: zstd`, así que hubo que extraer por tubería desde el hub
(`zstd -dc … | ssh caja 'tar -xf -'`). Un hub que se instala solo no puede depender de que otro hub
le descomprima las cosas.

La receta ya existía y está sellada (`b3:1ceb6215…`): sólo faltaba declararla.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 02:04:55 +00:00
SergioandClaude Opus 5 7e41ffd0e6 SDD 28 §6.5: el muro derribado — de 23 binarios inertes a 1, y sin mover un ArtifactHash
Elegidas las salidas 1 y 2 del §6.4; hecha la 1, que resultó más barata de lo que parecía y desbloquea
la 2 en vez de competir con ella.

`musl-shared` publica `/usr/lib/libc.so` + `/lib/ld-musl-x86_64.so.1`. Como variante y no tocando la
canónica: `musl` es Stage 1 y su `of_tree` es el baseline del selfhost. Control: `takana hash
recipes/musl.toml` sigue en `b3:ce952f72…`. Y `musl` no es dep de nadie ⇒ cero re-hasheo del corpus.

En la caja: `sh: python3: not found` → `Python 3.12.10`. De 23 inertes quedó **1**, `sqlite3`, que
pedía `libz.so.1`; `zlib-shared` ya estaba sellado y sólo faltaba declararla en `base`.

La salida 2 (python/perl estáticos) sigue viva pero deja de ser urgente: con el cargador publicado el
sistema ya no cuelga del rootfs del lab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 02:00:51 +00:00
SergioandClaude Opus 5 7e86459738 musl-shared: el corpus publica su propio CARGADOR — 23 binarios dejan de ser inertes
La imagen del perfil servidor traía 23 binarios de 726 que no podían ejecutarse:

    $ python3 -c "print(1)"
    sh: python3: not found        <- y `command -v python3` decía /usr/bin/python3

No faltaban: eran ELF dinámicos pidiendo `/lib/ld-musl-x86_64.so.1`, y **ningún artefacto del corpus
publicaba ese fichero**. Funcionaban en el hub sólo porque el rootfs Alpine del LAB lo presta — la
misma fuga que documentan `zlib-shared` y `expat-shared`, un piso más abajo, y peor: el lab no entra
en `hash_inputs`, así que la dependencia era invisible para el store.

Los 23 son la suite binutils entera + perl, python3, sqlite3, flex y nft. Entre ellos, TODO el
instrumental del proyecto, que es Python.

**Variante y no `--enable-shared` en la canónica**: `musl` es componente de Stage 1 y su `of_tree` es
el baseline de `selfhost-verify`. Re-hashearlo obliga a rehacer ese baseline a propósito, y eso es su
propia unidad de trabajo. Con el patrón `*-shared` (ya hay 23 en el corpus) la canónica no se mueve —
control: `takana hash recipes/musl.toml` sigue dando `b3:ce952f72…`, el mismo sellado de Stage 1. Y
además `musl` no es dep de NADIE (medido: sólo de sí misma; el enlace estático lo resuelve el musl de
zig), así que esto suma sin mover un solo ArtifactHash del corpus.

**⚠ `compiler = "gcc"` y no `zig-cc`, medido.** Con zig cc el `libc.so` sale con 1586 símbolos
dinámicos contra los 1654 de Alpine, y los 68 que faltan son EXACTAMENTE los que musl implementa en
ensamblador x86_64 (`memset memcpy memmove memcmp strlen` y toda la familia matemática) más los
`__stack_chk_*`. No es que no se compilen —en el `libc.a` canónico están—: zig los resuelve con su
propio musl y los deja `FUNC LOCAL HIDDEN` de tamaño 0, fuera de la tabla dinámica. El síntoma es un
cargador que arranca, reloca y muere con `memset: symbol not found`, que se lee como "el binario está
roto" y no como "a la libc le faltan símbolos". Con gcc: **1651 símbolos**, `memset` y `ceil`
presentes, y `python3 3.12.10` CORRE con el cargador del corpus.

De paso: el comentario de `musl.toml` decía que el artefacto aportaba «el loader, para uso dinámico
futuro». Era falso —`--disable-shared` no construye ninguno— y es justo la etiqueta que hizo que
nadie buscara el agujero. Corregido (los comentarios no entran en `hash_inputs`; verificado).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-11 01:47:54 +00:00
SergioandClaude Opus 5 2613fe3951 servidor-image.sh: la imagen del perfil servidor, armada por script — y netup pasa a ser raíz
El ensamblado dejaba de ser reproducible en cuanto se cerraba la terminal: cuatro pasos, tres de
ellos con una trampa que no se ve. Ahora es un script, con las trampas escritas en su cabecera:

1. **El kernel.** `linux.toml` NO sirve para hcloud (apaga SCSI y el disco de Hetzner es virtio-SCSI).
   Va `linux-generic`, que sí trae `CONFIG_SCSI_VIRTIO=y` — dato que no está en la receta, lo pone
   `make defconfig`, y sólo se ve en el `.config` que el artefacto publica.
2. **El init se pisa.** La clausura del perfil trae busybox y su `/sbin/init` gana por hidratarse
   después. El script lo restaura e IMPRIME la reparación (`../bin/busybox → /usr/bin/arje-zero`),
   que es la diferencia entre arreglarlo y creer que estaba bien.
3. **EXDEV.** Comprueba con `findmnt` que STORE y WORKDIR estén en el MISMO montaje y aborta con un
   mensaje que dice qué hacer, en vez de fallar fichero por fichero a mitad de un `cp -al`.
4. **La clave es obligatoria.** Sin `AUTHKEYS` no arma nada: una instalación remota sin
   `authorized_keys` deja una máquina viva e INALCANZABLE, que es peor que una que no arrancó.

Y el cmdline va SIN `init=`, a propósito: así corre el wrapper que escribe `install-image.sh`, que
es quien monta `/store` y `/var/lib/hammer`.

**`netup` entra como raíz de `perfil.servidor`** aunque su binario ya venga dentro del
`product-rootfs`. La razón es concreta y se pagó hoy: el del producto está congelado en el artefacto
sellado del bootstrap, así que el arreglo de la ruta on-link NO llegaba a la imagen. Declarado como
raíz, la hidratación lo proyecta encima y la imagen lleva el vigente — verificado por sha256: la
imagen trae `a23df20e…` y el product-rootfs `a84b0a0d…`.

`recipes/netup.toml` re-pineado a `be383ea4` (el commit del arreglo).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-10 20:15:39 +00:00
SergioandClaude Opus 5 7b38e2ef16 kde: los portales — segundo hueco cerrado, y OBS ya tiene con quién hablar
Medido recorriendo los `.service` de D-Bus de TODO el store: los únicos backends de portal sellados
eran los de COSMIC y GNOME. Ninguno de KDE, y la cola `incoming-kde` no tenía ni el frontend.
Consecuencia concreta: la imagen KDE trae `obs-studio` con su plugin linux-pipewire, que captura
pantalla POR EL PORTAL — o sea que en Plasma no tenía con quién hablar mientras en los otros dos
escritorios ese frente estaba cerrado. Sin portal tampoco hay diálogo de fichero ni compartir
pantalla para nada que los use.

Van los DOS paquetes porque la cadena es de tres:
  cliente → org.freedesktop.portal.Desktop [frontend] → org.freedesktop.impl.portal.* [backend]
El frontend se copia de la cola de COSMIC y **comparte artefacto**: hash idéntico (ecbe13f68eca6)
desde las dos colas ⇒ un solo directorio en el store, cero builds.

El backend es receta NUEVA, 6.7.2 — la MISMA versión que plasma-workspace, kwin y kscreenlocker de
esta cola, porque habla interfaces privadas de kwin para el ScreenCast y desalinear ahí es pedir que
dos mitades del mismo release se entiendan por casualidad. **Cero frontera nueva**: sus 22
dependencias (Qt6, KF6, KWayland, protocolos de Wayland, xkbcommon) ya estaban todas, verificadas
una por una antes de escribir la receta.

⚠ SIN EL PORTAL DE IMPRESIÓN, y la causa es más honda que el error. El build moría en
`src/print.cpp:27` con `'QtPrintSupport/private/qcups_p.h' file not found`, y la cadena está medida
entera: no hay NINGUNA receta de cups en el catálogo ⇒ qtbase 6.11.1 se construyó sin CUPS ⇒ su
`QtPrintSupport/private/` trae qpaintengine_alpha_p.h y qprintengine_pdf_p.h pero NO qcups_p.h.
No falta una cabecera: falta el subsistema. Un portal de impresión sin con qué imprimir no es
funcionalidad que se pierde, es código muerto que no enlaza.

Upstream no expone `option()` para portales —se compilan todos—, así que el parche va en TRES
sitios y no en uno: la fuente, la instanciación (desktopportal.cpp/.h) y **la lista de interfaces
que el backend ANUNCIA**. El tercero es el que se olvidaría y el que importa: anunciar
`impl.portal.Print` sin implementarlo hace que el frontend enrute a un portal que no contesta, que
es peor que no tenerlo. Cada `sed` lleva su `grep` de verificación al lado, para que el día que
upstream mueva esas líneas el parche falle RUIDOSO en vez de volverse inerte.

VERIFICADO sobre el artefacto sellado: publica `org.freedesktop.impl.portal.desktop.kde.service`,
y su `kde.portal` anuncia 17 interfaces con **0 apariciones de Print** e incluyendo ScreenCast,
Screenshot y RemoteDesktop, que es exactamente lo que OBS necesita. Y sobre el rootfs hidratado:
están el frontend (`org.freedesktop.portal.Desktop.service`), el backend y el `kde.portal`.

⚠ Y la licencia casi la firmo mal: leí la cabecera de UN fichero y generalicé. Contadas las
etiquetas SPDX del árbol entero: 88 `LGPL-2.0-or-later`, 23 de la fórmula de KDE e.V. y 1
`GPL-2.0-or-later`. El binario las enlaza a todas ⇒ la expresión es la CONJUNCIÓN.

El perfil pasa a 98 raíces, 361/361 listo, deuda 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 23:17:42 +00:00
SergioandClaude Opus 5 c599daa346 SDD 28: el servidor de producción — y perfil.servidor declarando la deuda que no se ve
El frente arranca con el inventario MEDIDO de gioser, no con un plan: PID 1 es `arje-zero` (nuestro
init ya gobierna un servidor real, aunque sobre userland Artix), Hetzner Cloud arranca por **BIOS**
—que es justo el layout que `takana-install.sh` ya escribe— y el kernel propio trae VIRTIO/EXT4 `=y`
con `netup` haciendo DHCPv4, que es exactamente el caso de una caja hcloud.

Dos hallazgos que cambian el trabajo:

- **La secuencia de arranque de gioser no está escrita en ningún lado.** `rc-status` dice `stopped`
  para caddy, gitea, sshd, cronie y act-runner y los cinco están VIVOS colgando de PID 1. Un
  `arje-absorb --from openrc` produciría una Semilla perfecta que no levanta el servidor.
- **La mitad del Caddyfile describe un servidor que ya no existe**: 4 de 19 dominios sin DNS y sin
  ficheros en `/var/www`, uno en 502. Una mudanza fiel copia también la basura ⇒ la regla queda
  escrita: no se muda lo que no responde.

`[perfil.servidor]` hereda `cli` (SDD 27 §7.1) y declara 5 raíces que existen (openssh, caddy, curl,
wget, tmux) y **5 que NO** (takana, chrony, nftables, cronie, logrotate). Quedan `wanted`, y `wanted`
no es `debt` ⇒ `drenaje.json` seguirá diciendo `deuda=0`. Se declaran igual: sin ellas el perfil
saldría N/N —completo y verde— describiendo un servidor sin hora, sin cortafuegos y sin latido. Es
la lección de `foot` en escritorio-sway, pagada por adelantado. Cuenta esperada: 5, verificada
resolviendo cada nombre contra el campo `name` de las 869 recetas.

La peor de esas cinco: **takana no tiene receta**. El corpus construye 869 y no la suya, así que el
host no puede instalar takana desde el repo de takana — el bloqueante de que el servidor se sirva a
sí mismo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-09 22:53:55 +00:00
SergioandClaude Opus 5 c20372597a targets: kde/gnome/cosmic heredan cli — las imágenes ya no salen sin shell ni red
Paso 1 del SDD 27. Resultó ser UNA LÍNEA POR PERFIL: el mecanismo `hereda` ya existía en
`scripts/targets.py` —transitivo, con detección de ciclos y orden preservado— y `escritorio-sway`
ya lo usaba desde el 2026-08-07. Los otros tres nunca lo declararon, y eso no era una decisión: era
composición no declarada.

LO QUE ARREGLA, medido antes: de los 27 paquetes del perfil `base`, la clausura de escritorio-kde
alcanzaba DOS. Contra el rootfs real: sin `bash`, sin `sudo`/`doas`, sin `git`, sin `useradd`, sin
`gpg` y **sin `dhcpcd`** —o sea sin con qué pedir una IP—; y lo que parecía estar (`ip`, `mount`,
`fsck`) eran applets de busybox (`/sbin/ip → ../bin/busybox`).

DESPUÉS, verificado hidratando de verdad y no leyendo el grafo: en el rootfs aparecen `bash`,
`sudo`, `doas`, `git`, `dhcpcd`, `useradd`, `gpg`, `rg`, `fd`, `bat`, y `/sbin/ip` pasa a ser el
BINARIO de iproute2 en vez del applet.

  raíces  kde 49→96 · gnome 39→86 · cosmic 43→88
  nodos   kde 301→356 · gnome 204→256 · cosmic 162→216
  deuda   0 en los tres — no hubo que construir NADA, ya estaba todo sellado
  colisiones nuevas al hidratar: CERO (los 28 .hammer-tmp son los de gmp/mpfr de antes)

⚠ `escritorio-mirada` NO hereda a propósito: es el rootfs *slim* del USB y sumarle 47 raíces
contradice su razón de ser. Si algún día se quiere, es la misma línea.

`kde-rootfs` en el volumen se rehidrató con la herencia (11 G) para que el nombre canónico no quede
viejo — que es el error que este mismo día costó encontrar en GNOME y COSMIC. El rootfs FUNDIDO y la
imagen siguen siendo los de antes: rehacerlos es un paso aparte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 22:31:12 +00:00
SergioandClaude Opus 5 542d9875fb kde: upower — la imagen declaraba powerdevil y no tenía batería
Primero de los dos huecos que dejaban a KDE POR DEBAJO de una instalación estándar. El perfil
declaraba `powerdevil` —el gestor de energía de Plasma— pero no a `upower`, que es con quien
powerdevil habla para leer la batería y aplicar perfiles. Medido: upower sólo existía en la cola de
GNOME, y las colas no se ven entre sí (HERMANO→corpus, nunca de reojo). En un portátil eso es un
escritorio sin indicador de batería, y el arranque no falla ⇒ no se ve hasta usarlo.

Cuesta TRES recetas y no siete, y la diferencia es una perilla: `-Dintrospection=disabled`.
GNOME la necesita ENCENDIDA porque su shell hace `imports.gi.UPowerGlib` desde JavaScript, o sea que
carga el typelib. powerdevil NO: habla D-Bus con `org.freedesktop.UPower`. Apagarla saca de la
frontera gobject-introspection, gi-foreign-girs, glib-introspected y py3-setuptools, que sólo viven
en la cola de GNOME.

⚠ Y NO SE PROMUEVEN AL CORPUS, aunque era lo primero que intenté. Al mover `libgudev` allá su
ArtifactHash CAMBIÓ (7404bc8d → 87ca5e73) y eso no es una mudanza: `glib` existe en las DOS colas y
son recetas distintas, así que desde `incoming-gnome/` se construye contra la glib de GNOME y desde
el corpus contra la del corpus. Promoverla habría arrastrado a GNOME a la glib del corpus — el
cuadro de las «dos glib» que este repo ya pagó caro. Revertido y verificado que vuelve al hash de
antes (7404bc8d), o sea GNOME intacto. Se COPIAN a la cola de KDE.
`udev-pc` sí comparte artefacto (sus deps son sólo pkgconf ⇒ mismo hash desde las dos colas, un
solo directorio en el store). `libgudev` es otro a propósito, por la glib.

VERIFICADO sobre el artefacto sellado, no supuesto: trae `usr/libexec/upowerd`, el
`org.freedesktop.UPower.service` de system-services y su política en `system.d` — que es exactamente
lo que powerdevil necesita para que el demonio arranque por activación D-Bus.

El perfil pasa a 49 raíces, 301/301 listo, deuda 0.

Queda el segundo hueco: KDE no tiene backend de portal (ni `xdg-desktop-portal` en su cola), o sea
que no hay compartir pantalla — y en particular la captura de obs-studio, que la imagen ya trae, no
tiene con quién hablar en Plasma. Eso es más caro: pide traer el portal a la cola y ESCRIBIR
`xdg-desktop-portal-kde`, que no existe como receta en ninguna cola.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 22:02:49 +00:00
SergioandClaude Opus 5 e1d58cc583 cosmic: hidratar desde el perfil — su lista a mano se saltaba 17 declarados
Misma divergencia que se encontró ayer en GNOME, medida igual: `hydrate-cosmic.sh` nombra 25 raíces
escritas a mano dentro del script, el perfil `escritorio-cosmic` declara 43, y **17 paquetes
declarados no llegaban al rootfs**:

  adwaita-cursors · xkeyboard-config · bash · atuq (el navegador) · mpv · swayimg
  zathura + zathura-pdf-poppler · dejavu-fonts y dejavu-fonts-nerd · libnotify
  desktop-file-utils · hicolor-icon-theme · y las cinco *-shared de runtime

⚠ EL PRIMERO NO ES UNA COINCIDENCIA: `adwaita-cursors` es EXACTAMENTE el ejemplo con el que la
cabecera de `scripts/hydrate-profile.py` advirtió de esto el 2026-09-04 — «se añadió a las raíces de
cosmic y sway, y los scripts de hidratación, que no la conocen, habrían seguido armando un rootfs
sin cursores». No era hipotético: seguía pasando hoy, cinco días después. Y `xkeyboard-config` es la
cicatriz de sway repitiéndose: sin él el compositor arranca sin mapa de teclado.

Y AL REVÉS NO SE PIERDE NADA, que es lo que hace el cambio barato y lo separa del caso GNOME (donde
primero hubo que mover 11 raíces de runtime al perfil): de las 25 del script, la única que el perfil
no declara es `zlib-shared`, y la clausura del perfil la alcanza igual. O sea que el motivo que
justificaba la lista a mano —los componentes que `cosmic-session` lanza por PATH, que ningún grafo
de build ve— YA ESTÁ RESUELTO en el perfil, que los declara todos como raíces. La lista quedó
redundante sin que nadie lo notara.

VERIFICADO HIDRATANDO, no leyendo: `hydrate-profile.py escritorio-cosmic` proyecta 162/162 nodos
(24166 ficheros, 4,8 G) y ahí están los 17. (Ojo con la sonda: `bash` instala en `/bin/bash`, no en
`/usr/bin` — mi primer chequeo lo dio por ausente y era el chequeo el que miraba mal.)

⚠ Y UN DATO QUE MATIZA EL DIAGNÓSTICO Y LO EMPEORA. El rootfs que hay en disco
(`escritorios/cosmic-rootfs`, 3,9 G) SÍ tiene cursores, xkb y mpv — o sea que no se armó con la
lista por defecto, alguien le pasó raíces a mano. Y aun así le falta `atuq`. El problema real no es
que la lista esté mal: es que **lo que llega a la imagen depende de lo que alguien se acuerde de
teclear**. Derivarlo del perfil quita esa variable.

El script se deja porque su cabecera documenta por qué COSMIC necesita raíces de runtime explícitas
—sigue siendo cierto sobre la naturaleza de cosmic-session—; lo que ya no es cierto es que su lista
sea la verdad. Los dos armadores de imagen (qemu y metal) ahora apuntan a `hydrate-profile.py`.

── Y de paso, una nota vieja de targets.toml corregida ────────────────────────────────────────────
Decía que el perfil de KDE «son 13 raíces de ARRANQUE EN METAL, no la imagen completa», con
konsole/kate/dolphin y otras 19 apps selladas y sin declarar. Ya no: hoy son 48 raíces, con esas
apps, las fuentes y las *-shared declaradas — el frente KDE lo reordenó y la nota quedó atrás.
De su cola quedan 8 recetas fuera del perfil y son las que corresponde: libICE, libSM, libXtst y los
cinco eslabones huérfanos de xwayland desde que se retiró su receta.
Lo que a KDE le queda es OTRO problema, también medido hoy: su imagen no se hidrata del perfil sino
de un rootfs YA ARMADO (8,4 G) que nadie re-deriva. De las 18 hojas del perfil están 15 — faltan
`obs-studio` y `atuq`, justo las dos añadidas después de esa hidratación. Re-hidratarlo es decisión
del frente KDE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 20:54:51 +00:00
SergioandClaude Opus 5 eb8f217245 gnome: el perfil y el hidratador decían cosas distintas — 21 paquetes no llegaban a la imagen
Verificando que las extensiones de la tanda anterior llegaran al rootfs apareció algo bastante peor
que lo que fui a buscar: **`scripts/gnome/hydrate-gnome.sh` no lee `targets.toml`**. Arma el rootfs
desde una lista de 14 raíces escrita a mano dentro del propio script, y de las 28 raíces que el
perfil declaraba sólo DOS coincidían.

MEDIDO recorriendo la clausura de esa lista (123 recetas) contra la membresía del perfil: **21
paquetes declarados en `escritorio-gnome` no llegaban al rootfs hidratado**. No son accesorios —
son las hojas, que es justo lo que un perfil declara porque no es dep de build de nadie:

  atuq (el navegador) · foot (la terminal) · helix (el editor) · mpv · swayimg (el visor)
  zathura + zathura-pdf-poppler (el lector de PDF) · dejavu-fonts y dejavu-fonts-nerd (las FUENTES)
  xdg-desktop-portal-gnome · libnotify · desktop-file-utils
  bzip2-shared, expat-shared, libffi-shared, ncurses-shared, xz-shared (las .so que se cazaron el
  2026-09-03 recorriendo NEEDED, justamente para que el rootfs no resolviera contra el lab)
  y las cuatro de ayer: las tres extensiones y gnome-tweaks

O sea que el trabajo de descubrir que la imagen no tenía terminal, ni editor, ni una sola fuente
—con su análisis largo escrito en este mismo fichero— estaba DECLARADO y no llegaba.

Y la divergencia iba en los dos sentidos: 11 raíces de RUNTIME vivían sólo dentro del script y el
perfil no las conocía (accountsservice, geoclue, libgdm, libgweather, upower, librsvg, ibus, los dos
gi-*-typelibs, gsd-schemas y wireplumber). Sin ellas el shell no arranca: las carga por
`imports.gi.*`, que es invisible para la clausura de deps.

ARREGLO: las 11 se mueven a `targets.toml` con la justificación que el script ya traía bien escrita,
y el script queda marcado como SUPERADO por `scripts/hydrate-profile.py`, que hidrata desde el
perfil — la única declaración que existe. Su propia cabecera ya advertía de este modo de fallo
(«DOS fuentes de verdad para lo mismo, y divergen sin que nada lo diga»); esto es esa advertencia
cumpliéndose. El perfil pasa de 28 a 39 raíces y de 190 a 204 nodos, deuda 0.

VERIFICADO HIDRATANDO DE VERDAD, no leyendo: `hydrate-profile.py escritorio-gnome` proyecta
210/210 nodos (27993 ficheros, 3,5 G) y ahí están las tres extensiones, `gnome-tweaks` con su
módulo `gtweak`, y los tres gschemas en el directorio GLOBAL. Después se corrió el bloque nuevo del
lanzador contra ESE árbol: enumeró las tres, escribió el override y `glib-compile-schemas` salió 0
con stderr vacío.

⚠ DOS COSAS QUE APRENDÍ MIDIENDO Y CONVIENE QUE ESTÉN DICHAS:

1. **El hub NO puede hidratar GNOME hoy.** `gjs`, `gnome-shell` y `spidermonkey` salen «no sellado»
   localmente. No es deriva ni un grafo mintiendo: sus hashes vigentes SÍ están en
   `work/farm-sellados.txt` — o sea sellados EN EL WORKER — y la cosecha baja el manifiesto y no el
   store, a propósito (bajarlo deshacía la poda). `build-state` los cuenta bien porque mira también
   el manifiesto; `hydrate-profile.py` mira sólo el disco local. Los dos tienen razón.
2. **EXDEV entre bind-mounts, otra vez.** Hidratar a `work/` o a `work/out` falla con «Invalid
   cross-device link» AUNQUE `work/out` esté en el mismo /dev/sdb que el store: son bind-mounts
   distintos y `linkat()` no cruza montajes. Hay que hidratar accediendo al store por su montaje
   raíz (`--store /mnt/cosecha/store --into /mnt/cosecha/escritorios/…`). El mensaje de error del
   script lo dice y aun así me costó dos intentos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 19:56:19 +00:00
SergioandClaude Opus 5 4e4a2c8d2a gnome: dash-to-panel, appindicator y gnome-tweaks — y la lista de activas sale de las recetas
GNOME deja de ser la cáscara: tres extensiones y la app que el proyecto no instala y todo el mundo
termina instalando. El perfil queda 190/190 con 28 raíces, deuda 0.

  dash-to-panel v73   junta dash, ventanas y bandeja en una sola barra (lo que más cambia el uso)
  appindicator v64    devuelve los ICONOS DE BANDEJA que GNOME quitó; sin ella las apps que usan
                      StatusNotifierItem corren y no tienen dónde mostrarse
  blur-my-shell v72   (ya estaba; se le saca el override, ver abajo)
  gnome-tweaks 46.1   tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que
                      GNOME esconde, y el interruptor de las extensiones

⚠ LA LISTA DE ACTIVAS SE MUEVE A UN SOLO SITIO, y esto era una bomba de relojería. `enabled-extensions`
es un ARRAY: un override de GSettings lo escribe ENTERO. Con la primera extensión el override vivía
en su receta y funcionaba; con la segunda NO se habrían sumado — ganaría la que compile última
(orden alfabético) y las otras quedarían instaladas y MUERTAS, sin un solo error. Ahora la arma
`scripts/gnome/gnome-start-qemu.sh`, que ya compilaba los esquemas al arrancar, enumerando los uuid
presentes en /usr/share/gnome-shell/extensions. La política es «lo que la imagen instala, se
activa»; el dconf del usuario gana sobre el default.
Una receta NO puede ser ese sitio: hammer exige `[source]` en toda receta («missing field `source`»),
así que no hay forma de escribir una receta de pura política. Se probó el bloque contra un árbol de
juguete: lista las dos extensiones, salta un directorio sin metadata.json, el override compila con
stderr vacío y `gsettings get` devuelve las dos. Y si la lista cambia borra `gschemas.compiled` para
forzar recompilación — sin eso, una imagen ya arrancada ignoraría la extensión nueva en silencio.

⚠ EL HALLAZGO CARO: `msgfmt` de gettext-tiny REVIENTA con las formas plurales del árabe. SIGILL,
exit 132, «index 6 out of bounds for type 'size_t[6]'», y deja un .mo de 0 bytes. Reproducido FUERA
de la receta con el binario sellado. No es el idioma sino el mensaje: blur-my-shell compila su
ar.po sin problema porque no tiene NINGÚN plural (msgstr[5] = 0); el de tweaks tiene exactamente
uno. Acá se saca `ar` del LINGUAS y se pierde la traducción de UNA app, con el diagnóstico escrito
en la receta. ARREGLARLO ES OTRA UNIDAD DE TRABAJO: gettext-tiny la usan 108 recetas.

Cada receta usa el mecanismo de SU upstream, que no es el mismo en las tres:
  blur-my-shell   `make build` llama a `gnome-extensions pack` —el CLI que nuestro shell apaga— así
                  que se copia el árbol a mano, con el layout LEÍDO del zip de EGO y comparado
                  contra él (idéntico, sin sobras ni faltantes)
  dash-to-panel   su Makefile tiene un `install` que honra DESTDIR y hace lo de un paquete de
                  distro: gschema y locale a los directorios GLOBALES. ⚠ `VERSION=73` se pasa a
                  mano porque sin ella el Makefile hace `git describe` y el árbol viene por
                  `git archive`, sin `.git` ⇒ metadata quedaría con el marcador "version": 9999
  appindicator    meson. ⚠ `-Dlocal_install=disabled` explícito: en `auto` decide mirando si el uid
                  es 0, o sea que hoy acierta POR ACCIDENTE — y si el lab dejara de correr como
                  root instalaría en $HOME/.local y el artefacto sellaría vacío. Y `jq` va en deps
                  aunque sea JavaScript puro: su meson saca el uuid del metadata.json con jq

⚠ gnome-tweaks NO costó ninguna receta nueva, y eso hubo que medirlo: su meson declara siete deps
mínimas de runtime y las siete ya estaban selladas. La que parecía traer una cadena de Python
entera —`pygobject-3.0`— la provee `py3-gobject`, que YA ESTABA en el corpus con otro nombre y
publica `pygobject-3.0.pc`. El nombre no es el hecho, otra vez.
Sí costó dos deps que el configure reclamó y que son el patrón `.pc Requires` → `[deps].build`:
`appstream` (la pide `libadwaita-1.pc` y el error culpa a libadwaita, que está bien instalada) y
`desktop-file-utils` (meson la busca como PROGRAMA para `update-desktop-database`).
Sus deps de RUNTIME —python3 y py3-gobject— van declaradas: es una app de Python, si no están en la
imagen se instala y no arranca, y ningún build lo delata.

Las cuatro licencias, verificadas en la fuente pineada y NINGUNA leída del COPYING —cuyo apéndice
«How to Apply» trae siempre la frase «or any later version»—: blur-my-shell del README del commit,
dash-to-panel y appindicator de la cabecera de sus fuentes, gnome-tweaks de su etiqueta SPDX.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 19:41:52 +00:00
SergioandClaude Opus 5 8c2cb10414 gnome: la primera extensión del catálogo — blur-my-shell v72, instalada Y activa
La imagen de GNOME traía la CÁSCARA: el shell sabe cargar extensiones —`ui/extensionSystem.js` va
en el gresource sin opción que lo apague y su gschema sellado trae `enabled-extensions`— pero no
había ninguna que cargar. Esta es la primera, y fija el patrón para las otras ~1400.

⚠ NO HIZO FALTA TLS, que era la conclusión fácil del análisis de ayer. glib-networking sólo lo pide
BAJARLAS desde extensions.gnome.org con el descargador del shell. Empaquetada, una extensión se
instala como cualquier receta y no toca la red.

LA FUENTE ES EL REPO DEL AUTOR, NO EL ZIP DE EGO, por dos razones y las dos medidas:
  1. hammer no sabe abrir un zip: el fetch extrae con `tar -x` y GNU tar contesta «This does not
     look like a tar archive». Ninguna receta del corpus usa .zip.
  2. Y aunque lo abriera, el zip de EGO es un ARTEFACTO que construye EGO desde este mismo repo.
     Pinear el zip es pinear el binario de un tercero; pinear el commit es pinear el código.
El tag v72 es el que EGO sirve para shell 48 —preguntado a su API, no supuesto— y el metadata.json
declara ['46','47','48','49','50']. ⚠ Eso hay que preguntarlo por extensión: nixpkgs va por GNOME
50 y hay extensiones que ya sólo declaran el major nuevo.

SIN `gnome-extensions pack`, que es lo que usa el Makefile de upstream y es el CLI que nuestra
receta del shell apaga (-Dextensions_tool=false; encenderlo costaría gnome-autoar y re-hashear
gnome-shell entero). No hace falta: `pack` sólo arma un zip que después alguien descomprime, y lo
que el shell lee es un DIRECTORIO. El layout no se inventó — se leyó del zip de EGO v72, que es la
salida de ese mismo `pack`, y el artefacto sellado se comparó contra él: **entradas idénticas, ni
sobras ni faltantes**, 45 .mo compilados.

⚠ INSTALADA ≠ ACTIVA, y esto es lo que hace que la receta sirva. El shell sólo carga lo que esté en
`org.gnome.shell enabled-extensions`: sin eso el artefacto sella, el fichero está en la imagen y NO
PASA NADA al arrancar — el mismo cuadro de «sellado ≠ instalado» que este repo ya se comió con
foot, mpv y libnotify, un escalón más abajo. Se resuelve con un override de GSettings, que cambia
el DEFAULT y deja ganar al dconf del usuario que no la quiera.
PROBADO DE VERDAD, no deducido: se compiló el directorio de esquemas del shell con el override
puesto (`glib-compile-schemas`, exit 0 y stderr vacío — que hay que mirar, porque sale 0 aunque
RECHACE un esquema) y después `gsettings get org.gnome.shell enabled-extensions` devolvió
['blur-my-shell@aunetx'].

⚠ TRAMPA ANOTADA PARA LA SEGUNDA: `enabled-extensions` es un ARRAY y el override lo escribe ENTERO.
Dos recetas con su propio override no se suman — gana la que compile última y la otra queda
instalada y muerta, sin error. Cuando llegue la segunda, la lista pasa a un solo sitio.

Y una corrección sobre la licencia, que casi firmo mal: el LICENSE es el texto del GPLv3, y su
apéndice «How to Apply» CONTIENE la frase «either version 3 … or any later version» siempre, así
que grepearla ahí no distingue -only de -or-later (la trampa que la memoria del repo ya tenía
escrita). La evidencia buena es el README del commit pineado: «This program is distributed under
the terms of the GNU General Public License, version 3 or later». El árbol JS no lleva cabecera de
licencia en ningún fichero.

Va de RAÍZ en escritorio-gnome por la lección de `foot` que targets.toml ya aprendió tres veces.
El perfil queda 183/183 listo con 25 raíces, deuda 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
2026-09-09 19:14:08 +00:00
SergioandClaude Opus 5 248fa8e991 desktop-file-utils: cerrar el «abrir con»
update-desktop-database construye la caché MIME→APLICACIÓN (mimeinfo.cache). Comprobado antes
de escribir la receta: NINGÚN artefacto del store lo traía, así que esa caché no se construía
nunca. Con shared-mime-info (cerrado ayer) el escritorio sabe QUÉ TIPO es un fichero; sin ésta
seguía sin saber CON QUÉ abrirlo — el menú «abrir con» vacío y sin aplicación por defecto.

Misma familia que aquel hueco: falla en RUNTIME, no en build. gnome-shell, libadwaita y mutter
lo piden en nixpkgs y acá construyen tan contentos sin él, así que ninguna métrica de deuda
puede verlo.

Prueba del consumidor con 14 .desktop REALES de okular/dolphin/kate/konsole/spectacle:
  mimeinfo.cache de 2.368 bytes · 47 asociaciones
  application/pdf=okularApplication_pdf.desktop
  text/plain=okularApplication_txt.desktop;org.kde.kate.desktop;org.kde.kwrite.desktop
Los 4 binarios estáticos con 0 NEEDED (--prefer-static desde el principio, no tras el error).
REPRODUCE bit a bit · tarball en el mirror.

Va como RAÍZ en los CUATRO escritorios y no en , y la razón importa: escritorio-kde y
escritorio-gnome comparten CERO raíces con base y bash NO está en su clausura. escritorio-mirada
documenta que no hereda «a propósito, es slim»; esos dos no dicen nada, y sway sí hereda cli.
Declararla en los cuatro funciona con cualquiera de las dos lecturas. La asimetría en sí queda
para decidir aparte: hoy «KDE 302/302» significa «la capa de escritorio está completa», no «la
imagen está completa».

Triaje: huecos 2 → 1 (queda ). Clausuras 303/182/160/205, todas selladas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
2026-09-09 13:40:46 +00:00
SergioandClaude Opus 5 1da6851775 bash-completion: cerrar el hueco del tabulador
bash es RAÍZ del perfil base —el shell de la distro— y cinco recetas repartidas en cuatro colas
(appstream, colord, gnome-shell, modemmanager, swayimg) instalan completados en el directorio
que este paquete define. Sin él NO los instalan y siguen adelante: nada falla, nadie se entera,
y el usuario se queda sin tabulador en las herramientas del sistema. Hueco de RUNTIME puro, de
los que ninguna métrica de deuda puede ver — por eso hizo falta la frontera para encontrarlo.

Va al CORPUS y como raíz de , no a una cola: lo alcanzan las cuatro imágenes de escritorio
y la CLI, y una receta resuelve sibling-first y después el catálogo padre, nunca una cola
hermana. Y entra como RAÍZ porque nadie lo alcanza por deps de build: los consumidores sólo
preguntan por su .pc.

Verificado como consumidor, las dos mitades:
  pkgconf --variable=completionsdir bash-completion → /usr/share/bash-completion/completions
  el marco carga 131 completados y complete  devuelve regla · 522 ficheros instalados
  REPRODUCE bit a bit · tarball en el mirror

Triaje: los huecos vuelven a CERO. base pasa de 26 a 27 raíces, clausura 58/58 sellada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
2026-09-09 02:48:20 +00:00
SergioandClaude Opus 5 4f9ee5430f dunst: sway ya atiende las notificaciones, y hay un píxel que lo prueba
Era la única mitad que quedaba abierta del mapa del §6.10: KDE atiende
`org.freedesktop.Notifications` con plasma-workspace, GNOME con gnome-shell y
COSMIC con cosmic-notifications; sway no tenía a NADIE, así que una página que
pedía notificar mandaba el mensaje al bus y se lo comía el silencio.

`dunst` 1.12.2 y no `mako` —que es el de la casa wlroots— por dos razones
medidas: el nombre `mako` YA ESTÁ OCUPADO en el corpus por el motor de plantillas
de mesa, y el cierre del perfil incluye las herramientas de build, así que la
imagen terminaría con dos artefactos homónimos peleando por las mismas rutas; y
mako habla D-Bus por sd-bus, que en musl es una receta nueva (basu), mientras
dunst habla por GDBus, que ya está sellado. Cero dependencias nuevas.

DOS GUARDIANES EN LA PROPIA RECETA, porque las dos cosas que importan no se ven
en que compile:

- `X11=0` es una intención; el hecho se lee en el ELF. dunst compila los dos
  backends por defecto y su config.mk avisa: sin wayland «forzará xwayland». En
  esta distro eso sella un binario que arranca, toma el bus y NO PINTA NUNCA. La
  fase exige `libwayland-client` entre los NEEDED y prohíbe `libX11`.
- El `.service` de D-Bus es la mitad que importa: sin systemd, al daemon lo
  levanta EL BUS. Se comprueba que reclame `org.freedesktop.Notifications` y se
  le saca la línea `SystemdService=`, que acá no puede significar nada.

Y `DUNSTIFY=0`: entró primero en 1 y el binario **segfaultea hasta en
`--help`** (rc=139 en las tres pruebas). La causa es el cuadro que este repo ya
tiene escrito: nuestro `libnotify.so.4` es compartido y arrastra la cadena glib
`.so`, mientras esta cola enlaza la GLib estática ⇒ dos copias de GObject en un
proceso. No se pierde nada: el emisor de la casa es `notify-send`, que viene
DENTRO del artefacto de libnotify y ya está en las cuatro imágenes. Hay un
guardián que frena el día que alguien vuelva a poner DUNSTIFY=1 sin leer.

LA PRUEBA, en `scripts/wlr/dunst-headless.sh`: sway headless + bus de sesión +
`notify-send`, y NADIE lanza dunst a mano — lo activa el bus por el .service,
que es el mismo camino de una página web en atuq. La evidencia es un diff de
píxeles antes/después acotado al cuadrante donde dunst dibuja, porque un log en
verde es compatible con una pantalla vacía y el color no distingue (swaynag pinta
su barra de error en el mismo rojo).

    positivo  14832 píxeles cambiados · servidor: dunst knopwob 1.12.2 1.2
    control   0 píxeles · ServiceUnknown · notify-send rc=1

El control negativo borra el .service dentro del overlay temporal y EXIGE que no
se dibuje nada: probado en los dos sentidos.

Captura en docs/evidencia/dunst-sway-notificacion-2026-09-07.png.
Vigía de sonames: escritorio-sway 203 → 204 nodos, 512 sonames, 0 sin proveedor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
2026-09-07 18:15:29 +00:00
SergioandClaude Opus 5 07b9b31454 targets: libnotify en los cuatro escritorios que llevan atuq
Sin esta línea la receta queda sellada y en NINGUNA imagen — la lección de `foot`
que este mismo fichero tiene escrita cuatro renglones más arriba de `atuq`, y que
la clausura no puede ver porque mide lo declarado.

Va en los cuatro perfiles que ya llevan el navegador (kde, gnome, cosmic, sway) y
NO en base/cli/mirada, que no lo llevan. Verificado parseando el TOML y no
leyéndolo: libnotify=1 en esos cuatro, 0 en los otros tres.

Medición al lado, con el vigía de sonames antes y después:

    kde     306 -> 307 nodos    2083 -> 2086 sonames    0 sin proveedor
    gnome   190 -> 191           657 ->  660            0
    cosmic  161 -> 162           498 ->  501            0
    sway    201 -> 202           509 ->  512            0
    mirada   41 ->  41           231 ->  231            0   (no lleva atuq)

+1 nodo exacto por perfil, que es lo que se agregó, y el que no lo lleva no se
movió. Si hubiera arrastrado algo sin querer, el delta no sería 1.

Y una comprobación que el mapa de §6.10 no da y que es la que decide si el
`dlopen` va a funcionar: que resuelvan las deps de la PROPIA librería. Un dlopen
falla en silencio igual si libnotify está pero su gdk-pixbuf no. Se lo pregunté
al loader musl, no a mí:

    ld-musl --list /usr/lib/libnotify.so.4
      libgdk_pixbuf-2.0.so.0 => /usr/lib/...   libgobject-2.0.so.0 => /usr/lib/...
      libglib-2.0.so.0       => /usr/lib/...   libgio-2.0.so.0     => /usr/lib/...
      libc.so                => /lib/ld-musl-x86_64.so.1

Toda la cadena cae dentro del rootfs salvo libc.so, que es la excepción del lab
ya documentada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
2026-09-07 01:09:53 +00:00
Sergio 3a83d2eda4 gnome: xz-shared — sin ella NINGUNA app de la imagen carga, no una
`scripts/vigia-sonames.py` sobre los cinco perfiles marcaba `liblzma.so.5` en GNOME pedido por
`libadwaita` y por `python3`. La columna de QUIÉN lo pide es la que decide, y acá decidió fuerte:
mirando el artefacto sellado, el `NEEDED` no lo lleva un binario suelto sino
**`usr/lib/libadwaita-1.so.0`**, que es la librería contra la que enlaza toda app GTK4/GNOME de la
imagen. Sin proveedor del SONAME el loader falla en TODAS, no en una.

Misma fuga de siempre —el `xz` canónico del corpus es `.a`, así que el rootfs lo resolvía contra el
sysroot Alpine DEL LAB— y por eso invisible al store: el lab no entra en `hash_inputs`.

La receta `-shared` existía sólo en `incoming-kde`. Se promueve al corpus con el mismo procedimiento
que `bzip2-shared` el 2026-09-03, y comprobando lo mismo antes de creerlo: el hash es idéntico desde
las dos rutas (b3:174289d5) ⇒ un solo artefacto y cero rebuilds. Si hubiera diferido, sería otra
receta y no valdría la promoción.

Va sólo en GNOME: en cosmic y sway ese soname lo pide únicamente `python3`, que es herramienta de
build y no viaja en la imagen.

GNOME pasa de 3 sonames sin proveedor a 2. De los dos que quedan, `libperl.so` es de `perl` (build),
y `libreadline.so.8` lo pide `usr/bin/sqlite3` —el shell de la CLI, NO `libsqlite3.so.0`—, así que
las apps están bien y lo roto es el comando. Queda anotado como verruga, no como rotura.
2026-09-05 16:39:06 +00:00
Sergio 92d47cfe1d mirada: las tres -shared que faltaban — el USB resolvía expat/zlib/libffi contra el lab
`scripts/vigia-sonames.py` sobre los cinco perfiles: `escritorio-mirada` era el único que aún tenía
la fuga al sysroot del lab en componentes de RUNTIME. `mesa-swrast` y `wayland` salen con
`NEEDED libexpat.so.1`, `libz.so.1` y `libffi.so.8`; las recetas canónicas de expat, zlib y libffi
son `--disable-shared`; ningún artefacto del cierre publicaba esos SONAME. O sea que el rootfs los
resolvía contra el Alpine DEL LAB y el USB sólo arrancaba donde hubiera Alpine debajo — que es peor
que una dep faltante, porque el lab no entra en `hash_inputs` y el store no puede ni notarlo.

Es la misma fuga que los otros cuatro perfiles cerraron el 2026-09-03. mirada quedó fuera porque su
lista nació como copia literal del PKGS de `mirada-usb.sh` y nadie la revisó desde entonces. La
dirección hoy está invertida —el script deriva su PKGS de `targets.py escritorio-mirada`— así que
arreglarlo acá arregla el USB, y no hay dos listas que puedan divergir. Actualicé el comentario, que
seguía diciendo «lift verbatim».

Las tres viven en `corpus`, la misma cola del perfil, y ya están selladas ⇒ CERO rebuilds: sólo
entran a la clausura.

NO sumo `bzip2-shared` ni `ncurses-shared`, que sí llevan los otros perfiles: acá esos sonames los
pide únicamente `python3`, que es herramienta de build y no viaja en la imagen. El vigía imprime
siempre QUIÉN pide cada soname justamente para poder separar eso; sin esa columna su informe no se
puede triar y uno acaba tapando ruido.

Verificado: mirada pasa de 10 sonames sin proveedor a 7, y los 7 que quedan son todos de `python3` y
`perl`.
2026-09-05 16:37:02 +00:00
Sergio b606bf988b targets.toml: atuq entra en los cuatro escritorios
Hasta ahora atuq estaba sellado y NO estaba en ninguna imagen. Es exactamente la lección que este
fichero ya aprendió con `foot` en el perfil de sway: una receta sellada que ningún perfil declara no
la lleva nadie, y la métrica de clausura no lo puede ver porque mide lo DECLARADO.

Va en los cuatro por el mismo motivo que `mpv` y desde el mismo sitio: vive en el CORPUS, y una
receta resuelve sibling-first y después el catálogo padre, así que las cuatro colas lo alcanzan.

Arrastra la cadena GTK3 en sus variantes `-shared`, y el comentario lo dice: con las estáticas,
libgtk-3 y libgdk-3 se llevaban cada una su copia de pango/cairo y el navegador no llegaba a pintar.

NOTA sobre la métrica: el grafo del hub sólo reporta base/cli/escritorio-mirada — los cuatro
escritorios no aparecen en `by_profile` porque sus raíces viven en las colas. Es previo a este
cambio y no lo introduce; queda anotado porque significa que esta declaración NO se ve todavía en
`build-state.json`.
2026-09-05 15:36:08 +00:00
Sergio e684c26e52 targets: obs-studio entra al perfil escritorio-kde (274/274)
Una receta sellada que ningún perfil declara NO ESTÁ EN NINGUNA IMAGEN, y la
métrica de clausura no lo puede ver porque mide lo declarado — la lección que este
fichero ya aprendió con foot en sway y con las 22 apps KDE. OBS estaba sellado y
en ninguna imagen.

Entra SÓLO en escritorio-kde, y no por preferencia: su frontend es Qt6 y las 13
recetas Qt viven sólo en esa cola. Verificado con `yupana perfiles obs-studio`.
El perfil pasa de 263/263 a 274/274 — arrastra 11 nodos a la clausura, todos ya
sellados, y el grafo sigue cerrando.

De paso: el aviso de los «tres huecos de mpv» estaba repetido en los CUATRO
perfiles de escritorio y hoy quedó vencido en los cuatro (ALSA/PipeWire, Lua/OSC
y vaapi están cerrados). Corregidos los cuatro; el assert de count==1 fue lo que
destapó que no era uno solo.
2026-09-04 17:42:47 +00:00
SergioandClaude Opus 5 8c4e1a6b24 cursores: cosmic y sway corrían con el puntero INVISIBLE — receta propia, y el vigía medía mal
`vigia-imagen.py` daba ✗ en cursores en DOS de los cuatro escritorios. No es cosmético: con el
cursor por software —obligatorio en virtio-gpu y en todo render por CPU— el compositor dibuja la
imagen que le da el TEMA, y sin tema el ratón se mueve invisible. cosmic llegó a 43/43 y sway a
173/173 así, porque un tema de cursor no es dep de build de nadie: sólo entra si se DECLARA.

`adwaita-cursors` (corpus, 48.1, data-only): del mismo tarball que `adwaita-icon-theme` pero SÓLO
`Adwaita/cursors/` — 39 ficheros y 14 MB, sin un icono. Promover el tema entero habría regalado a
sway y a cosmic los iconos de GNOME, que está anotado como decisión pendiente y no como olvido.

Las dos cosas que el tarball no trae y la receta fabrica:
· los nombres X11 heredados (`left_ptr`, `xterm`, `watch`, `hand2`…) son enlaces que genera el
  `meson.build` de upstream. El mapa se PARSEA de ahí, no se copia: copiado envejece en silencio.
  Si el origen de un enlace no existe, la fase falla — upstream pone un `files()` como aserción.
· `/usr/share/icons/default/index.theme` con `Inherits=Adwaita`. Sin `XCURSOR_THEME` en el
  entorno, libXcursor y wlroots buscan el tema llamado literalmente `default`; sin él no hay
  puntero AUNQUE Adwaita esté instalado. Es el eslabón que hace que ande sin configuración.

⚠ no declarar esta receta junto a `adwaita-icon-theme`: chocan en `Adwaita/cursors/*`.

Y el vigía estaba midiendo el invariante de al lado: exigía `index.theme` en el directorio para
contar un tema, que es correcto para ICONOS —la búsqueda XDG recorre `Directories=`— y falso para
CURSORES, porque libXcursor abre `<tema>/cursors/<nombre>` directo y el índice sólo hace falta para
seguir un `Inherits=`. Con la receta instalada seguía diciendo «NINGÚN tema de cursor».

De paso queda anotado en `targets.toml` que el comentario de cosmic decía «sin ellos arranca sin
puntero» sobre `cosmic-icons`, que no trae cursores: describía una protección que no existía.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGyBj4QyeCZiwLMkP9GJp6
2026-09-04 01:38:30 +00:00
SergioandClaude Opus 5 d69c437051 escritorios: KDE y GNOME corrían SIN tema de iconos — el panel vacío no era el panel
Medido sobre los artefactos del cierre de cada imagen, contando qué `usr/share/icons/*/`
trae un `index.theme` (que es lo que convierte un directorio en un tema):

    escritorio-kde      sólo Breeze_Light y breeze_cursors — que son CURSORES
    escritorio-gnome    CERO, ni siquiera hicolor
    escritorio-cosmic   Cosmic + hicolor            <- el único que estaba bien
    escritorio-sway     CERO

Las dos capturas de QEMU de hoy ya lo mostraban y se leyeron como «arranca»:

- `plasma6-qemu-2026-09-03-kactivitymanagerd.png`: panel con el reloj y nada más. Los `.so`
  de los applets estaban TODOS instalados; lo que faltaba era qué pintar. `kickoff`,
  `systemtray`, `showdesktop` y `trash` SON un icono, así que sin tema quedan de ancho cero
  — el reloj se ve porque dibuja TEXTO. El log lo decía en una línea:
  `kf.iconthemes: Icon theme "breeze" not found.`
- `gnome-shell-qemu-2026-09-03-libs-compartidas.png`: barra superior con la fecha y el pill
  de espacios, y la DERECHA vacía — red, volumen y batería son iconos.

`breeze` (ya declarado) es el tema de WIDGETS y CURSORES; los ~14.000 iconos viven en
`breeze-icons`, receta aparte, sellada desde siempre y declarada por NADIE. Igual
`adwaita-icon-theme` en GNOME, que además trae los XCursor (su cabecera dice por qué:
sin tema de cursor no hay puntero visible con el cursor por software de virtio-gpu).

Es la figura de las fuentes y de `foot` otra vez: data de RUNTIME, ninguna arista de BUILD
la alcanza, y la métrica de clausura no la echa de menos porque mide lo declarado.

`hicolor-icon-theme` —el fallback obligatorio de freedesktop— se promueve al corpus para que
lo alcancen las cuatro imágenes: hash IDÉNTICO desde la cola y desde el corpus
(b3:98ad44a5), y sus 4 consumidores (adwaita-icon-theme, cosmic-icons, cosmic-app-library,
cosmic-launcher) miden el mismo hash antes y después ⇒ un solo artefacto, CERO rebuilds.

Cierre tras declararlos, todo sellado y sin deuda:
  kde 263/263 (+2) · gnome 158/158 (+2) · sway 173/173 (+1) · cosmic 133/133 (igual)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YGXe6mShYYw6V8ea1dQ13u
2026-09-03 21:18:17 +00:00
SergioandClaude Opus 5 8df2559556 escritorio-kde: declarar kactivitymanagerd (plasmashell ABORTA sin él)
Arrancando la imagen KDE en QEMU: kwin toma el DRM, expone wayland-0, plasmashell
arranca… y la pantalla se queda EN NEGRO con los dos procesos vivos. El serial
repetía 'STATUS: kwin=1 plasmashell=1' durante minutos, que se lee como salud.

La causa estaba en el log de Qt, no en el estado de procesos:

  kde.plasmashell: Aborting shell load: The activity manager daemon
  (kactivitymanagerd) is not running.

No sale de la clausura de las 7 raíces porque NO es dep de build de nada: plasmashell
lo pide por D-Bus al arrancar. scripts/kde/plasma-start-qemu.sh ya lo lanzaba (l.188);
lo que faltaba era que el paquete entrara al rootfs. Ya estaba sellado — declararlo no
cuesta un build.

Misma figura que las raíces de runtime de GNOME (imports.gi.*): lo que se invoca por
bus es invisible al grafo de deps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0164nrZWZc78Mr2srrsnoM8U
2026-09-03 20:31:28 +00:00
SergioandClaude Opus 5 6c37df6fa4 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
2026-09-03 17:29:24 +00:00
SergioandClaude Opus 5 68274d6c2a kde: declarar las 22 apps que ya estaban selladas y en ninguna imagen
CERO builds: las 22 llevaban tiempo selladas en incoming-kde y no las declaraba nadie,
así que sólo entran a la clausura. El perfil pasa de 200 a 259 nodos y sigue 998/998.

POR QUÉ FALTABAN, que no es lo mismo que «se decidió que no fueran». Las 7 raíces del
perfil son el set MÍNIMO del arranque en metal, tomado de
docs/HANDOFF-noche-kde-2026-07-14.md:135. Ese mismo documento nombra lo que faltaba como
el trabajo siguiente: «Capa 2 KF6 / apps núcleo: importar+construir konsole/dolphin/kate
vía granja (extiende el escritorio usable)». Se construyeron y se sellaron; lo que nunca
ocurrió fue DECLARARLAS. La métrica no lo podía ver porque mide lo declarado — la misma
figura que dejó a sway en 121/121 sin emulador de terminal.

Sin esto la imagen era compositor + shell + tema y nada más: sin terminal no se puede
salir de un fallo, y sin plasma-nm/plasma-pa/powerdevil no hay red, ni audio, ni gestión
de energía.

Entran en tres grupos:
- plomería de Plasma: plasma-desktop, plasma-nm, plasma-pa, powerdevil, kscreen,
  systemsettings, kinfocenter, kmenuedit, kwalletmanager
- núcleo usable: konsole, dolphin, kate, okular, ark, spectacle, gwenview
- accesorios ya construidos: kcalc, kfind, filelight, kcharselect, kruler, kdf

Cómo se encontró, para repetirlo: listar los (cola,nombre) cuyo artefacto instala un
usr/share/applications/*.desktop y que yupana.membresia() no asigna a ningún perfil.
Salieron 23; 22 eran de acá.

VERIFICADO con scripts/vigia-sonames.py, que es donde 22 componentes de RUNTIME nuevos
podrían filtrar al lab: escritorio-kde queda con 1254 sonames provistos y 5 sin
proveedor, y los 5 son ruido conocido —cmake, python3 y perl son herramientas de build
que la imagen no instala, más el pw-top de pipewire que ya estaba anotado—. Ninguna app
nueva aporta un NEEDED huérfano.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
2026-09-03 17:22:02 +00:00
SergioandClaude Opus 5 1f415802f1 apps: GNOME tenía imagen sin terminal ni editor — arreglado con CERO recetas; y el montón A queda agotado
DOS COSAS, y la segunda no estaba en el plan.

1. EL MONTÓN A SE AGOTÓ DE COSAS SIN MURO. Re-triadas con scripts/provee.py las tres
   candidatas que la tabla daba como baratas o dudosas; las tres son muro, y ninguna por
   el motivo que decía la tabla:

   - imv     → no hay proveedor de OpenGL de escritorio (ya documentado; la sustituyó swayimg)
   - mupdf   → la nota decía «el X11 es del visor mupdf-x11; mupdf-gl no». Falso: platform/gl/
               va sobre GLUT + OpenGL de escritorio, el mismo muro que imv. mupdf NO TIENE
               visor Wayland: sus dos UIs son X11 o GL. El motor no tiene muros pero no hace
               falta, el PDF ya lo da poppler.
   - inkscape→ era GTK3 encubierto, como la tabla sospechaba: INKSCAPE_1_4_4 pide
               gtkmm-3.0>=3.24 y gtk+-3.0>=3.24. master SÍ migró a GTK4 (gtk4>=4.14) pero
               está sin publicar, y además el catálogo no tiene NI UN binding C++:
               provee.py da ✗ en sigc++-2.0, glibmm-2.4, cairomm-1.0, pangomm-1.4 y gtkmm.

   ⇒ todo lo que queda está detrás de UNA de cuatro decisiones (GTK3 / Qt6-en-el-corpus /
   GL de escritorio / X11), no de trabajo de recetas.

2. LO QUE SÍ ESTABA ROTO, Y NO ERA FALTA DE RECETAS. Cruzando la membresía de los cuatro
   perfiles contra emuladores de terminal, editores y gestores de ficheros:

       escritorio-cosmic  term=cosmic-term  editor=cosmic-edit  archivos=cosmic-files
       escritorio-sway    term=foot         editor=vim          archivos=—
       escritorio-gnome   term=—            editor=—            archivos=—
       escritorio-kde     term=—            editor=—            archivos=—

   Es la lección de foot otra vez y a mayor escala: sway llegó a 121/121 SIN emulador de
   terminal porque nadie lo declaraba, y la métrica de clausura no lo ve porque mide lo
   DECLARADO. Un escritorio sin terminal no es uno incompleto: es uno del que no se puede
   salir cuando algo falla.

   GNOME arreglado con CERO recetas nuevas: foot y helix viven en el CORPUS, ya estaban
   selladas y no las declaraba NINGÚN perfil. foot es Wayland puro sobre xdg-shell (no
   pide protocolos de wlroots) así que corre bajo mutter igual que bajo sway; su único
   NEEDED es libc.so. helix es TUI y corre dentro de la terminal.

   ⚠ KDE NO SE TOCA, y es un hallazgo aparte que hay que acordar con ese frente: tiene 22
   apps con .desktop selladas en su cola y sin declarar (konsole, dolphin, kate, okular,
   spectacle, ark, gwenview, systemsettings, kcalc…) y además le faltan plasma-desktop,
   plasma-nm, plasma-pa, powerdevil y kscreen — o sea red, audio, energía y pantalla. Su
   perfil son 13 raíces de ARRANQUE EN METAL, no la imagen de escritorio completa (ésa se
   hidrató aparte, por hash). No cuesta un build; cuesta acordarlo.

Nota de método: build-state.py invoca `hammer hash` ~1000 veces y el otro agente
recompiló el binario a mitad del barrido ⇒ kjobwidgets salió `unhashable` y KDE reportó
997/998. No era la receta: era la carrera. Re-corrido da 998/998.

Los cinco grafos: 819/819, 998/998, 882/882, 855/855, 832/832, cero nodos `wanted`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
2026-09-03 16:18:44 +00:00
SergioandClaude Opus 5 cab9439644 fuentes: dejavu al corpus (arregla un wanted que dejé), KDE con fuente, y la nerd de monospace
TRES COSAS, y la primera es un bug mío de hace un rato.

1. dejavu-fonts PROMOVIDA AL CORPUS — arregla un nodo `wanted` que introduje.
   Al añadirla de raíz a escritorio-gnome quedó como `wanted`: la receta existía en
   incoming-{kde,wlr,cosmic} y NO en incoming-gnome ni en el corpus, y una receta no
   alcanza una cola hermana. O sea que la raíz no resolvía a nada y GNOME seguía sin
   fuentes. `wanted` NO es `debt`, así que drenaje.json seguía diciendo deuda=0 y el
   hueco no salía por ninguna métrica — se vio mirando el nodo en build-state-gnome.json.
   Promoción gratis: las tres copias sellaban el mismo hash (b3:5b3a5df3) y sus deps
   están vacías. Las tres se jubilan. Ahora los cuatro perfiles resuelven a corpus y los
   cinco grafos quedan con CERO nodos `wanted`.

2. dejavu-fonts en escritorio-kde, como se pidió. Tenía UNA fuente y de rebote, dentro
   de qtbase — apoyarse en un detalle de empaquetado de Qt deja sin nada a todo lo que
   no es Qt.

3. dejavu-fonts-nerd 3.5.1 en los CUATRO perfiles: DejaVu Sans Mono parcheada con Nerd
   Fonts. Lo que se midió antes de escribirla, porque cambia la forma de hacerla:

   - SE AÑADE, NO REEMPLAZA, y no es prudencia: la licencia de Bitstream Vera exige que
     una fuente modificada se RENOMBRE. Por eso upstream la llama «DejaVuSansM Nerd Font
     Mono» — leído del name de la TTF, no supuesto. Una fuente nerdeada NO puede
     responder al nombre de la original; sustituirla rompería a todo lo que pide «DejaVu
     Sans Mono», empezando por foot, que ya murió una vez así.
   - SÓLO LA MONOESPACIADA: upstream no publica DejaVu Sans ni Serif parcheadas, y en una
     fuente de interfaz proporcional esos glifos no los pide nadie. «Todas nerdeadas» no
     se puede cumplir al pie de la letra.
   - SÓLO UNA DE LAS TRES VARIANTES: NerdFont / NerdFontMono / NerdFontPropo son los
     mismos glifos con distinto avance. Las doce pesan 32 MB; las cuatro de Mono, 11 MB.
     Se quedan las Mono, que son las correctas para una rejilla de terminal. Referencia:
     la familia DejaVu ENTERA son 9,8 MB.

   ⚠ EL NÚMERO DEL .conf ESTÁ MEDIDO, NO ELEGIDO. fontconfig carga conf.d en orden
   alfabético y el <prefer> que llega antes gana. Con 60-nerd-monospace.conf —que ordena
   después de 60-latin.conf, que ya prefiere Noto/DejaVu/Inconsolata— `fc-match
   monospace` seguía dando DejaVu Sans Mono: la conf estaba INSTALADA Y ERA INERTE, el
   mismo modo de fallar que el plugin ALSA de PipeWire. A 59- gana. 45-latin.conf también
   menciona monospace pero usa <default>, no <prefer>, así que no compite.

   ⚠ Y strip_components = 0, que tampoco es cosmético: el tarball de nerd-fonts es PLANO
   y con el default de 1 el árbol de fuentes queda VACÍO — el build no se detiene ahí,
   sigue y falla más tarde en el cp, con un mensaje que no menciona la extracción.

   ⚠ LA LICENCIA NECESITA UNA PASADA HUMANA antes de `hammer pack`: el LICENSE.txt del
   tarball cubre sólo DejaVu/Bitstream Vera y no menciona los ~10 conjuntos de iconos
   importados, que tienen licencias propias (Font Awesome CC-BY-4.0, Octicons MIT,
   Material OFL, Powerline MIT…). El campo dice lo DOCUMENTADO, no el conjunto real; no
   se inventa una cadena SPDX. Anotado en la receta.

EVIDENCIA, contra el conf.d REAL del artefacto de fontconfig:
  monospace          -> DejaVuSansM Nerd Font Mono   (los iconos llegan)
  "DejaVu Sans Mono" -> DejaVu Sans Mono             (intacta)
  sans-serif         -> DejaVu Sans                  (sin tocar)
  serif              -> DejaVu Serif                 (sin tocar)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
2026-09-03 16:09:24 +00:00
SergioandClaude Opus 5 da0186d250 apps: zathura + backend de PDF, en gnome/cosmic/sway — y tres defectos de imagen que salieron
La cadena: 5 recetas nuevas (girara, xxhash, poppler-glib, zathura,
zathura-pdf-poppler) y 5 promociones GRATIS al corpus (json-glib, lcms2, glib-shared,
pcre2-shared, bzip2-shared — todas con hash idéntico desde la cola y desde el corpus,
medido con hammer hash antes de mover nada).

NO va en escritorio-kde, y está escrito en tres sitios para que no se liste por
descuido: su backend necesita una poppler con ENABLE_GLIB=ON, y esa imagen ya trae la
de Qt6 vía okular. Las dos instalan /usr/lib/libpoppler.so.146 y son artefactos
DISTINTOS ⇒ hidratar las dos pone dos ficheros en la misma ruta. La salida no sería
listarla igual, sería jubilar una de las dos popplers.

LAS TRES COSAS QUE SE ROMPIERON, que valen más que las recetas:

1. -DENABLE_GLIB=ON SE APAGA SOLO Y EL BUILD SALE OK. CMakeLists:260 hace
   `if(NOT CAIRO_FOUND) set(ENABLE_GLIB OFF)`: no falla, obedece distinto. El primer
   artefacto selló sin nada de glib, exit 0, sin aviso. La causa era una línea que este
   repo ya tiene como patrón: `.pc Requires` → `[deps].build`. cairo.pc pide pixman-1 y
   pixman no estaba declarado ⇒ `pkg-config --exists cairo` falso ⇒ CAIRO_FOUND falso.
   Un .pc que falta a tres saltos apaga una FUNCIÓN, no una librería. La receta ahora
   comprueba en install que poppler-glib.pc y libpoppler-glib.so existan.

2. DOS COPIAS ESTÁTICAS DE GObject EN UN PROCESO. Con la glib estática del corpus todo
   sella y zathura arranca — y al dlopen del plugin escupe «cannot register existing
   type 'gchar'» y no abre nada. No son dos ficheros en una ruta: son dos copias del
   sistema de tipos dentro del mismo proceso, una en el ejecutable y otra dentro de
   libpoppler-glib.so. Un .a NO duplica (sólo tiene símbolos sin definir, se resuelven
   al ligar); el problema aparece sólo cuando dos objetos enlazados por separado meten
   cada uno la suya. Arreglo: las TRES recetas de la cadena declaran glib-shared.

3. FUGA AL LAB, cazada por scripts/vigia-sonames.py: zathura selló con
   NEEDED libsqlite3.so.0, un SONAME que ningún artefacto del cierre publica — meson
   había resuelto dependency('sqlite3') contra el sysroot Alpine DEL LAB. Y declarar la
   dep NO alcanzó: el .pc da un -lsqlite3 pelado y el linker prefiere la .so del lab
   sobre la .a del store. Lo arregla -Dprefer_static=true. Medido: los NEEDED bajaron de
   DIEZ a CUATRO y los cuatro los publica el cierre.

DOS DEFECTOS DE IMAGEN PREEXISTENTES que salieron de paso:

- libbz2.so.1 no lo publicaba nadie y freetype-shared lo pide, en los TRES perfiles.
  Se vio de verdad al probar el visor (el plugin no hacía dlopen). bzip2-shared existía
  sólo en incoming-kde; promovida y puesta de raíz junto a expat-shared/libffi-shared.

- escritorio-gnome NO TRAÍA NI UN FICHERO DE FUENTE. Contado sobre los artefactos del
  cierre: gnome=0, sway=22, kde=1 (una de rebote dentro de qtbase). Tenía fontconfig,
  que es el MOTOR que busca fuentes, no una fuente. Un PDF con base-14 se ve VACÍO y sin
  error. Añadida dejavu-fonts, la única receta de fuentes del catálogo entero.
  ⚠ KDE queda igual y NO se toca acá: no lleva zathura y su árbol lo trabaja otro frente.

EVIDENCIA DE QUE ANDA, no de que sella:
- `zathura --version` con el plugin lista «(plugin) pdf-poppler (2026.07.18)» sin un
  solo GObject-CRITICAL.
- poppler-render-check, receta-TESTIGO en el espíritu de gtk4-hello: arma un PDF a mano,
  lo abre con poppler-glib, lo rasteriza sobre cairo y CUENTA PÍXELES — 9600 negros,
  exactamente el rectángulo de 160x60. Un lienzo blanco no pasa. Su primera versión
  medía el TEXTO y falló: el sandbox no tiene fuentes, que es cómo se descubrió el
  hueco de arriba. El assert quedó sobre el rectángulo, que no depende de tipografía, y
  el texto se informa aparte.

Los cinco grafos quedan en N/N con cero deuda.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
2026-09-03 12:14:07 +00:00
SergioandClaude Opus 5 d8dd779812 apps: swayimg 4.7 sellada, visor de imágenes en las cuatro imágenes
El plan proponía `imv` como paso 4. Al medirla apareció un muro que el método de
triaje no podía ver: imv dibuja con OpenGL de función fija (glBegin/glOrtho) y esta
distro NO tiene proveedor de GL de escritorio — las tres variantes de mesa van
-Dglx=disabled -Dglvnd=false y no publican libGL.so ni gl.pc ni opengl.pc, y no hay
receta libglvnd. La trampa fina: mesa SÍ instala GL/gl.h, así que compila entero y
muere al ligar. Darlo vuelta = autorar libglvnd + rehacer mesa (radio 139).

swayimg cubre el mismo caso de uso sin tocar GL (rasteriza a wl_shm) y con CERO
recetas nuevas: sus deps requeridas ya estaban en el corpus. De yapa trae backend DRM
(imágenes en TTY pelada, sin compositor).

Pineada 4.7 y no 5.5 a propósito: desde la 5.0 luajit es obligatoria y no hay receta
(la lua5.2 del OSC de mpv es otra ABI). Escribir luajit es el próximo paso barato.

-Dversion=4.7 no es cosmético: su default 0.0.0 dispara un `git describe` sobre el
árbol ⇒ la versión estampada en el binario dependería del checkout. Misma familia que
el -Dbuild-date de mpv.

Evidencia de que anda, no sólo de que sella: NEEDED = libc.so y nada más (cero glibc,
cero X11, cero GL), `--version` dice 4.7 con jpeg/png/gif/webp/tiff, prueba las dos
UIs (Wayland → DRM) y el bucle de decodificación rechaza lo que no es imagen.

Añadida a las raíces de los cuatro perfiles (sellado ≠ instalado). Los cinco grafos
quedan en N/N con cero deuda.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XB2iEmxeLZgzqNfhWChrLo
2026-09-03 11:28:19 +00:00
Sergio 39f5007cff wf-recorder 0.6.0: la primera app que cobra la promoción de pipewire
Sellada b3:25caa359 al primer intento. `--help` corre y los NEEDED traen `libpipewire-0.3.so.0`:
graba CON audio, que era exactamente lo que anoche no se podía.

Es la app que estaba bloqueada por el defecto del triaje —preguntaba «¿existe la receta?» en vez de
«¿la alcanza quien la usa?»—: sus dos backends de captura de sonido vivían sólo en colas y una
receta del corpus no alcanza una cola hermana. Con pipewire en el corpus, cae sola.

`-Ddefault_audio_backend=pipewire` explícito y no `auto`: su meson resuelve `auto` mirando qué
encontró en el sandbox (meson.build:95-101), o sea que el artefacto dependería de lo que quedó
montado en el lab. Misma disciplina que en mpv y ffmpeg.

⚠ SE LISTA SÓLO EN `escritorio-sway`, y la razón es de PROTOCOLO: captura por `wlr-screencopy`, que
implementan los compositores wlroots. KWin y mutter NO lo implementan —usan el portal/ScreenCast—,
así que meterlo en esas dos imágenes sería enviar una herramienta que no puede funcionar ahí: deuda
fantasma con forma de app. En COSMIC hay que verificar si cosmic-comp expone el protocolo antes de
listarlo; no se listó a ciegas.

Cubre el caso de uso principal por el que uno instala OBS, con UNA receta en vez de las 20 + Qt6 +
X11 que OBS pedía. escritorio-sway 146/146.
2026-09-03 06:17:57 +00:00
SergioandClaude Opus 5 f64859bade qorpa export: shims generados, y la clase ajeno para que nadie los cuente mal
Paso 5 del ADR 0015, sus dos mitades.

SHIMS. `hammer qorpa export <id>` genera lanzadores finos y `.desktop` en el
espacio del host, desde lo DECLARADO en `[export]` — nunca todo: exportar todo
haría que el `ls` de la imagen compita con el nuestro, que es la falla de
Bedrock (arbitra en tiempo de exec, por heurística). Gana en tres cosas contra
un FUSE: cero costo en runtime, `cat` al shim y ves qué hace, y se revoca
borrándolos.

Se GENERAN, no se copian. El `.desktop` se arma con lista BLANCA de claves, así
que `Exec`, `TryExec`, `Path` y `DBusActivatable` quedan fuera por definición y
no por enumeración — una lista negra dejaría entrar la próxima clave ejecutable
que invente el estándar. El Exec original se cita en un comentario del fichero
generado, para que se vea qué decía y qué no se copió. El icono se busca en la
vista merged (upper primero, imagen después: si no, se perdería lo que instaló
el gestor de paquetes) y se copia al host, porque un icono que el host no
resuelve se ve como un cuadrito gris. Y `exported.json` registra cada fichero
escrito, para que `--remove` borre EXACTAMENTE eso y no por patrón sobre el
~/.local/bin de alguien.

Probado de punta a punta con un .desktop ajeno real de la imagen de Arch: el
shim corre `pacman -Q` del huésped desde el host, el X-KDE-Wayland-Interfaces
quedó fuera, el icono viajó, y --remove dejó 0 ficheros con la instancia intacta.

CLASE `ajeno`. build-state.py inyecta los nodos declarados en el nuevo
docs/state/qorpa-ajenos.toml ANTES que los `wanted`, y ese orden es la mitad del
punto: un nodo que provee una imagen ajena no es una receta por escribir. Con
eso el `xwayland` de escritorio-kde deja de ser deuda y pasa a contarse aparte:

  escritorio-kde       187/188 listo   falta   1  (raíces 14, + 1 ajenas)

Dos decisiones que sostienen esa cifra: los ajenos se RESTAN del denominador (si
entraran, el número que se lee como "cuánto construimos" crecería solo cada vez
que alguien enjaula una app), y la declaración vive en el REPO y no se lee de
/var/lib/hammer — build-state.json se commitea y lo regenera el cron en dos
máquinas; si la clase saliera de las instancias instaladas, cada una diría algo
distinto y se pisarían en cada cosecha. Es el error que ya se cometió con
sealed_remoto. Qué provee una imagen ajena es diseño; qué tenés instalado, no.

Un ajeno tampoco se hashea, y no por comodidad: no tiene procedencia de fuente,
así que un hash afirmaría que lo reproducimos.

2 tests nuevos (que del .desktop ajeno no sobreviva nada ejecutable; que el shim
no se rompa con rutas raras). 47/47.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
2026-09-03 06:15:19 +00:00
Sergio f9da495b8c vigía de sonames: KDE, GNOME y COSMIC tenían la MISMA fuga al lab que sway ya había documentado
Verificando que mpv anduviera en las cuatro imágenes salió esto: `libEGL.so.1` (mesa) pide
`libexpat.so.1` y `libwayland-client.so.0` pide `libffi.so.8`, las recetas canónicas de expat y
libffi son `--disable-shared`, y ningún artefacto del cierre publica esos SONAME ⇒ los tres rootfs
los resolvían contra el **sysroot Alpine DEL LAB**.

Es peor que una dep faltante: el lab NO entra en `hash_inputs`, así que el store no puede notarlo —
el artefacto se sella, el perfil reporta 100%, y la imagen sólo arranca en una máquina con Alpine
debajo. El perfil de sway ya tenía escrito exactamente este párrafo desde 2026-08-26; lo que faltaba
era el instrumento para ver que las otras tres estaban igual.

Arreglo (gratis, sin rebuild: las dos recetas ya estaban selladas por sway): `expat-shared` y
`libffi-shared` pasan a raíces de los tres perfiles. Van de raíces y no de `[deps]` por la misma
razón que las fuentes y el XKB — son data de RUNTIME y ninguna arista de build las alcanza.

`scripts/vigia-sonames.py` es el guardián que sale del punto ciego: recorre los NEEDED de todo el
cierre de cada imagen contra los SONAME que ese mismo cierre publica. Dos decisiones de diseño:
  · keying por PAR `(cola, nombre)` vía yupana, NO por nombre — `build-state.json` colapsa los
    nombres que viven en dos colas y su campo `perfiles` puede quedar vacío para una receta que sí
    está en la imagen.
  · imprime SIEMPRE quién pide cada soname, porque el cierre incluye herramientas de build
    (python3, cmake, perl, go) que la hidratación no instala: sin esa columna el informe no se tría.

Después del arreglo, `libexpat.so.1` y `libffi.so.8` desaparecen de los tres. Lo que queda son
hallazgos REALES que no son de este commit y quedan anotados:
  · kde:    karchive pide libbz2.so.1 y liblzma.so.5
  · gnome:  spidermonkey pide libstdc++.so.6 y libgcc_s.so.1; libadwaita pide liblzma.so.5;
            freetype-shared pide libbz2.so.1; sqlite-shared pide libreadline.so.8
  · cosmic: llvm18 pide libgcc_s.so.1
  · los tres: `pw-top` de pipewire enlaza `libncursesw.so.6` DEL LAB — y la receta afirma en un
    comentario que sin la dep «meson saltea pw-top». Es falso desde al menos el 2026-08-29:
    `dependency('ncursesw')` lo encuentra igual en el sysroot del lab. El comentario dice una cosa
    y el binario otra.
2026-09-03 03:42:31 +00:00
Sergio 01b86fd0fb targets: mpv entra como raíz de los cuatro escritorios — sellado no es instalado
Sin esto mpv quedaba sellado y en NINGUNA imagen. Es exactamente la lección que este fichero ya
tiene escrita en el perfil de sway: `foot` estaba sellado en el corpus, ningún perfil lo listaba,
y el escritorio daba 121/121 SIN EMULADOR DE TERMINAL. La métrica de clausura mide las raíces
declaradas y no puede ver lo que falta en la declaración.

Se lista en los cuatro porque mpv vive en el corpus: una receta resuelve sibling-first y después
el catálogo padre, así que desde cualquiera de las cuatro colas se alcanza.

Los cuatro perfiles siguen en 0 deuda y cierran con la clausura nueva ya sellada:
  escritorio-kde     171 → 180/180
  escritorio-gnome   119 → 129/129
  escritorio-cosmic   90 → 103/103
  escritorio-sway    129 → 139/139
Corpus 788 → 798 recetas, 798 selladas. grafo: CIERRA | topo-sort: OK en los cinco.
2026-09-03 03:21:38 +00:00
Sergio 776692d01a ADR 0015 (propuesto): imágenes ajenas — el mundo glibc entra enjaulado y no entra al store
Decide la frontera antes de escribir código. Sale de una medición incómoda: el
corpus tiene cuatro escritorios que cierran y CERO navegador, ofimática,
reproductor o editor de imagen. Al partir el «qué falta» por causa, la
intersección de «sólo X11» con «compilable desde fuente en musl» es casi vacía:
casi todo lo que se pierde por Wayland ya estaba perdido por la libc. El montón
que duele son binarios ajenos que nadie va a recompilar.

Las siete decisiones:

D1 — Una imagen ajena NO es un artefacto y no vive en el store. El store promete
     reconstrucción bit a bit desde fuente; un rootfs de Fedora no. Meterlo ahí
     sería la misma clase de error que el artefacto vacío: algo que se lee como
     garantía y no lo es. Namespace paralelo, por digest, fuera de hash_inputs.
D2 — Se cruza el borde con protocolos y nodos de dispositivo, NUNCA con
     librerías. Wayland/PipeWire son protocolos; /dev/dri y /dev/ntsync son ABI
     de kernel. Mesa va adentro de la imagen. Corolario: la jaula no sabe qué
     libc hay adentro, y por eso resuelve el montón entero de una vez.
D3 — El manifiesto es la verdad; el `upper` del overlay es CACHÉ. Misma relación
     que receta↔artefacto. De ahí se caen solas la actualización de base (se
     recrea, no se rebasea), el respaldo (KB, no GB) y la poda.
D4 — Cuatro granularidades, no una. El runtime curado inmutable (tipo 2) sigue
     siendo el preferido cuando alcanza: se sella. El rootfs con dnf existe
     porque es justo lo que el tipo 2 no permite.
D5 — Transparencia por shims GENERADOS, no por un FUSE global. Es el poder de
     Bedrock sin sus formas: cero costo en runtime, inspeccionable, revocable, y
     se exporta lo declarado (Bedrock arbitra en tiempo de exec, con heurísticas).
     Los nodos exportados entran al grafo con clase `ajeno` ⇒ no se pueden contar
     como corpus. Bedrock no puede decirte qué tenés.
D6 — Steam ya ES un contenedor: se anida pressure-vessel adentro, que es la
     configuración que Valve prueba. El bwrap anidado hay que VERIFICARLO.
D7 — Es el único lugar del sistema donde la política se escribe en vez de
     derivarse. harkaq deriva `política = clausura(deps)`; una imagen ajena no
     tiene clausura declarada. Excepción nombrada y acotada, por defecto vacía.

Y lo que el ADR admite que NO resuelve, escrito para no descubrirlo en producción:
el socket de Wayland es un borde de privilegio y lo pasamos crudo (screencopy y
virtual-keyboard incluidos — Flatpak pasa un proxy filtrante, nosotros no lo
tenemos); el UID mapping va a fallar primero y las piezas ya están en el corpus
(shadow instala newuidmap/newgidmap y crea /etc/subuid vacío, falta
provisionarlo); es una segunda cadena de suministro sin garantías; y hay que
acotar por escrito el claim de bit-repro o la cultura de números honestos se
erosiona sola.

Se cae gratis: `xwayland` deja de ser deuda del corpus (va DENTRO de la imagen,
que ya lo trae, y se cuelga de kwin por el socket) ⇒ el wanted de KDE se
disolvería sin escribir la receta y sin tocar Wayland-only. GIMP e Inkscape dejan
de reabrir la deuda GTK3. Y del plan de juegos: F2 (glibc+multilib desde fuente,
«una campaña entera») queda CANCELADA y F0 (flatpak+ostree al catálogo)
innecesaria.

Lo nativo no se afloja: el montón A se sigue construyendo, en orden mpv → OBS →
Firefox.

Toca sólo documentación: el ADR nuevo, la nota de generalización en
plan-jaula-juegos.md §Capa 3, y el comentario en targets.toml que evita que
alguien escriba la receta de xwayland sin ver la decisión pendiente. Cero recetas
tocadas, cero re-hasheo: --kde sigue en 978 sealed / 1 wanted / 171-171, CIERRA.
2026-09-03 02:49:40 +00:00
SergioandClaude Opus 5 0ac6fbfb5b targets: el perfil GNOME ya no reclama las 3 recetas aparcadas por diseño
`targets.toml` declaraba gnome-session, gnome-settings-daemon y gdm como raíces del
perfil, mientras el encabezado de las tres recetas dice "APARCADA por diseño" desde el
2026-08-07, verificado contra el meson.build de cada tag: las tres mueren en GTK3, que es
una de las tres deudas que el frente GNOME aparcó a propósito (GTK3 / X11 / PAM).

Dos documentos del repo decían cosas opuestas, y el que se mira primero es el grafo. El
resultado era deuda FANTASMA: escritorio-gnome reportaba 124/127 con 3 en `never` para
siempre, se leía como trabajo pendiente, y el worker las reintentaba en cada ciclo. En
esta misma sesión me hizo afirmar dos veces que GNOME estaba "a 3 recetas de cerrar".

No bloquean el escritorio: gnome-shell no depende de gnome-session ni de g-s-d, ni en
build ni para arrancar. El camino vivo es mutter → gnome-shell, lanzado por arje; el
único que pedía gnome-session era gdm.

Las recetas SIGUEN en el repo con su análisis intacto. Si algún día se autora GTK3, se
vuelven a añadir a `paquetes` y el objetivo reaparece solo.

escritorio-gnome: 119/119, CIERRA.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
2026-08-31 00:05:24 +00:00
SergioandClaude Opus 5 f8f679d038 corpus: expat-shared y libffi-shared cierran la fuga al Alpine del lab
sway es `link = "dynamic"` A PROPÓSITO —un compositor dlopea los drivers DRI de mesa en runtime— y
sale con doce NEEDED. Nueve los cubría la clausura (pixman, drm, evdev, input, udev, wayland-server,
wlroots, xkbcommon, libc), porque esas recetas ya son dinámicas. Los otros tres —libz.so.1,
libexpat.so.1, libffi.so.8— venían de recetas `--disable-shared`, así que NINGÚN artefacto sellado
producía ese `.so` y el rootfs los resolvía contra el sysroot Alpine DEL LAB.

POR QUÉ ERA PEOR QUE UNA DEP FALTANTE. Una dep ausente falla ruidosamente. Ésta no: el lab NO entra
en `hash_inputs`, así que el store daba el artefacto por bueno mientras el binario sólo arrancaba en
una máquina que tuviera Alpine debajo. La fuga era invisible para todo el sistema de medición.

`zlib-shared` ya existía en el corpus y sólo faltaba declararla. `expat-shared` y `libffi-shared` son
nuevas, calcadas del patrón: autotools con --enable-shared, sin el truco de --whole-archive que
zlib-shared necesita porque SU configure aborta bajo zig cc. SONAMEs verificados contra lo que pide
el ELF: libexpat.so.1, libffi.so.8, libz.so.1. Exactos.

NO se tocó `link` en las canónicas: entra en `hash_inputs` y habría re-hasheado expat, libffi y todo
lo que los lista en deps —fontconfig, dbus, mesa, glib, python3, los crates `-sys`— cientos de
recetas selladas por libs que ya están bien. El nombre `*-shared` es distinto del canónico, así que
conviven. Verificado antes de construir: recalculadas las 794 recetas, **0 hashes cambiados**, sólo
las 2 nuevas.

Van al CORPUS y no a incoming-wlr/ porque la resolución de deps es hermano→padre: una receta del
corpus no ve una cola. Es donde ya viven zlib-shared, fontconfig-shared, freetype-shared…

VERIFICADO CON PÍXELES, no con el log. Rootfs rehidratado desde cero (126/126), CERO ficheros de
`.dev-fs/alpine`. Cero «Error relocating». La captura tiene el MISMO sha256 que la de las libs de
Alpine: 177fea396caa9dca, 1280x720, 383 colores. Tercera vez que la evidencia sale bit a bit igual
al cambiar la procedencia — la soberanía no costó ni un píxel.

Perfil: escritorio-sway 126/128. Faltan strace (linux-headers del lab) y rsync (404 de upstream).

QUEDA UN RESTO: el loader `/lib/ld-musl-x86_64.so.1` todavía se toma del host. `recipes/musl.toml`
existe en el corpus pero está EN DEUDA y fuera de todo perfil — mismo agujero de declaración, un
nivel más abajo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
2026-08-26 18:42:11 +00:00
SergioandClaude Opus 5 428ad80b24 wlr: fuentes y XKB DECLARADAS en el perfil — la captura sale bit a bit idéntica
`dejavu-fonts` y `xkeyboard-config` entran en `recipes/incoming-wlr/` y en las raíces de
`escritorio-sway`. La clausura pasa de 123 a 125 y ambas ya estaban selladas: cero builds.

POR QUÉ TENÍAN QUE SER RAÍCES Y NO PODÍAN LLEGAR POR CLAUSURA. Son DATOS, no binarios: ninguna
receta depende de una fuente ni de un mapa de teclado para COMPILAR. El grafo puede seguir todas las
aristas que quiera y no va a alcanzarlas nunca. Si no se declaran, no están — y el perfil sigue
dando 100%, porque mide la clausura de las raíces declaradas.

Copias BYTE-IDÉNTICAS a las de incoming-kde, a propósito. Ya había tres copias iguales de
xkeyboard-config (kde, gnome, cosmic) y dos de dejavu-fonts (kde, cosmic); ésta es la cuarta. No se
consolidan al corpus ahora porque eso toca las colas de otros tres frentes que otros agentes
trabajan. Mantenerlas idénticas deja esa consolidación como un `git mv` trivial en vez de un merge:
cuatro ficheros iguales se unifican de un tirón, cuatro casi-iguales exigen revisión.

VERIFICADO de punta a punta, no por el log: rootfs rehidratado DESDE CERO con sólo lo que la
clausura declara (123/123), y `sway-headless.sh` sin una línea de inyección manual de fuentes ni
XKB. Cero errores de xkbcommon, cero de fcft. La captura resultante tiene el MISMO sha256 que la de
ayer con todo inyectado a mano — 1280x720, 383 colores. La declaración es exactamente equivalente a
la inyección, y el pipeline reproduce.

Queda como deuda real (no de declaración): libz.so.1 / libexpat.so.1 / libffi.so.8, que sway pide
por estar enlazado dinámico y el corpus sólo produce como `.a`. Y `fonts.conf` NO era deuda: lo trae
el propio artefacto de fontconfig.

Perfil: escritorio-sway 123/125. Los 2 que faltan son strace (choca con los linux-headers del lab) y
rsync (404 de upstream), ninguno del escritorio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
2026-08-26 18:03:46 +00:00
SergioandClaude Opus 5 64e1ad2942 khipu: el grafo de sway llevaba 17 días congelado — el latido nunca corría --wlr
`build-state.py --wlr` existe desde que se abrió el frente, y su propio comentario dice que sin él
el frente es INVISIBLE para el khipu. La línea nunca se agregó a `cosecha-cron.sh`: el latido
regeneraba {base,kde,gnome,cosmic} y saltaba wlr.

Medido: build-state-wlr.json quedó fijo el 2026-08-09 anunciando `escritorio-sway 121/121`. Hoy,
regenerado, da 108/123. En el medio se re-hashearon freetype, make, python3 y libpng-pic, que
arrastraron a deuda a 14 dependientes (fuzzel, yambar, swaybg, swaylock, slurp, sed, strace, tzdata,
rsync, pciutils, procs, sd, skim, tokei). Nadie lo vio porque el número que se mira estaba perfecto.

Un grafo que nadie regenera no envejece en cualquier dirección: envejece hacia el OPTIMISMO. Sólo
puede sobreestimar lo sellado, porque el paso del tiempo únicamente invalida hashes, nunca los crea.

Va también `foot` a las raíces de escritorio-sway. El comentario de targets.toml afirmaba que entraba
por la clausura de `cli`; el grafo lo desmiente — `perfil.cli` no lo lista y ningún perfil lo
arrastraba. El escritorio daba 121/121 SIN EMULADOR DE TERMINAL. Se vio al hidratar la clausura para
armar la imagen, no antes: la métrica mide la clausura de las raíces DECLARADAS, y es estructuralmente
ciega a lo que falta en la declaración.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v9ozVm44p6DB7EMXeZK4o
2026-08-26 16:02:04 +00:00
sergioandClaude Opus 5 55dd36d97a perfil escritorio-sway: 121/121 CERRADO — la cuarta imagen de escritorio, y la más barata
Declarado en targets.toml y verificado con el grafo: **121 nodos, 121 sellados, falta 0**. Es
una imagen construible hoy, no una intención.

Nueve raíces (sway, yambar, fuzzel, swaybg, swaylock, swayidle, grim, slurp, wl-clipboard) más
`hereda = ["cli"]`, porque un WM sin userland debajo no se usa: hacen falta la terminal y las
herramientas. `foot` no se lista porque ya entra por la clausura de `cli`. El resto de la
imagen —wlroots, mesa, wayland, libinput, seatd, cairo, pango, fcft…— lo calcula el grafo
siguiendo las aristas: escribir la clausura a mano es justo lo que targets.toml existe para
evitar.

LA COMPARACIÓN QUE VALE: KDE y GNOME fueron campañas de semanas porque debajo tienen una torre
de C (Qt entero, GTK, la cascada de mesa). Esto es una decena de binarios pequeños sobre
wlroots y salió en una noche. Ya estaba escrito en el SDD 20 como hipótesis —«los WMs ligeros
son la mejor relación esfuerzo/resultado que queda»—; ahora está medido.

`--wlr` en build-state.py, con su flag propio por la misma razón que COSMIC: sin él el frente
es INVISIBLE para el khipu, y `yupana radio` sobre una receta compartida (wayland, libinput,
pixman, mesa, libxkbcommon…) no reportaría la imagen sway entre las afectadas — o sea que el
próximo que toque una de ésas mediría de menos y creería que no rompe nada.

Queda lo que no puede decidir el grafo: probarlo en QEMU CON PANTALLA. La regla ya pagada cara
es que las imágenes de escritorio no se validan con `-nographic`, porque el compositor puede
«arrancar» en los logs y no pintar nada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 22:01:24 -04:00
sergioandClaude Opus 5 8d10bccf34 🔇 wireplumber: construido, corriendo… y NO era el bloqueo
b3:b8f3baf0, selló a la primera. Arranca desde cosmic-start ("wireplumber OK (pid
192) — hay gestor de sesión"), se conecta al grafo (dos clientes en wpctl status) y
wpctl pasa a mostrar una sección Video que antes no existía.

Y el Start de ScreenCast SIGUE sin Response a los 60s. Mismo resultado exacto.

LA HIPÓTESIS QUEDA MATADA POR MEDICIÓN. "Falta el gestor de sesión que mueva el nodo
de Paused a Streaming" era mía y era razonable; no era cierta. Queda escrita junto a
su refutación porque una hipótesis descartada CON EVIDENCIA vale más que una lista de
sospechosos: acota el próximo paso a lo que queda, que es el backend mismo.

El dato nuevo, que es por dónde seguir: con wireplumber corriendo, `wpctl status`
lista `Video → Streams` VACÍO durante el handshake. O sea que el nodo
`cosmic-screencast` que SÍ existe en `pw-cli ls Node` no llega a wireplumber como
stream gestionado. Próximo movimiento: RUST_LOG=trace sobre screencast_thread, no
otra dep.

── por qué receta propia y no la de GNOME ──────────────────────────────────────
La de incoming-gnome funciona pero NEEDea libglib/libgobject/libpipewire de la ISLA
DINÁMICA de GNOME: traerla metería una segunda glib y una segunda pipewire en la
imagen. De 19 deps, 14 dan hash idéntico; las que divergen divergen a propósito —
sobre todo `pipewire`, que acá apunta a la de COSMIC (338d1d8c), la que el escritorio
realmente ARRANCA. Un gestor de sesión contra otra pipewire que la que corre no
gestiona nada.

El cambio de fondo es glib → glib-shared. COSMIC usa la glib ESTÁTICA del corpus, que
le alcanza al portal porque es un ejecutable. Acá no: wireplumber produce un .so y 17
módulos, y libglib-2.0.a tiene 44.275 reubicaciones R_X86_64_32/32S ⇒ no es PIC. La
regla del frente GNOME (readelf -r ANTES de gastar el build) ahorró uno.

Y la salida no fue una campaña nueva sino una MEDICIÓN: incoming-kde/glib-shared
copiada a esta cola resuelve al MISMO hash (0584ce1f) y ya estaba sellada ⇒ cero
rebuild. Idem pcre2-shared, lua y libelogind. Las dos glib coexisten sin pisarse (.a
y .so son ficheros distintos) y el portal conserva sus 3 NEEDED.

escritorio-cosmic: 84 → 89 recetas, 0 faltantes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 10:54:54 -04:00
sergioandClaude Opus 5 ee037912a9 🎥 ScreenCast: el stream SE CREA — nodo cosmic-screencast vivo en pipewire
La receta de la sonda (b3:af49d32d) y lo que midió. Es la única receta de la campaña
cuyo producto no es una pieza del escritorio sino un instrumento para medirlo: entra
a la imagen porque un handshake de portal sólo se puede ejercer DESDE la sesión, con
bus y compositor vivos, y sale ESTÁTICA (0 NEEDED) porque un instrumento no debe
depender de aquello que mide.

  [1/4] CreateSession  → Response 0, session_handle                    ✓
  [2/4] SelectSources  → Response 0                                   ✓
  [3/4] Start          → el backend ABRE "Share your screen", con
                         miniatura EN VIVO del framebuffer y el output
                         Virtual-1; se elige, se pulsa Share…
                         y NO llega Response en 60s                    ✗

El log del backend da la línea exacta:
  screencast_thread: state-changed 'Connecting' -> 'Paused'

Y `pw-cli ls Node` durante la espera da el veredicto INDEPENDIENTE:
  node.name = "cosmic-screencast"   media.class = "Video/Source"

EL NODO DE VIDEO EXISTE EN EL GRAFO DE PIPEWIRE. La cadena entera —cliente →
frontend → backend → compositor → demonio— funciona hasta crear y negociar el stream.
Lo único que no ocurre es el Response de vuelta tras quedar en `Paused`. Eso es un
lugar muy distinto del de esta mañana ("no hay demonio con quien negociar").

Sospecha para el próximo paso, y es HIPÓTESIS no medición: falta `wireplumber`. Sin
gestor de sesión nadie mueve el nodo de Paused a Streaming y el backend parece
esperarlo. Construirlo la confirma o la mata.

Gotchas que costaron corridas y quedan escritos:
 · matar el backend se lleva puesto al frontend (ambos pierden dueño del bus);
 · el lanzador busca por NOMBRE VISIBLE: `cosmic-term` no matchea, `Terminal` sí —
   escribir el nombre del binario abre otra app;
 · `| head -N` bufferiza y deja la terminal en blanco: parece colgada y está esperando;
 · pkg-config OMITE los -L de dirs "estándar" y zig cc cross NO los busca ⇒
   `-ldbus-1` pelado da "unable to find static system library", que suena a librería
   faltante cuando lo que falta es la ruta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 10:28:14 -04:00
sergioandClaude Opus 5 1e786aaf42 🚪 cosmic: el BACKEND del portal sella (b3:7ad0f124) — cadena completa en la imagen
69 min. NEEDED: libgbm, libpipewire, libxkbcommon, libc. Instala el binario
en /usr/libexec, el .service de D-Bus con @libexecdir@ sustituido, el
cosmic.portal que declara las cinco interfaces
(Access/FileChooser/Screenshot/Settings/ScreenCast) y sus iconos.

clang18 se construyó en la granja y se cosechó al laptop (b3:62c6bfb5), pero
el backend NO se pudo construir allá: el worker no tiene la cola COSMIC
horneada —el snapshot golden es del 2026-07-15 y toda esta cola es
posterior— así que intentó reconstruir pipewire y murió buscando glib. Es la
regla de «rootfs laptop ≠ worker» pero con el STORE: lo que en el laptop es
cache-hit, en el worker es un build entero con sus propias deps.

Y destapó que la receta de pipewire NO lleva --wrap-mode=nodownload: ante
una dep faltante intenta bajarse un subproyecto de internet y, sin red, el
error que sale no es «te falta glib» sino «Unhandled python exception / This
is a Meson bug». Queda anotado como deuda.

--offline SIN --locked, y no es descuido: el sed del parche corre en la fase
compile, o sea DESPUÉS del cargo vendor, así que cargo ve el Cargo.toml
cambiado y con --locked se niega. Quitar una feature sólo puede ENCOGER el
conjunto de crates, así que lo que haga falta ya está vendorizado; la
hermeticidad la da --offline + el árbol vendorizado, no el --locked.

Dos raíces en el perfil y en la hidratación, porque son DOS procesos que se
encuentran por D-Bus en runtime y ningún [deps] los relaciona. Imagen en 83
recetas (eran 74).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 06:04:13 -04:00
sergioandClaude Opus 5 520271ea2f 📷 cosmic: cosmic-screenshot sellada y ROTA A PROPÓSITO — y la glib compartida YA existe
b3:8b57ec9b, 9 minutos, NEEDED = libc.so y nada más. Un solo NEEDED, y el
único `[deps] build = []` de la campaña: 130 líneas de Rust que no dibujan
ni hablan wayland.

Se incluye en la imagen sabiendo que falla, para que el hueco sea MEDIBLE en
vez de supuesto. Error exacto capturado corriéndola desde cosmic-term dentro
de la VM:

  panicked at src/main.rs:76:10: failed to send screenshot request:
  Portal(ZBus(MethodError(ServiceUnknown, "The name
  org.freedesktop.portal.Desktop was not provided by any .service files")))

Misma forma que la deuda de org.freedesktop.locale1: un nombre de D-Bus que
nadie sirve.

LA CADENA DEL PORTAL SON TRES ESLABONES, NO DOS. Medido en el fuente:
xdg-desktop-portal-cosmic declara DBUS_NAME =
"org.freedesktop.impl.portal.desktop.cosmic" (src/main.rs:27) — es BACKEND,
no toma el nombre que los clientes buscan. Falta también el frontend
xdg-desktop-portal, y ninguno de los dos está en ninguna cola.

⚠ CORRECCIÓN AL PROPIO RUNBOOK: decía que `gvfs` exigía «una campaña
propia». Es falso desde que KDE selló las piezas. Verificado con `hammer
hash` desde las dos colas —el método correcto para saber si una receta se
comparte—: glib-shared 2.88.1 (b3:0584ce1f) y pcre2-shared (b3:f254abaa)
resuelven IDÉNTICO desde incoming-cosmic ⇒ cero rebuild, los artefactos
sirven tal cual. zlib-shared ya estaba en la cola. libffi entra estático
dentro de la .so (su Requires.private lo ignora pkg-config fuera del modo
estático).

Lo que queda de `gvfs` NO es técnico sino una decisión: mete una segunda
glib en la imagen —lo que GNOME midió y evitó— y re-hashea cosmic-files, que
arrastra a cosmic-term y cosmic-edit porque lo usan como crate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 23:40:00 -04:00
sergioandClaude Opus 5 b51f90ffea 🛍 cosmic: cosmic-store — sella (b3:a06437f7), y BoringSSL cruza a musl sin cmake
La quinta aplicación, y la primera de la suite que choca con que hammer es
OTRA distro: una tienda es la cara de un gestor de paquetes y el de abajo no
es el suyo. De los cuatro backends de `src/backend/`:

  flatpak     pide libflatpak (GObject) ⇒ la cadena glib COMPARTIDA más
              ostree/libsoup/gpgme: la torre de C que esta campaña no tiene
  packagekit  Rust puro (packagekit-zbus) pero necesita el DEMONIO en el bus
  rpm-ostree  irrelevante
  pkgar       el único que arranca: ni demonio ni enlace, lee el AppStream
              del sistema — o sea los .metainfo.xml que instalan las recetas

ALCANCE HONESTO: con pkgar NAVEGA, no INSTALA. Ninguna operación está
cableada al .swm de hammer (Etapa F). El camino para arreglarlo quedó
identificado y no cuesta un solo .so de C: servir
`org.freedesktop.PackageKit` sobre .swm, mismo patrón que
arje-logind-compat.

HALLAZGO REUTILIZABLE: `aws-lc-sys` (BoringSSL, C + ensamblador) compiló
bajo zig-cc/musl SIN cmake y SIN perl — ninguno de los dos está en los 182
binarios de work/builder-rootfs/usr/bin. Tomó el camino de bindings
pregenerados (`cargo:rustc-cfg=universal`) y compiló los 21 MB de
libaws_lc_crypto.a con el crate `cc`. El `Compiling cmake v0.1.58` del log
es el crate ayudante, que se compila aunque el binario no se invoque. Esto
destraba cualquier receta futura que entre con rustls por defecto, que hoy
es casi todo lo que use reqwest.

NEEDED: libxkbcommon.so.0 y libc.so, igual que cosmic-edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 22:42:53 -04:00
sergioandClaude Opus 5 d5f798a6b1 ✎ cosmic: cosmic-edit — la 4ª aplicación, y el Cargo.lock miente por exceso
El editor de texto sella en b3:2323a325, igual que el dry-run. Es la más
barata de las cuatro: depende de `cosmic-files` como CRATE con
`default-features = false`, así que su árbol ya estaba compilado (68 min de
enlace, no de compilación desde cero).

Lo que aprendió esta receta, y que corrige un borde de la regla anterior:
**el `Cargo.lock` lista lo POSIBLE, no lo ENCENDIDO**. Aparecen `gio-sys`,
`glib-sys` y `gobject-sys` —que en cualquier otro paquete mandarían a
declarar glib y de ahí a la cadena compartida— y sin embargo entran sólo por
la feature `gvfs`, que se apaga. El lock dice el universo; las features
dicen el recorte.

Features: `--no-default-features --features dbus-config,wayland`. Fuera
`gvfs` (pide las glib compartidas, el corpus las tiene estáticas) y `wgpu`
(el que desbordó el filesystem dos veces en cosmic-files).

La doble barra `pop-os//` que mordió en applets y settings acá está
comentada en el Cargo.toml; verificado con grep ANTES del build.

Evidencia: el binario enlaza sólo `libxkbcommon.so.0` y `libc.so` — dos
NEEDED, el mínimo de la suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 20:21:56 -04:00
sergioandClaude Opus 5 232f23c5a8 ⚙ cosmic: cosmic-settings — el panel de control, y NO arrastra medio sistema
Sellada b3:716dac53. Cola 33/33, escritorio-cosmic 71/71. Abre desde el lanzador con la
barra lateral entera (Red, Bluetooth, Accesibilidad, Escritorio, Pantallas, Sonido,
Energía, Entrada, Aplicaciones, Fecha y hora, Sistema y cuentas). Tarda ~20s con render
por software; no está colgado.

La sorpresa buena: nada de NetworkManager, libpulse, udisks ni accountsservice — habla con
los daemons por zbus, que es Rust puro. Se verificó ANTES de escribir la receta listando
los crates -sys del Cargo.lock, que es donde vive la verdad sobre qué C hace falta: sólo
drm-sys, input-sys, libudev-sys, dav1d-sys, wayland-sys (dlopen) y gettext-sys. Regla
barata: grep '^name = ".*-sys"' Cargo.lock antes de adivinar deps por lo que el programa
hace.

Volvió a morder la doble barra de cosmic-protocols// (404 de GitHub, muere en el fetch con
tres reintentos). Segunda víctima ⇒ es patrón de la suite, no rareza de un repo.

Instala 32 .desktop, uno por página. Y default_schema va con find, no cp plano: es el árbol
<Componente>/v1/<clave> de cosmic-config, donde cada clave es un fichero con su nombre.

Deuda que deja su propio log: org.freedesktop.locale1 no lo sirve nadie ⇒ la página de
idioma no podrá cambiar nada. Mismo tipo que login1, mismo arreglo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 17:21:53 -04:00
sergioandClaude Opus 5 23c0e82527 📁 cosmic: cosmic-files — la 2ª aplicación, y «ya está compilado» era falso por una línea
Sellada b3:48d2b072. Cola 32/32, escritorio-cosmic 70/70. Abre desde la biblioteca y lista
el sistema de ficheros real de hammer (bin 72 ítems, dev, ente, etc, lib, lost+found).

Entró creyendo que era casi gratis porque cosmic-term ya la compila como crate. No lo era:
cosmic-term la declara con default-features = false. Lo que un paquete cuesta depende de
con qué features lo pide quien lo usa, así que «ya se compiló» puede ser falso.

Dos features apagadas, las dos por medición:
· gvfs trae gio/glib y el enlace final pide las glib COMPARTIDAS; el corpus sólo las tiene
  estáticas. Se pierden montajes remotos, no la navegación local. Por eso tampoco se
  construye cosmic-files-applet: su Cargo.toml fija gvfs a mano.
· wgpu desbordó el filesystem dos veces (No space left on device en /src/target) con
  wgpu+naga+ash+glow+spirv a codegen-units=1. Sin él el árbol queda en ~2 GB, y no se
  pierde nada: el resto de la suite pinta con tiny-skia y la imagen no tiene GPU.

Y la regla que dejó el intento con glib declarado: el error fue «Package libpcre2-8,
required by glib-2.0, not found» — el que falla no es la dep sino lo que su .pc declara en
Requires. Declarar una dep trae su artefacto, no su clausura de pkg-config; se lee con
grep ^Requires sobre los .pc ANTES de gastar el build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 13:28:42 -04:00
sergioandClaude Opus 5 e39610588d 🖥 cosmic: cosmic-term — la primera APLICACIÓN, y la biblioteca deja de estar vacía
Sellada a la primera (b3:d0ea1c1e). Cola 31/31, escritorio-cosmic 69/69.

Dentro de la ventana: bash-5.3#, uname -srm devuelve Linux 6.16.12 x86_64 —el kernel
propio de hammer— y qalc -t '6*7' devuelve 42, o sea la calculadora que empaquetamos una
hora antes. Se abre con click en su icono en la biblioteca, que hasta hoy salía vacía con
razón: los 20 .desktop de la imagen eran NoDisplay=true. Éste no lo es.

Lo que arrastra y no se adivina: su Cargo.toml depende de cosmic-files, o sea que el
gestor de ficheros entra como LIBRERÍA (selector y drag-and-drop) y compilar la terminal
compila medio gestor de ficheros. En esta suite las aplicaciones se usan unas a otras como
crates, y el grafo de Cargo no se parece al mapa de componentes. De paso, empaquetar
cosmic-files como aplicación queda casi gratis.

wgpu viene en default, al revés que el resto de los clientes: se deja, porque wgpu y naga
ya se habían compilado enteros para cosmic-workspaces y apartarse de las features por
defecto es apartarse de la única combinación que upstream prueba. Cierra con dos NEEDED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 06:32:27 -04:00
sergioandClaude Opus 5 03ec0e6827 cosmic: libqalculate + gmp + mpfr — qalc anda, y el plugin del lanzador igual no lo puede usar
Las tres selladas a la primera (gmp b3:d99a0a5d, mpfr b3:13982d20, libqalculate
b3:6bb435a7). La cadena entera desde una tecla está en el catálogo: Super →
cosmic-launcher → pop-launcher → plugin calc → qalc → mpfr → gmp. Cola 30/30,
escritorio-cosmic 68/68.

qalc FUNCIONA: -t '2+2' da 4, -f fichero anda, e interactivo sobre terminal calcula.
Pero el plugin lo invoca SIN expresión, con stdin en tubería, le escribe la cuenta y
cierra — y en ese modo nuestro binario no lee nada. Acotado a que, 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.

Descartado midiendo, para no repetirlo: no es el toolchain (una sonda estática musl del
sandbox lee la tubería en C y en C++); no es readline vs no-readline (sin ella es peor:
no lee nunca); no es la versión de readline (una 8.3 sombra no movió el síntoma, y se
descartó en vez de dejar una receta duplicada inútil); y no es el descriptor 0, porque
Could not open "/dev/stdin". —el mismo pipe reabierto por ruta— sí funciona. El que no sirve es
el FILE* stdin, y ahí queda la pista.

Dos gotchas del empaquetado: gmp va con --disable-assembly (elige rutinas mirando la CPU
de quien compila, o sea hornearía la ISA del laptop en el hash), y el configure de
libqalculate no encuentra readline porque prueba -lncurses/-lcurses/-ltinfo y el corpus
sólo empaqueta libncursesw.a — los alias van en un directorio local, no ensuciando
/usr/lib, que es la evidencia de qué se declaró.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 04:47:55 -04:00
sergioandClaude Opus 5 505eaf5e44 cosmic: pop-launcher — el lanzador eran DOS programas, y ahora Super abre
cosmic-launcher sólo dibuja: manda lo tecleado por stdin a un proceso hijo, pop-launcher,
que es quien busca. Sin ese binario la ventana no tiene qué mostrar y no se muestra —
mismo modo de falla que el panel sin applets: falta un HIJO, no una librería, y el log
del padre se lee sano.

Vive en otro repo (pop-os/launcher), fuera del pin epoch-N. La versión la fija el
Cargo.lock de cosmic-launcher (rev a332a3a7 de la 1.2.7), no el último tag: el lock es
lo que garantiza que hablen el mismo protocolo. Tarball por commit, porque no hay tag que
nombre esa rev. Selló a la primera, b3:64a5cbaa, con un solo NEEDED: libc.so.

Verificado de punta a punta: Super abre el lanzador, «?» lista los seis plugins con su
sintaxis (o sea que la enumeración y el IPC JSON-sobre-stdio andan) y «=2+2» LANZA el
plugin, que contesta «qalc command is not installed». Esa respuesta es la mejor prueba
disponible: el hijo corrió, evaluó y devolvió fila. Falta libqalculate, que es un
paquete, no un problema.

Cola 27/27, escritorio-cosmic 63/63.

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