Commit Graph
305 Commits
Author SHA1 Message Date
sergioandClaude Opus 5 6263d24788 store-gc + farm-sync: cortar el BUCLE DE CHURN de 24 G que hacía inútil la poda
Podé el store por la mañana: 362 artefactos superados, 24 G. Lo volví a podar por la tarde:
362 artefactos, 24 G. Comparados los dos manifiestos, **solapamiento del 100%: son los
MISMOS 362**. No era casualidad ni recuento nuevo — es un bucle.

LA CAUSA no estaba en el gc sino en `farm/farm-sync.sh`, que bajaba el store del worker
ENTERO sin filtro. El worker nunca se poda, así que conservaba los superados y nos los
devolvía en cada cosecha. Podar → cosechar → vuelven → podar. El disco pagaba 24 G por
vuelta y el gc informaba éxito cada vez, lo que es peor que fallar: parecía que se avanzaba.

Es la misma familia de fallo que el gc que reportaba borrados falsos, sólo que un nivel más
arriba: entonces el borrado no ocurría, ahora ocurre y se deshace solo. En los dos casos el
mensaje de éxito era cierto y engañoso a la vez.

EL ARREGLO es darle memoria al sistema. `store-gc.sh` acumula lo podado en
`work/store-gc-superados.txt` y `farm-sync.sh` lo usa como `--exclude-from` al bajar el
store del worker. Es seguro porque el store es CAS: los nombres `<hash>-<paquete>` son
inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda —
y si retrocediera, se reconstruye, que es barato.

Disco: 159 G → 182 G tras la poda de hoy, y esta vez debería quedarse.

De paso, en el respaldo: rsync 24 («ficheros del origen desaparecieron») NO es un fallo, es
justo lo que pasa al podar mientras se sube. El bucle de reintentos lo tomaba por error real
y habría parado el respaldo entero por algo inofensivo — y encima cuando el disco aprieta,
que es cuando menos conviene quedarse sin copia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:11:31 -04:00
sergioandClaude Opus 5 89c02db639 respaldo: rsync 24 (ficheros desaparecidos) no es un fallo — es podar mientras se sube
El bucle de reintentos trataba el 24 como «error que no es de red» y habría PARADO el
respaldo entero. Pero 24 significa que ficheros del origen desaparecieron durante la
transferencia, que en este proyecto es una combinación NORMAL: el respaldo dura ~19 h y
`store-gc.sh` es la válvula del disco, así que podar mientras se sube va a pasar. Y pasaría
justo cuando el disco aprieta, o sea cuando menos conviene quedarse sin copia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:06:26 -04:00
sergioandClaude Opus 5 be4e91086c licencias: el texto dentro de la imagen, con veto — y el SDD 19/20 se equivocaba de sitio
═══ LA CORRECCIÓN DE FONDO: no es `pack`, son las IMÁGENES ═══
El SDD 19 y el 20 decían «inyectar el texto de la licencia en `hammer pack`, que es aguas
abajo del ArtifactHash y sale gratis». La conclusión sobre el coste era correcta pero el
SITIO estaba mal, y por una razón que sólo se ve leyendo el código: `pack` produce un
`.swm`, que es una RECETA DE TRANSFORMACIÓN sobre fuente pública y por diseño explícito
«NUNCA transporta binarios cocidos»; e `install` REPRODUCE construyendo, con cache-hit del
corpus, en vez de bajar binarios. O sea que **el canal de paquetes de hammer no distribuye
binarios** y la obligación de acompañar-el-binario ahí casi no aplica.

Donde sí aplica, con toda su fuerza, es en la IMAGEN INSTALABLE: su rootfs se puebla con
`hammer install` sobre un prefix y de ahí sale un USB/ISO lleno de binarios que se le da a
alguien. Ése es el punto de entrega. (La obligación del espejo de FUENTES es otra y sigue
entera: las recetas apuntan a URLs de terceros que se caen.)

═══ LO QUE ENTRA ═══
· `scripts/licencias-textos.sh` + `licenses/`: los 44 textos CANÓNICOS de SPDX
  (spdx/license-list-data), uno por identificador realmente en uso — la lista se saca de las
  recetas, no de un fichero escrito a mano que se desincroniza. 764K.
· `scripts/licencias-rootfs.sh`: escribe /usr/share/licenses/<pkg>/ con el texto de CADA
  licencia de la expresión (`Unlicense OR MIT` ⇒ los DOS textos: en un OR el destinatario
  elige, y entregar uno solo le quita una opción que el autor le concedió), más un
  MANIFEST.tsv. Medido: 1,0 MB para el perfil `cli` de 44 paquetes.

═══ Y SOBRE TODO: ES UN VETO ═══
Sale ≠0 si algún paquete del rootfs no declara licencia. Un informe que no puede bloquear no
cambia lo que ocurre; esto corta el paso. Y se ganó el sueldo al primer intento: reveló que
los perfiles `base` y `cli` —los dos más simples, los que primero se publicarían— llevaban
10 y 12 paquetes con binarios y licencia desconocida. Curados los 10 que se podían afirmar
(doas, iputils, less, mandoc, procps-ng, rsync, strace, sudo, tree, usbutils); ambos perfiles
bajan a 2.

Esos 2 NO se rellenan a propósito, y quedan vetando:
 · lsof   — licencia propia del proyecto, sin identificador SPDX. Toca `LicenseRef-lsof` con
            su texto, que ya viaja en el tarball (la receta instala su COPYING).
 · tzdata — dominio público por declaración de sus autores, y SPDX no tiene identificador
            para eso (es una AUSENCIA de licencia, no una licencia). Toca
            `LicenseRef-PublicDomain` documentado, no forzar uno de la lista.

De paso: normalizado `GPL-3.0+` (sufijo antiguo) a `GPL-3.0-or-later`, misma equivalencia
documentada que la barra de Cargo. Y el caso especial que puse para `Linux-syscall-note`
(«las excepciones viven en exceptions/») era una suposición razonable y FALSA: SPDX las
publica en el mismo `text/`; ese directorio no existe.

1063 de 1141 (93%). Hashes verificados: 0 movidos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:53:20 -04:00
sergioandClaude Opus 5 487e155f74 licencias: leer el LICENSE de verdad cuando GitHub dice «no sé» — 1053 de 1141 (92%)
Último recurso mecánico, `scripts/licencias-texto.sh`: para las 60 recetas donde la API de
GitHub devuelve NOASSERTION (el repo TIENE un LICENSE pero licensee no lo reconoce: texto
retocado, encabezado propio, dos licencias en un fichero), baja el texto y lo clasifica acá.
34 identificadas — 16 MIT, 10 Apache-2.0, 7 BSD-2-Clause, 1 BSD-3-Clause.

SÓLO PERMISIVAS, Y NO ES PEREZA. MIT, Apache-2.0, BSD, ISC, MPL-2.0 y Unlicense se
reconocen por frases inconfundibles y NO tienen variantes -only/-or-later ⇒ reconocer el
texto da el SPDX completo.

La familia GPL se deja al humano A PROPÓSITO, y la razón es sutil: el COPYING de la GPL es
IDÉNTICO tanto si el proyecto es «sólo v3» como «v3 o posterior» — lo que las distingue vive
en las cabeceras de los fuentes. Y encima el propio COPYING incluye, en su apéndice «cómo
aplicar la licencia», la frase «or (at your option) any later version», así que buscarla da
SIEMPRE positivo y parecería evidencia siendo texto de plantilla. Es exactamente la trampa
de la regla del `.a` no-PIC, donde grep contaba reubicaciones de .debug_* y mentía.

Comprobado a mano un caso que daba mala espina: el LICENSE de `conftest` empieza
«Conftest — Write tests against your config files / Copyright (C) 2019 …», que es el formato
típico de una cabecera GPL. Leído entero, dice «Licensed under the Apache License, Version
2.0». La clasificación era correcta; la sospecha, barata.

Hashes verificados sobre las 36 tocadas: idénticos.

Quedan 88, ya sin vía mecánica: los repos donde ni la API ni el texto deciden, la familia
GPL sin desambiguar, y tarballs de sitios propios (gmplib, xiph, sr.ht, codeberg…).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:06:59 -04:00
sergioandClaude Opus 5 c8d1357f06 licencias: la declaración del autor manda — y arreglo 5 recetas que ROMPÍ y commiteé
Dos cosas: una mejora de calidad y un fallo mío que hay que contar entero.

═══ EL FALLO: escribí un error JSON dentro de 5 recetas y las commiteé rotas ═══
En `a6a9d59`, cinco recetas quedaron con
    license = "{"message":"Not Found","documentation_url":"...","status":"404"}"
Causa: cuando el repo da 404 (renombrado, borrado, privado), `gh api` escribe el CUERPO DEL
ERROR en stdout y el filtro `--jq` falla, así que la variable queda valiendo el JSON. Mi
`case` sólo descartaba ""/null/NOASSERTION/other ⇒ el JSON pasó como si fuera una licencia.
Las comillas ROMPEN el TOML, y esas 5 recetas dejaron de parsear y de tener ArtifactHash.

No lo vio nadie leyendo: lo cazó la comparación de hashes, al detectar que `cargo-sort`
había cambiado. Afectadas: cargo-sort, cargo-audit, cargo-binstall, waybackurls, usbutils.

La lección no es «se me pasó un caso», es que **validé los «no sé» conocidos en vez de la
FORMA del dato**. Ahora se acepta sólo lo que tiene forma de expresión SPDX, y la barrera
está en los DOS sitios que escriben (detectar y sembrar), más un GUARDIÁN en el informe que
sale ≠0 si alguna licencia tiene forma imposible. Un fallo aguas arriba disfrazado de dato
hay que verlo sin buscarlo.

Y de paso normalizadas 11 licencias con la barra antigua de Cargo (`MIT/Apache-2.0`), que
no es SPDX válido. Traducir la barra a OR no es inferir: Cargo documentó esa equivalencia.

═══ LA MEJORA: `scripts/licencias-cargo.sh` ═══
Lee la licencia que el AUTOR declara en su Cargo.toml, pidiéndola AL TAG QUE LA RECETA
PINEA (`?ref=v<version>`, con HEAD como último recurso y anotando cuál se usó). 176
obtenidas, 167 al tag exacto. Es la fuente más autoritativa de las que usamos y corrige los
dos defectos de la detección por API de una vez:
 · las licencias DOBLES dejan de colapsar: `fd` pasa de `Apache-2.0` a `MIT OR Apache-2.0`,
   `ripgrep` a `Unlicense OR MIT`, `bat` a `MIT OR Apache-2.0`;
 · los SPDX AMBIGUOS caen de 71 a 55, porque el autor sí escribe -only/-or-later.
66 corregidas, 7 nuevas. Y aparecieron cosas que la detección había perdido: `eza` es
EUPL-1.2, una copyleft europea que se habría empaquetado creyendo otra cosa.

Jerarquía de evidencia, escrita en la cabecera del script: nombre (prohibido) < familia/URL
de fuente < API de licencias de GitHub < Cargo.toml del autor.

997 de 1141 (87%). Verificación COMPLETA sobre las 77 recetas tocadas —no una muestra—:
0 hashes cambiados, y 5 que ahora parsean y en HEAD no.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:51:09 -04:00
sergioandClaude Opus 5 5e42d201b1 respaldo: reintentar los cortes de red — el primero murió a los 2,3 G
La primera corrida real subió el cerebro (estado + repo, lo irreemplazable) y 2,3 G de
store, y entonces murió: `Connection reset by peer` / `Broken pipe`, rsync 255. Con ~19 h
de subida sobre un enlace de oficina, que la conexión se corte NO es un accidente: es lo
normal. Y un respaldo que hay que relanzar a mano cada vez que se cae no se completa nunca,
porque nadie está mirando.

Como rsync ya es incremental y `--partial-dir` conserva los trozos a medio subir,
reintentar es barato y seguro: cada intento retoma donde quedó, no reempieza. El bucle
insiste sólo ante fallos de RED (10/12/23/30/35/255) con espera creciente hasta 60 s; un
error de verdad —permisos, destino lleno, ruta inexistente— sale a la primera en vez de
repetirse 40 veces contra la misma pared.

DE PASO, UN ERROR MÍO QUE VALE REGISTRAR: comprobé el respaldo con `pgrep -f
respaldo-storagebox` y dijo que corría, cuando llevaba rato muerto — el pgrep se estaba
matcheando A SÍ MISMO, porque la cadena buscada está en la propia línea de comando del
shell que la ejecuta. Ya me había pasado hoy con el worker. La comprobación buena es mirar
lo que el proceso PRODUCE (bytes en el destino, última línea del log), no si hay un proceso
vivo. Es la misma lección que el guardián de store-gc: un `rm` que devuelve 0 no prueba que
el fichero se fue, y un proceso vivo no prueba que esté avanzando.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:30:58 -04:00
sergioandClaude Opus 5 a6a9d59e4f licencias: 936 de 1141 (82%) — detección por evidencia, con sus dos imprecisiones declaradas
Esta mañana eran 0. Tras la tabla curada (228) y este paso, 936. Ninguna receta se
re-hasheó: verificado en 30 de las 708 tocadas, 30 hashes idénticos, 0 cambiados.

DE DÓNDE SALE EL DATO, Y POR QUÉ NO ES ADIVINAR. `scripts/licencias-detectar.sh` consulta
la API /repos/{o}/{r}/license de GitHub para las 673 recetas cuya fuente vive allí. Eso
devuelve el resultado de DETECTAR el fichero LICENSE que el repo tiene de verdad
(licensee), no una etiqueta escrita a mano en una web: es la misma evidencia que veríamos
abriendo el tarball, obtenida sin bajar 673 tarballs por un enlace de 8 Mbps. 610 con SPDX
definido; las 63 que GitHub marca NOASSERTION/other se DESCARTAN — un «no sé» de la fuente
se propaga como hueco, no se redondea a una licencia plausible.

Las familias no-GitHub van curadas por la URL DE FUENTE, que es la evidencia que el nombre
no da. Y ahí cometí el error simétrico al que este mismo fichero advertía: había puesto
`knighttime` entre las «herramientas Go» POR SU NOMBRE, y su tarball sale de
download.kde.org — es un Framework de KDE, LGPL. Descartar por nombre falla igual que
aceptar por nombre. Corregido en la cabecera.

LAS DOS IMPRECISIONES, contables con `licencias.sh --revisar` en vez de escondidas:

 1. SPDX OBSOLETOS Y AMBIGUOS (71 recetas). GitHub devuelve `GPL-3.0`, `LGPL-2.1`,
    `AGPL-3.0`… identificadores que SPDX declaró obsoletos PRECISAMENTE porque no
    distinguen `-only` de `-or-later`, y esa diferencia decide con qué se puede combinar el
    paquete. NO se normalizan a ciegas: mapear GPL-3.0 → GPL-3.0-or-later sería inventar el
    dato que falta. Quedan marcadas para resolver mirando el fuente.

 2. LICENCIAS DOBLES COLAPSADAS. La API devuelve UNA sola licencia y muchos proyectos Rust
    son «MIT OR Apache-2.0» (p.ej. `fd` quedó como Apache-2.0). No es falso —cumplir una de
    las opciones concedidas basta— pero es incompleto. Lo resuelve el cierre estructural:
    leer el campo `license` del Cargo.toml en la fase de fetch.

Y un fallo que habría escrito basura en silencio: `licencias-detectadas.tsv` tiene TRES
columnas (añade el owner/repo consultado, para poder auditar) y el sembrador leía dos, así
que la licencia se habría llevado pegado el slug — `license = "MIT<TAB>owner/repo"`, sin
que nada lo validara. Arreglado antes de sembrar.

Quedan 205 sin licencia y 71 por desambiguar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:26:22 -04:00
sergioandClaude Opus 5 346cd59706 licencias: campo license en la receta — de 0 a 228 de 1141, sin re-hashear nada
La deuda legal bloqueante del SDD 19 §2.1. Medido hoy: **0 de 1141 recetas** declaraban
licencia, no «5 de 771» como decía el informe anterior. Los dos números estaban mal: los
«5» eran falsos positivos de `grep license` (el paquete `addlicense`, el paquete
`cargo-bundle-licenses`, una línea `install .../share/licenses/` y un comentario), y las
recetas son 1141. Contar con `grep -l <palabra>` sobre TOML cuenta comentarios y nombres,
no campos; `scripts/licencias.sh` cuenta el campo de verdad (clave en la raíz, antes del
primer `[table]`).

LO QUE HACE LA DEUDA PAGABLE: `Recipe::hash_inputs` es una LISTA BLANCA — sólo entran
source, compiler, target, link, patches, flags, phases y deps. `license` no entra, igual
que `evidence` y `slots`. Por eso se puede poblar en las recetas YA SELLADAS sin mover un
solo ArtifactHash. Verificado, no supuesto: en 40 recetas modificadas se comparó el hash
con y sin la línea — 40 idénticos, 0 cambiados. Si el campo entrara al hash, declarar la
licencia costaría reconstruir el corpus entero y no se haría nunca. Clavado con el test
`licencia_round_trip_y_no_afecta_el_hash`.

TRAMPA DE TOML: una clave suelta después de un `[table]` pertenece a esa tabla. Puesta al
final del fichero, `license` acaba dentro de `[deps]` y se pierde EN SILENCIO, porque serde
ignora los campos que no conoce — no hay error, simplemente no está. Va arriba, junto a
`name` y `version`; el sembrador la inserta tras `version`.

NO SE ADIVINA. Declarar mal una licencia es peor que dejarla vacía: convierte un hueco
visible en una afirmación falsa. Sólo se puebla desde una tabla curada entrada por entrada
(`docs/licencias-conocidas.tsv`); lo que no tiene evidencia queda vacío y se CUENTA.
Concretamente se descartó el atajo «k* = KDE ⇒ LGPL»: en este catálogo `kail`, `kind`,
`ko`, `kopia`, `krew`, `kustomize`, `kyverno`, `katana`, `kibi`, `kmon` y toda la familia
`kube*` son herramientas Go sin relación con KDE. El nombre no es evidencia.

Quedan 913, casi todas CLIs Go/Rust importados en masa — y ésas sí son automatizables con
evidencia real: Cargo.toml trae el campo `license` y los módulos Go traen su LICENSE en el
árbol. El cierre estructural es capturarlo en la fase de fetch, que ya descarga y extrae
cada tarball, y inyectar el texto en `hammer pack` (aguas abajo del ArtifactHash) en vez de
en la fase install (que sí re-hashearía).

De paso, respaldo-storagebox.sh reordenado por valor irreemplazable y con zstd: medido en
la oficina, el uplink da 8 Mbps iguales por cable y por wifi ⇒ 128 G no caben en una
sentada, así que sube primero el cerebro (estado + repo) y `--partial-dir` hace que cortar
a mitad de un artefacto no tire lo ya subido.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:24:11 -04:00
sergioandClaude Opus 5 60d8ca9bbf respaldo: Storage Box BX11 en Hetzner — no dependemos más de un laptop
El usuario pidió respaldar en «el volumen, aunque sea montándolo aquí». No se
puede: un HC Volume es un dispositivo de bloque por RED, sólo se adjunta a
servidores de Hetzner del mismo DC, y sólo a uno a la vez. harkaq-cosecha es el
disco del worker efímero y vvv está al 78%.

El producto correcto es un Storage Box: se monta desde el laptop (SSHFS/CIFS) y
habla rsync/borg/restic por SSH. BX11 = 1 TiB, €3,20/mes, sin alta. Creado en
hel1 con protección de borrado y la clave github5.

rsync plano y no borg: el store es CAS, los ficheros son inmutables y se nombran
por hash, así que incremental es exactamente lo correcto y no hay repo ni claves
que mantener. borg comprimiría 4x (79% del store es .debug_) pero 126 G en 1 TiB
no aprieta.

El store va SIN --delete a propósito: un respaldo que replica los borrados no
protege del borrado por error, y store-gc.sh acaba de demostrar que puede
equivocarse en silencio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 08:10:30 -04:00
sergioandClaude Opus 5 514ae44135 store-gc: guardián — no reportar «borrado» sin comprobarlo
El script informó «364 artefactos borrados · libres: 24G → 48G» y no había
borrado ninguno: los 364 de su propio manifiesto seguían enteros en el store.
`xargs rm -rf` salió con 0 y el echo se lo creyó. Se reprodujo: corriéndolo en
SEGUNDO PLANO el borrado no se materializa; en primer plano sí.

Da igual la causa: un rm que devuelve 0 no es prueba de que el fichero se fue.
La válvula de escape del disco estaba rota EN SILENCIO y encima reportaba
éxito, que es peor que fallar — el disco llegó al 98% mientras yo creía haber
liberado 24G.

Ahora recuenta sobre el filesystem y sale distinto de cero si sobrevivió algo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 07:44:54 -04:00
sergioandClaude Opus 5 14bed923ad granja: el worker no molía GNOME — faltaban dos colas en QUEUES
El default era fósil de cuando `incoming-gnome-onda1` era el frente entero:
listaba esa cola (6 recetas) pero NO `incoming-gnome` (86, donde viven gtk4,
mutter y gnome-shell) ni `incoming-gnome-onda2` (la isla dinámica, 22). El
worker reportaba moler GNOME y molía 6 de 114.

Se agregan las dos y se mueven las tres ANTES de incoming-kde: con 205 recetas
KDE por delante, un ciclo se consumía sin darles turno aunque estuvieran
listadas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 19:51:41 -04:00
sergioandClaude Opus 5 213ebfbb03 📶 metal: el enlace WiFi se caía solo y cortaba la depuración remota a mitad
Depurando la OptiPlex por SSH sobre un hotspot, la máquina desaparecía de la red cada
pocos minutos. No es «la WiFi anda mal»: son dos causas que se suman y ninguna deja
rastro en dmesg.

  - `udhcpc -q` (el que usa wifi-up) pide la dirección UNA vez y termina. Nadie renueva
    el lease.
  - el AP olvida a los clientes ociosos y deja de contestar ARP. Desde fuera se ve «No
    route to host» mientras la máquina se cree perfectamente conectada.

El síntoma engaña justo en la dirección peor: la máquina no reporta nada, así que parece
que se colgó lo que estabas depurando.

`wifi-keep` hace ping al gateway cada 10s, que resuelve las dos a la vez — detecta la
caída para reasociar Y mantiene viva la entrada del cliente en la tabla del AP.

Sin esto cada sesión remota se interrumpe sola y hay que volver físicamente al teclado,
que es exactamente lo que la red venía a evitar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 11:33:30 -04:00
sergioandClaude Opus 5 47d8848e37 🔌 wifi-up: sin ctrl_interface, wpa_cli no tenía puerta a la que llamar
El /tmp/wpa.conf que generaba sólo traía el bloque `network`. Sin
`ctrl_interface=/run/wpa_supplicant`, wpa_supplicant no abre socket de control y
`wpa_cli` no conecta — síntoma que parece un fallo de red y es una puerta que no
existe. Peor: deja ciego el único dato que distingue «clave mal» de «DHCP mudo»,
que es wpa_state. Y la espera de asociación que acabo de añadir dependía de wpa_cli,
o sea que habría fallado siempre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 18:27:48 -04:00
sergioandClaude Opus 5 89076a5106 📶 wifi-up: esperaba 6s fijos y culpaba a la clave de un problema de tiempo
En el OptiPlex la interfaz wlan SÍ aparece (ath10k cargó y el re-probe PCI funcionó),
pero `udhcpc` salía a preguntar 6s después de lanzar wpa_supplicant — antes de que
hubiera asociación. Sin lease, y el mensaje decía «¿clave correcta?», que manda a
buscar exactamente donde NO está el problema.

Ahora espera al HECHO: wpa_state=COMPLETED (techo 40s), informa el estado real
mientras espera, y distingue los casos en el mensaje de fallo — 4WAY_HANDSHAKE es
clave, SCANNING es que no ve la red. Y DHCP reintenta 3 veces, porque el AP a veces
ignora el primer DISCOVER justo tras asociar.

Tercera vez hoy que un `sleep` fijo produce un diagnóstico falso (initramfs 5s,
cosmic-diag 50s, wifi-up 6s). El patrón: esperar al reloj en vez de al hecho no falla
donde se prueba, falla en la máquina lenta — y miente sobre la causa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 18:22:23 -04:00
sergioandClaude Opus 5 095b876bba 📶 metal: WiFi por ath10k (QCA9377) + cosmic-diag esperaba con reloj en vez de con hecho
WIFI. El OptiPlex 3060 SÍ tiene WiFi: `pci 0000:02:00.0: [168c:0042]` = Qualcomm
Atheros QCA9377. No aparecía interfaz porque el kernel sólo llevaba IWLWIFI. Añadido
ATH10K/ATH10K_PCI a linux-metal-dual + el firmware QCA9377 a la imagen + `wifi-up`.

El detalle que hace falta: con MODULES desactivado el driver va BUILTIN y prueba el
dispositivo a los ~5s pidiendo firmware, pero el rootfs se monta a los ~13s ⇒
`Direct firmware load failed -2` y la tarjeta queda muda, sin módulo que cargar
después. `wifi-up` fuerza un re-probe PCI (remove+rescan) con /lib/firmware ya
montado. Es la MISMA carrera que la del initramfs con el USB, un piso más abajo.

COSMIC-DIAG. La 1ª versión esperaba `sleep 50` y en esa máquina la sesión tarda ~105s
en llegar al compositor ⇒ el dmesg «después» se capturó ANTES de que cosmic-comp
arrancara y salió byte a byte idéntico al de antes. El viaje se gastó sin dato, y yo
leí ese dmesg vacío como «no hubo segfault», que era una conclusión sin respaldo.
Ahora espera al HECHO: que cosmic-comp aparezca y luego desaparezca (techo de 6 min).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:16:54 -04:00
sergioandClaude Opus 5 41e8b579d5 🩹 cosmic-diag: el patrón del grep daba un falso positivo con microcode:
`grep -i "Code: "` matchea `microcode: Current revision: 0x...`, o sea que reportaba
como hallazgo una línea de arranque normal. Anclado a lo que el kernel imprime de
verdad ante una señal: segfault, general protection fault, traps:, Killed process.

Dato real de la corrida en el OptiPlex: NO hubo segfault ⇒ cosmic-comp no muere por
señal, sale por su cuenta sin escribir nada. Cambia la búsqueda.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:07:49 -04:00
sergioandClaude Opus 5 e125b7a7c3 🔬 cosmic/metal: cosmic-diag — una corrida, toda la evidencia, incluido el dmesg
En metal cada iteración cuesta un viaje físico, así que la captura no puede depender
de que alguien recuerde los pasos. `cosmic-diag` corre la sesión y deja en
/var/log/cosmic: dmesg ANTES, run.txt de la sesión, y dmesg DESPUÉS.

El dmesg de después es lo que faltaba. Medido en el OptiPlex 3060: cosmic-comp toma
seat0 (seatd lo confirma: "Opened client 1"), vive 4,9s y se DESCONECTA, pero su log
se corta a los 0,4s sin panic y con RUST_BACKTRACE=1 activo. Eso no es un error
manejado, es una señal — y si es SIGSEGV el kernel imprime
`cosmic-comp[PID]: segfault at ... in <BIBLIOTECA>`, que ES el diagnóstico.

Descartado antes de pedir otro viaje: la clausura está completa (iris_dri.so, libEGL
y cosmic-comp resuelven todos sus NEEDED en el rootfs), y los errores de tema no son
la causa — cosmic-panel sobrevive al mismo GetKey(list_button) y muere después, de
NoCompositor. Los tres clientes que "no encuentran compositor" son consecuencia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:42:02 -04:00
sergioandClaude Opus 5 b854230863 🩻 cosmic: escribir a /dev/ttyS0 COLGABA el arranque en metal (open() espera DCD)
Diagnosticado por el usuario en el OptiPlex 3060 quitando los pipes de say().

Abrir un puerto serie sin portadora BLOQUEA en open() esperando DCD, salvo que el
termios tenga CLOCAL. El kernel pone CLOCAL cuando registra el puerto COMO CONSOLA,
y eso pasa en QEMU porque el cmdline lleva `console=ttyS0,115200`. En ese metal la
firmware NO aplica el CONFIG_CMDLINE horneado —el kernel arranca con `Command line:`
VACÍO— así que ttyS0 es un puerto común, sin cable: el PRIMER say() se colgaba ahí.

Explica los tres síntomas que no cerraban:
- /var/log/cosmic se creaba (el mkdir va antes) pero el .log nunca aparecía;
- `cosmic-start > /salida.txt` daba VACÍO ⇒ parecía que el script no se ejecutaba,
  cuando estaba bloqueado en su primera línea de salida;
- la corrida terminaba siempre en «log del compositor (primer tramo)»: no era el
  último paso, era el dump() bloqueándose en el mismo sitio.

El serial no se pierde: cuando ES la consola, /dev/console YA ES ttyS0. Cuando no lo
es, no había nadie del otro lado. En metal lo que sirve es el fichero de log.

Con say() destrabado, la detección de GL quedó CONFIRMADA en metal por primera vez:
  == cosmic :: GL por HARDWARE — kernel drm=i915 + iris_dri.so (sin overrides)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:59:40 -04:00
sergioandClaude Opus 5 77f8b3e3ec 🪵 cosmic: el log de MI validación viajaba horneado en el USB, y la salida salía doble
Dos defectos que juntos hicieron ilegible el 3er viaje.

1. LOG CONTAMINADO. Los logs de sesión van a disco para sobrevivir al apagón en
   metal — bien— pero validar la imagen en QEMU ESCRIBE DENTRO DEL FICHERO DE
   IMAGEN, y después se quema. El usuario grepeó /var/log/cosmic en su máquina y
   leyó `drm=virtio-pci`: mi corrida en QEMU, no la suya. Un log viejo que parece
   nuevo es peor que no tener log.
   Fix: la imagen crea /var/log/cosmic VACÍO como último paso. Y la regla —validar
   sobre una COPIA, o regenerar antes de quemar— queda escrita donde se comete.

2. SALIDA DOBLE. `say` tee'aba a /dev/console siempre. Lanzado a mano desde el
   getty, stdout YA es la pantalla, y /dev/console es tty0 = la MISMA pantalla
   cuando el cmdline llega vacío (la firmware del OptiPlex no aplica el
   CONFIG_CMDLINE horneado). Cada línea salía dos veces, entreverada con lo que
   arje-zero escribe a consola. Eso es lo que se leía como «basura de init»: no era
   ruido ajeno, era el propio script. Ahora /dev/console sólo si `[ -t 1 ]` es falso.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:33:50 -04:00
sergioandClaude Opus 5 86645c379c 🖥 cosmic/metal: la imagen forzaba render por SOFTWARE — el viaje no podía ganar
Diagnóstico del 1er viaje al OptiPlex 3060 (Coffee Lake, UHD 630). El dmesg del
propio USB muestra que la GPU estaba PERFECTA:

  [drm] Found coffeelake (device ID 3e92) ... Initialized i915 1.6.0 on minor 0
  fbcon: i915drmfb (fb0) is primary device

Lo que fallaba era el script de arranque. `cosmic-start-qemu.sh` exportaba
LIBGL_ALWAYS_SOFTWARE=1 y MESA_LOADER_DRIVER_OVERRIDE=kms_swrast INCONDICIONAL —
correcto sobre virtio-gpu, letal sobre Intel real: la mesa del corpus va con
-Dgallium-drivers=iris -Dllvm=disabled y NO EXISTE ningún kms_swrast_dri.so ⇒ EGL
falla. Y en el mejor caso habría sido peor: si arrancaba, componía por software y
ScreenCast fallaba igual que en QEMU ⇒ el viaje NO PODÍA contestar su pregunta.

El origen es un comentario que yo escribí en metal-desktop-image.sh afirmando que
el script no tenía supuestos del emulador. Los tenía, y el nombre del fichero lo
decía. Ahora:

- `cosmic-start-qemu.sh` → `cosmic-start.sh` (se llama por lo que hace).
- El modo de GL se DETECTA con dos patas: driver DRM del kernel Y .so de mesa
  presente. Si no hay ninguna, lo DICE en vez de morir dentro de EGL.
- La imagen VERIFICA que el script instalado no fuerce kms_swrast (falla el build
  si vuelve a pasar).

Y correr cosmic-start sobre la imagen de metal POR PRIMERA VEZ (antes sólo se
validaba que llegara al prompt) destapó que le faltaba entera la preparación de
sesión que sólo tenía qemu-desktop-image.sh: sin grupo `video` no arranca seatd,
sin usuario `messagebus` no arranca el bus de SISTEMA — y sin bus de sistema NO HAY
PORTAL, o sea que portal-probe screencast no habría tenido con quién hablar aunque
el GL fuese perfecto. Copiada: arje-logind-compat + política, video, messagebus,
setuid del launch-helper, /var/run→/run, COSMIC_MODE, libc.musl-x86_64.so.1.

Además, para que el próximo fallo en metal no cueste otro viaje a ciegas:
- Los logs van a /var/log/cosmic (ext4) y no a /tmp (RAM, se evapora al apagar).
  `dump()` también, que iba SÓLO al serial — donde se perdió la explicación de los
  tres fallos de arriba.
- authorized_keys horneada: sshd escuchaba en :22 pero era INALCANZABLE
  (PasswordAuthentication no, sin clave) ⇒ ahora se depura por red.
- netup-wait: el r8169 levanta el enlace a los 20,2s y el ente sshd pedía DHCP a
  los 13,7s, sin reintento. En QEMU no se ve: virtio-net tiene carrier desde el
  instante cero.
- firmware i915 de otras generaciones (la base sólo traía tgl_*; el Coffee Lake
  pedía kbl_dmc_ver1_04.bin).

Validado arrancando como usb-storage: seatd OK, bus de sistema OK, login1 OK,
pipewire+wireplumber OK, compositor OK. La línea de GL dice la verdad en QEMU.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:03:47 -04:00
sergio 08440220b6 estado: cosecha granja 2026-08-05T19:00:17Z — avance del árbol KDE 2026-08-05 15:00:17 -04:00
sergioandClaude Opus 5 705a70c33a 🩺 EFI: el initramfs perdía la carrera contra la enumeración USB — y en silencio
Síntoma en metal ajeno: la pantalla se congela justo tras `sdc: sdc1 sdc2 sdc3 sdc4`
/ `Attached SCSI removable disk`. No estaba colgado.

Prueba de que el root nunca se montó: con `ignore_loglevel` un montaje ext4 imprime
`EXT4-fs (...): mounted filesystem` en pantalla sí o sí, y no aparecía.

Dos causas encadenadas:

1. CARRERA. El rdinit arranca en cuanto se desempaqueta el initramfs, pero el bus USB
   enumera asíncrono y tarda segundos (reset de hub, settling de 1s por dispositivo,
   scan SCSI). Los 5 reintentos de 1s no alcanzaban. En QEMU el disco es virtio y está
   desde el instante cero ⇒ la carrera NUNCA se veía en validación. Es el `rootwait`
   que no podemos usar porque no hay root= en el cmdline. Ahora espera 60s.

2. CEGUERA. El cmdline horneado es `console=tty0 console=ttyS0,115200` y /dev/console
   = la ÚLTIMA `console=` ⇒ el serial. El `exec sh` de rescate nacía invisible. Es el
   MISMO bug que costó el 1er viaje físico de KDE, una capa más temprano: allá se
   arregló para después del switch_root (getty en tty1) y el initramfs quedó ciego.
   Ahora el log del pivote va a /dev/tty0, el marcador INIT-OK también, y el rescate
   abre shell en tty1 listando los bloques visibles.

+ `timeout 15` a `hammer boot menu`: corre ANTES del exec de arje-zero ⇒ colgarse ahí
  deja un arranque sin PID1 ni pantalla, indistinguible de un kernel muerto. El
  `|| true` protegía del fallo, no del bloqueo.

Validado arrancando la imagen COMO USB (qemu-xhci + usb-storage, -serial null, con
pantalla): marcadores del pivote visibles, motd y prompt `/ #`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:54:23 -04:00
sergioandClaude Opus 5 11e6a3c78c 🔩 cosmic/metal: imagen EFI con iris HW — el viaje que cierra ScreenCast
Hermano de kde/metal-desktop-image-dual.sh, pero con la decisión INVERTIDA y ése es
el motivo de existir. La de KDE va por mesa-llvmpipe a propósito (tenía que aguantar
también una NVIDIA Pascal, cuya mesa HW sólo existe como binario Alpine ⇒ rompería
soberanía).

Acá el objetivo es cerrar ScreenCast, y el diagnóstico de hoy midió que lo que falla
es el COMPOSITOR en una llamada EGL —8 ms antes del `Paused`— porque en QEMU mesa es
kms_swrast: software, SIN exportación dmabuf. El `mesa` del corpus se construye con
-Dgallium-drivers=iris y YA está en el rootfs de COSMIC; en QEMU nunca se usaba, en
un TigerLake es el driver correcto y trae dmabuf de verdad. O sea que este viaje no
es "lo mismo pero en metal": es la única forma de saber si ScreenCast está bien.

⚠ Por eso NO se inyecta llvmpipe: mezclarlos no es aditivo — llvmpipe trae su propio
libgbm/libEGL/libgallium y sobrescribirlos DESACTIVA iris. Si la máquina no tuviera
Intel, la imagen correcta sería la de KDE, no ésta con un parche.

Y lo que NO hace falta inyectar, medido: la de KDE mete libLLVM.so.18 + libgcc_s +
libstdc++ porque el JIT de llvmpipe los NEEDea. Acá NO — iris_dri.so pide sólo
libglapi/libdrm/libz/libzstd/libc (mesa va con -Dllvm=disabled) y cosmic-comp ocho
NEEDED sin C++. CERO binarios ajenos de Alpine. Verificado con clausura ELF completa
del rootfs: 472 objetos escaneados, 42 raíces de runtime, 0 con NEEDED sin proveedor
(los 3 huecos son llvm-*/perl, herramientas de build que el escritorio no arranca).

Se instala EL MISMO cosmic-start validado en QEMU, no una variante: nada de lo que
hace es específico del emulador, y que corra el mismo fichero es lo que hace que
"validado en QEMU" signifique algo para el metal.

VALIDADO EN OVMF CON PANTALLA, que es la regla de oro que costó un USB en el 1er
viaje de KDE (-serial null para simular metal sin puerto serie): arje-zero PID 1, las
4 particiones montadas, el motd VISIBLE y el prompt `/ #` en la pantalla — o sea que
el getty de tty1 hace su trabajo. El shell responde: iris_dri.so presente,
/dev/dri/card0, y cosmic-start/portal-probe/wireplumber en PATH.

Falta quemar el USB, que es destructivo y necesita que el usuario confirme el device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 11:41:16 -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 28dc473aca 🧹 store-gc: el recolector que hammer gc no tiene — 702 artefactos, 34G
El disco llegó a 100% y el gordo no estaba en `work/`: el store guarda un artefacto
por cada SELLADO, no uno por receta. Cuando una dep cambia, el ArtifactHash de todo
lo que cuelga cambia y el sellado nuevo SE SUMA al viejo. Medido: 1996 artefactos
para 1130 recetas — 12 copias de libqalculate, 9 de qt6-qtdeclarative, 8 de gtk4.
51G de 74G eran versiones anteriores.

El conjunto VIVO es `hammer hash` sobre todas las recetas de todas las colas: la
respuesta a "¿cuál es el hash vigente HOY?", que el store solo no puede dar. Lo que
no está ahí es rancio — pero rancio son DOS cosas muy distintas, y confundirlas es
la diferencia entre podar y perder:

  SUPERADO — su nombre TIENE un artefacto vigente presente. Versión anterior de algo
             ya sellado al día. Se reconstruye desde la receta, que está en git.
  HUÉRFANO — ningún artefacto con ese nombre es el vigente. El hash de hoy NO está
             sellado y éste es el ÚNICO ejemplar. Borrarlo sí pierde.

Por eso el default borra sólo los superados; `--huerfanos` hay que quererlo aparte.
Aplicado: 702 superados = 34G, de 5,4G libres a 43G. La verificación de que el
criterio era correcto no fue el `df`: fue que `hydrate-cosmic.sh` siguió dando 83/83
recetas y 0 faltantes.

Los 255 huérfanos (17G) quedan INTACTOS y son un hallazgo aparte: casi todos KDE
(kio×8, kparts×8, kcmutils×8), o sea que las recetas KDE se movieron después del
último sellado y el escritorio hidratado vive de artefactos que ya no son vigentes.

Dos avisos que el script lleva escritos porque cuestan al descubrirlos solos: el
espacio NO siempre se libera (los rootfs de work/ son hardlinks al store — borrar el
dir no rompe nada pero tampoco libera hasta que el rootfs se vaya), y `--store` por
defecto apunta a /store, no a ./store.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 09:46:41 -04:00
sergioandClaude Opus 5 e43ce95786 🎥 cosmic: pipewire ARRANCA, y el portal ya figura como cliente suyo
El bloqueo decisivo de ScreenCast no era una receta que faltara: `/usr/bin/pipewire`
estaba en la imagen desde que entró como dep de `cosmic-settings-daemon`, pero NADIE
lo lanzaba. En una distro con systemd de usuario eso lo hace `pipewire.socket`; acá
no hay tal cosa, así que es tarea de `cosmic-start` — el mismo patrón que el bus de
sistema y que login1.

Va justo después de asegurar XDG_RUNTIME_DIR y NO junto al bus de sesión, a
propósito: pipewire no necesita D-Bus para arrancar, sólo el runtime dir donde abre
su socket. Ahí sirve para los dos modos (bare y session) sin duplicar el bloque. Y se
espera EL SOCKET, no el pid: un demonio que muere al instante deja el pid existiendo
un rato, y esperar por él da un OK falso.

Medido dentro de la VM, cada paso una pregunta distinta:

  pipewire OK (socket pipewire-0, pid 189)      ¿abre socket?
  pw-cli info 0  → Core/4, 1.2.7, core.daemon   ¿SIRVE a un cliente?
  pw-cli ls Factory → "client-node"             ¿tiene lo que ScreenCast usa?
  pw-cli ls Client  → "xdg-desktop-portal"      ¡el portal YA está conectado!

La última línea es el veredicto: el frontend del portal aparece como cliente del
demonio, una conexión que antes no podía existir y que es justo la que `Start`
necesita para negociar el nodo. `client-node` es la factory con que el backend lo crea.

Sin regresión, con la cadena entera: `cosmic-screenshot` levanta la UI INTERACTIVA
del portal (región/ventana/pantalla + Capture + Save to Pictures) y el click deja un
PNG de 53.590 bytes. Esa UI no estaba documentada — el registro anterior sólo probaba
la ruta no-interactiva. Escritorio completo: 2414 colores (el panel mutilado da 130).

Lo que sigue faltando son DOS cosas distintas, y ninguna es un one-liner:
  · el handshake de punta a punta pide un cliente `ashpd` PERSISTENTE — el portal
    asocia la sesión al *sender*, así que CreateSession con un dbus-send y
    SelectSources con otro se rechazan por venir de nombres únicos distintos;
  · wireplumber, que separa "hay stream de video" de "hay audio". Ni construido está.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 09:46:21 -04:00
sergioandClaude Opus 5 2e26a45060 🎥 cosmic: ScreenCast — dos bloqueos, y el que importa es que NADIE ARRANCA PIPEWIRE
BLOQUEO 1: dbus-send no puede manejarlo, al revés que FileChooser. El
selector se abre con UNA llamada; ScreenCast necesita un handshake de tres
pasos con estado (CreateSession → SelectSources → Start) y la sesión queda
atada a la CONEXIÓN que la creó — cada dbus-send es una conexión distinta
que muere al terminar, así que el paso 2 nunca encuentra la sesión del paso
1. Es límite de la herramienta, no del portal.

Lo que sí se midió: CreateSession FUNCIONA. El frontend devuelve
/org/freedesktop/portal/desktop/request/1_106/sc1 con nuestro handle_token.

BLOQUEO 2, y éste no lo arregla ningún cliente: NO HAY DEMONIO DE PIPEWIRE.

  ls /usr/bin/ | grep -i pipewire  →  pipewire, pipewire-aes67,
                                      pipewire-avb, pipewire-pulse
  ps ax | grep -c pipewire         →  1   (el propio grep)

Los binarios están —el backend del portal enlaza libpipewire-0.3.so— pero el
demonio no corre: cosmic-start no lo lanza y no hay systemd de usuario. O
sea que aunque se escribiera el cliente ashpd, Start no tendría con quién
negociar el stream. Arreglo: lanzarlo desde cosmic-start. Para AUDIO además
falta wireplumber, que ni está construido.

Y se corrigió un comentario DESACTUALIZADO de cosmic-start que decía que
cosmic-settings-daemon «todavía no se puede construir acá (arrastra
pipewire)»: es falso hace tiempo, ese daemon está sellado y corre. Un
comentario viejo manda a buscar un bloqueo que ya no existe, y eso cuesta
igual que un bug.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 07:51:36 -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 04146ec77b 🛍 cosmic: la tienda LISTA — el catálogo AppStream hubo que fabricarlo, y el <provides> la vaciaba
Validado con pantalla: «Popular apps» poblada (App Library, Files,
Launcher, Settings), «Recently updated», y la búsqueda `text` devolviendo
COSMIC Text Editor. 2564 colores sobre un escritorio completo.

DOS TRAMPAS ENCADENADAS, y la primera invalida lo que dije al escribir la
receta:

1. El backend `pkgar` NO lee /usr/share/metainfo/*.metainfo.xml —los
   ficheros que instala cada receta—. Lee el CATÁLOGO, o sea
   {/usr/share,/var/lib,/var/cache}/{swcatalog,app-info}/{xml,yaml}/, que en
   una distro normal genera `appstreamcli compose`. Sin ese fichero la
   tienda abre perfecta y no lista NADA. Lo genera ahora
   qemu-desktop-image.sh agregando los metainfo en un <components>: doce
   líneas de python, cero AppStream y cero glib.

2. Con el catálogo puesto listaba 2 de 7. Los metainfo que genera `xdgen`
   escriben <provides> con los envoltorios <mimetypes>/<binaries>, y el
   crate `appstream` espera los hijos PLANOS del esquema de catálogo: muere
   con «The tag provide doesn't have a value» y DESCARTA EL COMPONENTE
   ENTERO, callado. Los dos que se veían eran justo los que no tienen
   <provides>. Se elimina el bloque al agregar.

Cómo se diagnosticó, que es lo reutilizable: desde el lanzador la salida del
proceso no llega al log de la sesión. Hay que abrir cosmic-term DENTRO de la
VM y correr `rm -rf ~/.cache/cosmic-store; RUST_LOG=info cosmic-store`. El
rm no es opcional: la tienda cachea el parseo en bitcode y sin borrarlo
repite el resultado viejo.

Y el deadline del panel es una CARRERA, no un umbral: un arranque de esta
tanda salió mutilado (130 colores) con el anfitrión ocioso y -smp 8. Si sale
mutilado con carga baja, rearrancar antes de culpar al propio cambio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 23:14:01 -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 539e2acd5b ✎ cosmic: cosmic-edit VALIDADO en QEMU — y el deadline del panel sí mira la carga del anfitrión
El editor abre desde el lanzador (Super → «text» → primer resultado) y se
ESCRIBE: la evidencia tiene `hammer edita` tecleado sobre la línea 1 y el
punto de modificado en la pestaña. 2105 colores, icono en el dock con el
punto de «en ejecución». Hidratación 72 recetas (era 71).

CORRECCIÓN al runbook, medida acá: decía «no es carga del anfitrión (falla
con el anfitrión ocioso)», cierto para 4 vCPU pero se leía como que con
`-smp 8` el anfitrión daba igual. Dos arranques de la MISMA imagen con la
MISMA línea de QEMU:

  load ≈ 6 (otro agente compilando) → 130 colores, mutilado, `deadline has
                                      elapsed` en el log
  anfitrión ocioso (load ≈ 1,8)     → 1671 colores, completo

Este anfitrión tiene 8 núcleos, así que `-smp 8` le da la máquina entera a
la VM y cualquier compilación compite por los mismos hilos. Receta: mirar
/proc/loadavg antes de creerle a un arranque mutilado.

También: con las cuatro aplicaciones en el dock el escritorio completo da
~1670 colores, no 894. El umbral útil es «cientos vs miles».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 20:40:35 -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 9665cc29d4 cosmic: la calculadora del lanzador ANDA — el stdin de qalc era el stdout
=123*456 → 56088 en el lanzador. libqalculate re-sellado b3:edb8d14d.

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

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

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 05:13:14 -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
sergioandClaude Opus 5 cd1497d740 🏔 cosmic: EL ESCRITORIO ENTERO DIBUJADO — panel, dock y applets en QEMU
26/26 recetas selladas, escritorio-cosmic 62/62. La captura (895 colores) muestra el
panel superior con Workspaces/Applications, reloj y los applets de a11y, audio, red,
batería y encendido; el dock inferior con lanzador, workspaces y biblioteca; y el fondo
en el color exacto que configura cosmic-start. 48 procesos cosmic estables a los 30s.

Y CORRIGE UN DIAGNÓSTICO MÍO. Cuando el panel selló y no dibujó, lo atribuí a que le
faltaban los applets. Hacían falta —sin ellos el panel está vacío— pero no eran la causa
de la pantalla lisa: -device virtio-gpu-pci no reemplaza la VGA por defecto de QEMU, la
SUMA. Dos cards, dos outputs, cosmic-bg pinta en los dos y el panel se ancla en uno; el
screendump capturaba el otro. Log impecable, pantalla lisa. Con -vga none sale entero.
Un síntoma puede tener dos causas suficientes: arreglar la primera no prueba nada.

También: PATH como primera línea de cosmic-start. El getty da un PATH sin /usr/bin y en
la imagen mkdir sólo vive ahí, así que los cuatro mkdir -p fallaban en silencio — gratis
hoy porque los directorios venían horneados, fatal el día que cambie la imagen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 00:32:50 -04:00
sergioandClaude Opus 5 e99fc836fb cosmic: los applets necesitan la libdbus COMPARTIDA, y el linker lo dice una hora tarde
`cosmic-applets` declaraba `dbus` a secas. Alcanza para que el build.rs de libdbus-sys
pase —le basta el .pc, que el paquete del daemon trae— y el fallo salta recién en el
enlace final del multiplexor: 'unable to find dynamic system library dbus-1'. El corpus
sólo empaqueta libdbus-1.a; el .so vive en `dbus-shared`, que es lo que ya declaraban
settings-daemon y pipewire. Era la única receta de la cola que pedía la estática.

De paso, las cuatro piezas que spawnea el PANEL (no cosmic-session) entran como raíces
de hydrate-cosmic.sh: se invocan por AppID, así que ningún grafo de deps las alcanza.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 22:32:22 -04:00
sergioandClaude Opus 5 d724f0e267 🎉 cosmic: LA SESIÓN ARRANCA ENTERA — el camino de producción, no el andamio
cosmic-session levanta la cadena completa sobre el kernel de hammer con arje-zero
como PID1: cosmic-comp → settings-daemon → notifications → panel → osd → bg. El
fondo pinta (164 colores, el azul configurado) y el cursor está.

Los cuatro que faltan (app-library, launcher, workspaces, greeter) dejan una línea
de error! y la sesión SIGUE VIVA — exactamente lo que la tabla de .expect del
runbook predecía. Verificado, no supuesto.

cosmic-notifications sellado (b3:3e2a5478) completa el mínimo viable de cuatro.

Nota de método: esta corrida fue headless porque el Xwayland del laptop perdió la
autorización a mitad de sesión ('Authorization required, but no authorization
protocol specified'). El screendump por el monitor de QEMU sirve igual y es la
forma automatizable de la regla de validar con pantalla.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:16:30 -04:00
sergioandClaude Opus 5 999e9bdcb5 cosmic: los componentes que MATAN la sesión son TRES, no uno — corrección medida
El arranque en modo session murió con 'failed to start notifications daemon'
(main.rs:306). Grepeando '\.expect(' aparecen los tres:

  main.rs:255  cosmic-settings-daemon
  main.rs:306  cosmic-notifications
  main.rs:325  cosmic-panel

O sea que el mínimo viable de una sesión COSMIC son CUATRO binarios: compositor
más esos tres.

Cómo me equivoqué, que es la parte reusable: leí start_component(), vi que sólo
logueaba, y generalicé a 'todos menos el settings-daemon'. Pero notifications y
panel NO PASAN por esa función — tienen su propio .expect() unas líneas antes.
Buscar la función que lanza no es lo mismo que buscar los .expect del fichero, y
grepear la construcción que mata cuesta lo mismo y no generaliza de más.

De paso, el bloque que configura el fondo de color sale del branch de modo bare:
estaba adentro, así que en modo session cosmic-bg se quedaba con el wallpaper por
defecto (que vive en git-lfs y no existe) y la pantalla salía negra por una razón
distinta de la que se estaba investigando.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 14:25:12 -04:00
sergioandClaude Opus 5 516610b307 cosmic: cosmic-panel selló y no pintó — le faltaban los HIJOS, no una librería
cosmic-panel (b3:ac5e48c5) arranca perfecto: lee su config, crea su wl_output,
dice 'Spawning applets' y 'Done spawning applets'. Y la pantalla no cambió ni un
color. La respuesta estaba en esa misma línea: spawnea TRECE clientes por AppID y
ninguno existía. Un panel sin applets no es un panel vacío, es un panel que no
tiene nada que medir y no se dibuja.

Es la misma forma del muro de los typelibs en GNOME: un componente puede arrancar
sin errores y no pintar porque lo que le falta es un HIJO. El log del padre se lee
sano.

La receta cubre el panel entero de una: upstream compila UN binario multiplexor y
cada applet es un symlink con el nombre que el panel invoca; despacha por argv[0].
Veinte symlinks, dos binarios.

Fase compile custom porque hay que construir DOS targets (los default-members) y
'cargo rustc --' sólo admite uno.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 13:49:44 -04:00
sergioandClaude Opus 5 570fd3747e 🧮 cosmic: trazarle la yupana — el frente existía en el store y el ábaco no lo veía
Faltaba la mitad del método.  SÍ cruzaba incoming-cosmic (lo usé
antes de tocar libdisplay-info, pipewire y glib), pero la campaña no estaba en el
KHIPU: build-state.py sólo conocía --kde y --gnome, y targets.toml no declaraba
perfil.

Y eso no era cosmético. Antes de este commit,  decía
21 dependientes transitivos y dos imágenes; ahora dice 31 y TRES —
escritorio-cosmic entre ellas, con incoming-cosmic=10 en el reparto por cola. O
sea que el próximo que tocara libinput, libudev-zero o mesa habría MEDIDO DE
MENOS, que es exactamente el punto ciego que la metodología existe para cerrar.

Tres piezas:
- build-state.py --cosmic (y su build-state-cosmic.json).
- perfil.escritorio-cosmic en targets.toml, con las raíces que cosmic-session
  levanta MÁS los datos que ningún [deps] alcanza (iconos, xkb, fuentes, bash,
  dbus). Primera medición: 54/61 listo, faltan 7.
- el LATIDO lo regenera, por la misma razón por la que se le agregó GNOME en su
  momento: un frente que el cron no regenera envejece en silencio, y un grafo
  viejo miente con la misma cara que uno fresco.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 13:44:18 -04:00
sergioandClaude Opus 5 98608524dd cosmic: COSMIC_MODE viaja como fichero, no como variable
El getty ejecuta cosmic-start sin ambiente heredable, así que probar el modo
session exigía editar el script. Ahora la imagen hornea /etc/cosmic-mode, mismo
truco que /etc/gnome-mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 13:01:06 -04:00
sergioandClaude Opus 5 78899549fe cosmic: el wallpaper por defecto vive en git-lfs — fondo de color liso en la imagen de prueba
El default de cosmic-bg apunta a una imagen del repo cosmic-wallpapers, cuyo
tarball de GitHub pesa 20 KB: son punteros LFS, no imágenes. Una receta escrita
del modo normal produciría un paquete de ficheros de texto que además PARECERÍA
correcto — nombre bueno, ruta buena.

Mientras tanto cosmic-start escribe la config de usuario con una fuente Color,
que cosmic-bg soporta de fábrica. Va en el lanzador y no en el artefacto porque
es política de esta imagen de prueba, no algo que el paquete prometa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:50:50 -04:00
sergioandClaude Opus 5 ef4b18bc88 🖥 cosmic: COMPONE EN QEMU — cosmic-comp presenta frames sobre el kernel de hammer
Pantalla completa en el gris de COSMIC (39,41,42) con el cursor dibujado, 134
colores distintos, validado CON PANTALLA (DISP=gtk + screendump).

Entra el andamiaje completo: hydrate-cosmic.sh (44 recetas, 0 faltantes),
qemu-desktop-image.sh y cosmic-start-qemu.sh.

Tres causas medidas en tres arranques, ninguna donde uno la busca:

1. cosmic-settings-daemon NO es opcional: cosmic-session lo lanza con .expect()
   (main.rs:255) y panickea si falta. Corrige lo que este runbook afirmaba. De
   ahi el modo bare del lanzador, que separa como compone de como arranca la
   sesion.
2. La pantalla negra era libz.so.1: kms_swrast_dri.so lo NEEDea y el corpus solo
   traia zlib estatica. Sin driver de software no hay GBM y el compositor arranca
   igual, negro. Causa a tres capas del sintoma.
3. Los clientes no pueden ser estaticos: wayland-client entra con la feature
   dlopen y un musl estatico no tiene dlopen funcional — moria 'The wayland
   library could not be loaded' CON el .so presente.

Y dos carreras con /run (dbus y XDG_RUNTIME_DIR) de la misma forma que el bug de
PolicyKit1 de hoy en GNOME: comprobar al principio y usar al final. Se crea justo
antes de usar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:48:44 -04:00
sergioandClaude Opus 5 98a8152f49 cosmic: hidratador del cierre de runtime — 35 recetas, 0 faltantes
Hermano de hydrate-gnome.sh y con su misma propiedad: el cierre sale del GRAFO
REAL de recetas y el artefacto se elige por 'hammer hash' (la receta de HOY), no
por el más reciente del store, que con dos artefactos del mismo paquete miente.

Las raíces se nombran a mano porque en COSMIC la brecha entre cierre de build y
cierre de runtime es enorme: cosmic-session lanza a sus componentes por PATH y
por nombre, así que ninguno es dep de build de otro y el grafo no los ve. La
lista autoritativa es cosmic-session/src/main.rs.

Resultado: 207 binarios, 109 .so, y los siete NEEDED de cosmic-comp resueltos
dentro del propio cierre. Falta sólo el loader musl, que lo pone la base metal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:25:25 -04:00
sergioandClaude Opus 5 c0055331c7 gnome: esperar el NOMBRE de PolicyKit1 antes de lanzar a sus clientes
accounts-daemon y upowerd morían al arrancar: el shim de polkit se lanza en
background y tarda ~2s en adquirir org.freedesktop.PolicyKit1, ellos llamaban a
polkit_authority_get_sync() en ese hueco, D-Bus intentaba ACTIVAR el servicio y
fallaba con 'Failed to execute program ... Permission denied'.

Lo peor era el informe: los tres esperar_nombre estaban al final, así que para
cuando corrían el shim ya tenía el nombre y el resumen daba 'PolicyKit1 OK'
sobre dos clientes ya muertos. Un chequeo posterior a la carrera no la mide.

esperar_nombre pasa a definirse antes del primer daemon y el bloque de polkit
espera su propio nombre antes de que arranque ningún cliente suyo. Lanzar en
orden no es estar listo en orden.

De paso queda cerrado el ítem 4 del runbook: validación CON PANTALLA (DISP=gtk)
del Overview con el PID1 sellado nuevo — 689 colores distintos, con evidencia.

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