El handoff avisa que nada suyo está verificado contra hammer («si un dato de hammer lo
contradice, manda hammer»). Esto es esa verificación: contesta sus seis preguntas del §9 con
evidencia del código, corrige lo que estaba mal y añade dos objeciones que cambian el orden.
NO es aprobación ni plan de implementación.
EL HECHO QUE REORDENA TODO, y que el handoff no podía conocer: las FASES entran en
hash_inputs, y el config del kernel vive en la fase `configure` ⇒ **el config ES la identidad
del artefacto**. Corta en los dos sentidos. A favor: §2.2 (atestar un config por huella de
hardware) sale casi gratis, porque cada config ya es un artefacto direccionado por contenido,
reproducible y firmable — no hay que inventar el mecanismo. En contra: la explosión de builds
no es hipotética, es aritmética visible en el disco, que ya crece 24 G por ciclo. Y una
«perilla» de la UI no es un parámetro de runtime: es una edición de receta.
LAS SEIS RESPUESTAS:
P1 fragmentos — YA es como §6 prescribe: defconfig + `scripts/config -e/-d` + olddefconfig,
o sea que la app nunca escribe un .config. Media implementación está en producción. Falta
el diff-back, sin el cual un símbolo pedido que no sobrevive se pierde EN SILENCIO.
P2 repro con config variable — sí, por construcción: no hay un kernel cuya reproducibilidad
dependa de congelar el config; hay N kernels, cada uno congelado. Se pueden PREFIRMAR
artefactos, no sólo configs.
P3 tiempo — 35/45/60 min según receta ⇒ la bisección de §5 son ~4,5 h de la máquina que
acaba de no arrancar. Mata la bisección ingenua.
P4 harkaq — el patrón sí, el código no: su clausura enumera fichero a fichero sobre
artefactos del store, semántica de rutas. Kconfig es otro grafo.
P5 eje huella en el índice — sí y compatible hacia atrás: PackageEntry usa campos serde
opcionales. Mismo truco que hizo pagable el campo `license` hoy.
P6 PLAN-KIKIN §4.bis — DADO POR PENDIENTE Y ESTÁ HECHO EN DOS DE TRES: linux-metal y
linux-generic ya declaran los cuatro símbolos; sólo `linux` deja IO_URING y BPF_SYSCALL
al azar del defconfig. Y ojo al método: `grep IO_URING` da positivo por un COMENTARIO;
hay que mirar los -e/-d dentro del bloque configure. Mismo error que infló el conteo de
licencias hoy y que hace mentir la regla del `.a` no-PIC.
OBJECIÓN a §2.1: «clausura» sobre Kconfig no está definida todavía. Hay tres tipos de arista
(`depends on`, `select`, `imply`) con semánticas incompatibles, y `select` fuerza símbolos
IGNORANDO los `depends on` del destino. «Alcanzable desde CONFIG_WIRELESS» da conjuntos
distintos según la arista y ninguno es la respuesta. No lo invalida: lo convierte en la parte
con contenido técnico real. Y hay con qué validarlo GRATIS: el config actual de linux.toml
YA es un juego de bundles N1 hecho a mano («-d WLAN -d WIRELESS -d CFG80211 -d MAC80211
-d RFKILL» es literalmente «no necesito wifi») sobre un kernel que arranca.
LO QUE AGREGO:
· Bisección SIN recompilar: kernel «superset» con lo bisecable como módulo ⇒ los bundles de
hardware se prueban con lista de bloqueo en la línea de arranque. 6 reinicios en vez de 6
rebuilds. No cubre lo que debe ser =y (buena parte de N2), y el superset es el kernel gordo
que N1 quiere evitar: es diagnóstico, no el de todos los días.
· El gate de no-regresión debe ser POR OBJETIVO: linux.toml apaga USB/HID/INPUT a propósito
(es el kernel de QEMU con consola serie) y un gate global rechazaría una receta sana.
· «Booteó» no basta para atestar: un kernel arranca y te deja sin red, que es el desastre que
el propio gate quiere evitar. La atestación debe llevar QUÉ se comprobó — el mismo dato que
calcula el gate: una implementación, dos usos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Los identificadores obsoletos (`GPL-2.0`, `LGPL-2.1`, `AGPL-3.0`) no dicen si el proyecto
concede «sólo esta versión» o «ésta o cualquier posterior», y eso decide con qué se puede
combinar el paquete y bajo qué términos puede redistribuirlo quien lo reciba.
NO SE PUEDE MIRAR EL COPYING, que es la trampa evidente: el texto de la GPL es IDÉNTICO en
los dos casos —es la licencia, no la concesión— y encima su apéndice «cómo aplicar la
licencia» contiene literalmente «or (at your option) any later version», así que buscarla
ahí da SIEMPRE positivo y parece evidencia siendo plantilla. Misma trampa que la regla del
`.a` no-PIC, donde grep contaba reubicaciones de `.debug_*`. La concesión vive en las
cabeceras de los fuentes y en el README; ahí se busca, excluyendo los ficheros de licencia.
SE GUARDA LA CITA, no sólo el veredicto: fichero + frase exacta que decidió. Una licencia es
una afirmación legal y quien la revise tiene que poder ver POR QUÉ dice lo que dice sin
repetir el trabajo.
Y eso pagó de inmediato: de las 12 primeras resoluciones, TRES eran evidencia inválida y
sólo se vieron porque estaba la cita. Cada una de una clase distinta:
· caligula — la frase salía de `checks/headless/expected.iso`, un FIXTURE DE TEST;
· libnl — de `include/linux-private/linux/seg6.h`, una cabecera del KERNEL vendorizada.
La licencia del kernel no es la de libnl;
· lm-sensors — declarado GPL-2.0 y la cita decía «version 2.1 of the License», que es la
LGPL. La frase era real pero no sostenía lo que se le atribuía.
Las tres clases quedan filtradas en el script: ficheros de licencia, rutas de test/fixture, y
código vendorizado; más una comprobación nueva de que **la cita hable de la misma versión que
la licencia declarada**.
Sembradas las 9 auditadas (13 ficheros; algunas viven en varias colas). Ambiguas: 55 → 41.
Las 40 sin evidencia quedan listadas y siguen contadas — la mayoría son Go y la familia
cosmic, donde la frase no aparece fuera del COPYING.
Hashes verificados: 0 movidos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El catálogo decía que el texto de la licencia se inyecta en `hammer pack`. El razonamiento
del coste era correcto pero el sitio estaba mal: pack emite un .swm (receta sobre fuente
pública, sin binarios) e install reproduce construyendo, así que el canal de paquetes no
distribuye binarios. El punto de entrega es la IMAGEN, y ahí está hecho, con veto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
═══ 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>
Ú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>
Decisión del usuario: «tawasuyu está en MIT». Es la misma licencia que hammer (LICENSE en
la raíz + Cargo.toml), así que todo lo nuestro queda coherente bajo un solo término. Las 20
recetas con fuente en git.tawasuyu.net: arje-* (el init y sus compat), mirada-* (el
compositor, el greeter y mirada-ctl), netup, agora-cli, cosmos-cli, dominium-cli,
tinkuy-sim, llimphi-counter y libelogind.
⚠ ANOTADO EN LA TABLA porque es una trampa que casi pisé: `libelogind` NO es el elogind de
upstream (que sería LGPL-2.1+). Por el nombre lo parece; su cabecera dice que es una
reimplementación PROPIA de la C-ABI sd-login dentro de arje-compat. Declararla LGPL «porque
se llama así» habría sido exactamente el error que este fichero prohíbe — el tercero del
mismo tipo hoy, después de `knighttime` (parecía Go y es KDE) y de la familia `kube*`
(parecen KDE y son Go). El nombre nunca es evidencia; la fuente sí.
Hashes verificados sobre las 20: idénticos.
Quedan 124 sin declarar, ya todas de terceros.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Tercera tanda curada, agrupada por la URL DE FUENTE de cada receta (que es la evidencia que
el nombre no da): los once módulos Qt sin prefijo, freedesktop (dbus, libinput, libevdev,
libdisplay-info, poppler, pulseaudio, upower, NetworkManager, ModemManager, polkit),
kernel.org (los tres kernels, linux-headers, git, iproute2, libuuid) y PyPI.
Los kernels llevan `GPL-2.0-only WITH Linux-syscall-note` explícita. No es adorno: sin esa
excepción, todo binario de espacio de usuario que hace un syscall sería obra derivada del
kernel. Es exactamente el tipo de dato que un campo de licencia existe para no perder.
Quedan 147 sin declarar: 69 de GitHub donde la propia API dice NOASSERTION (hay que abrir
el fuente), 20 de tawasuyu —que son nuestras o del otro agente, así que la licencia la
decide el usuario, no yo— y el resto repartido en GNOME, gitlab.freedesktop y sueltos.
Más 71 con SPDX ambiguo pendientes de desambiguar (`licencias.sh --revisar`).
Hashes verificados otra vez sobre 20 recetas tocadas: 20 idénticos, 0 cambiados.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Fui a destrabar «las 12 recetas en deuda» con el diagnóstico heredado (fallan por el rootfs
del worker: python OSError en meson, find_package en cmake). Lo probé construyéndolas de
verdad y **el diagnóstico es falso**: cmake y meson corren perfectamente ahí. Son cuatro
situaciones distintas y ninguna es el rootfs.
(a) SEIS ESTÁN SUPERADAS, NO EN DEUDA. `gtk4` del corpus es link=static y enlaza
libfontconfig.a, que tiene 1122 reubicaciones no-PIC ⇒ 13.032 errores
`R_X86_64_64 cannot be used against local symbol`. No es una receta rota: es imposible.
Y mientras tanto recipes/incoming-gnome/gtk4.toml (link=dynamic, deps -shared) YA ESTÁ
SELLADA, con libadwaita y fontconfig-shared. O sea que la cadena estática del corpus
—gtk4, libadwaita, gtksourceview y los tres hello/edit que cuelgan— es un DUPLICADO
superado de la cadena dinámica de GNOME.
Cerrarla no es construirla: es decidir si se promueven las sombras -shared al corpus,
porque la resolución de deps es hermano→padre. Es una decisión de arquitectura, y hay
aviso registrado de que promover a ciegas hace que variantes homónimas pisen recetas
canónicas. NO la tomo yo.
(b) wlr-randr: cerrada en el commit anterior.
(c) dwarves: frente propio con muro identificado, no deuda. Nuestro elfutils entrega SÓLO
libelf a propósito (libdw arrastra argp/obstack/fts, lo difícil en musl). No se arregla
ampliando elfutils: de él cuelga el kernel que ya reproduce bit a bit. El camino es una
receta aparte `elfutils-libdw`, con el patrón de las sombras -shared. Sin urgencia:
ningún perfil pide dwarves y el kernel desactiva DEBUG_INFO_BTF justamente por su
ausencia. Queda escrito en la cabecera de la receta, que es donde se va a leer.
(d) mirada-compositor, mirada-greeter, llimphi-counter: source por SSH a
git.tawasuyu.net y el worker es SIN SECRETOS por diseño ⇒ HUB-ONLY estructural, no
fallo. llimphi-counter ni siquiera llega a construir: su commit son ceros con el
comentario «fijar al commit real». Receta sin terminar, y es de tawasuyu.
EL HALLAZGO DE FONDO: el bucle del worker sólo recorre las colas incoming-*; el corpus
(recipes/) NO está en QUEUES. Las 12 nunca se habían intentado allí. Buena parte de «la
deuda» era de ENCOLADO, no técnica — y por eso el diagnóstico heredado nunca se verificó.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pedido del usuario: «el catálogo de todo lo que falta hasta decir la distro está
publicable, y lo de más hasta la distro está completa. Y un tipo manual, todos los pasos
que tengo que ir haciendo en las tres etapas que marcan esos dos codos.»
SDD 20 = el inventario, medido contra el repo (grafo, no recuento a mano). SDD 21 = el
manual de a pie, un comando por vez, marcando qué puede hacer el agente y qué es del
usuario.
LO QUE APARECIÓ AL MEDIR, y que no se sabía:
· La deuda de construcción NO son 12+12+13+12 recetas: es UNA lista de 12 vista desde
cuatro grafos (la cadena GUI de la cascada de mesa). Un solo arreglo —declarar python3 y
cmake en `[deps]`, no engordar el rootfs del worker— las destraba todas. `base` y `cli`
ya están al 100%, KDE cierra 162/162.
· De WMs Wayland ligeros no hay NINGUNO: sólo `foot` y `mako`. Falta wlroots entero, los
compositores y todos los accesorios (waybar, fuzzel, swaybg, grim, slurp, wl-clipboard).
· De aplicaciones gráficas de terceros hay CERO. Ni visor de imágenes ni lector de PDF.
· Firefox no es «una receta más»: su cadena de `*-sys` con C++ es justo la frontera que el
techo MSRV del sandbox (1.96) no cruza. Antes de Firefox hay que subir el techo; empezar
por la receta es empezar por el final.
· El texto de la licencia va inyectado en `hammer pack`, NO en la fase install: las fases
entran al ArtifactHash, así que hacerlo en la receta re-hashearía las 1141. Pack es aguas
abajo y sale gratis. Misma lógica que hizo pagable el campo `license`.
Dos correcciones al SDD 19, que escribí yo ayer y tenía mal:
· «5 de 771 recetas declaran licencia» — falso por partida doble. El número real era 0 de
1141; los «5» eran falsos positivos de `grep license` (nombres de paquete, una línea de
install, un comentario) y 771 son los nodos del grafo del corpus, no las recetas.
· «El repo no declara licencia» — falso: hay LICENSE (MIT) en la raíz y en Cargo.toml. No
lo miré antes de escribirlo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Lo que falta para TERMINAR de construir no es lo mismo que lo que falta para
PUBLICAR. Lo primero es trabajo conocido (16 recetas, WMs Wayland ligeros,
apps de terceros — con el navegador como frente propio). Lo segundo tiene dos
bloqueantes que hoy no existen y no se improvisan el día del anuncio:
1. METADATOS DE LICENCIA: sólo 5 de 771 recetas los declaran. La distro no sabe
bajo qué términos redistribuye el 99% de lo que empaqueta. Hace falta campo
obligatorio validado por hammer + el texto dentro del artefacto.
2. ESPEJO DE FUENTES: la GPL no pide que el código exista en internet, pide que
quien recibe el binario pueda obtener la fuente correspondiente DE VOS. Acá
estamos bien parados —cada receta pinea tarball+sha256— pero hay que hacerlo.
Más: gestión de claves (firma sin eso es teatro), evidencia de reproducibilidad
publicada como diferenciador comprobable, imagen validada en METAL, y ciclo de
actualización ensayado antes de publicar.
Recomendación de arranque: el campo de licencia obligatorio, ya. A 771 recetas
es un rato; a 2000 es un proyecto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documenta el diagnóstico del OptiPlex 3060: la GPU estaba perfecta (i915 inicializó,
fbcon en el framebuffer) y lo que fallaba era el hardcodeo de software GL, más la
preparación de sesión que la imagen de metal nunca tuvo.
Deja las dos reglas: un artefacto compartido entre QEMU y metal no puede llevar el
entorno hardcodeado (se detecta), y «llega al prompt» no valida NADA del escritorio
— hay que correr cosmic-start en la imagen que se va a quemar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documenta el diagnóstico completo: la AUSENCIA del `EXT4-fs mounted` como prueba de
que el root nunca se montó, la carrera contra la enumeración USB (invisible en QEMU
porque virtio está desde el instante cero), y la ceguera del /dev/console = serial.
La lección que generaliza: CADA capa del arranque tiene que escribir a /dev/tty0. Se
arregló la capa post-switch_root en el viaje de KDE y el initramfs quedó ciego; tapar
una sola capa garantiza que la siguiente vuelva a caer.
Incluye el comando para reproducir metal desde el laptop (qemu-xhci + usb-storage +
-serial null) y la evidencia en pantalla.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Qué correr con el USB en la mano, en orden, y —lo que importa— CUÁL ES EL VEREDICTO:
el `[4/4] OpenPipeWireRemote` con su fd, no que el diálogo aparezca (eso ya pasaba en
QEMU). Y el contrafáctico que hace falsable el diagnóstico de hoy: si vuelve a
colgarse en `Start` pero YA NO aparece `Erroneous EGL call` en el log del compositor,
entonces la causa era otra y lo de hoy estaba incompleto.
`ls /dev/dri` con renderD128 es la señal barata de que hay HW: con swrast no existe.
Gotchas del quemado, todos medidos y todos con su porqué:
· NUNCA a un NVMe y NUNCA POR NOMBRE — hoy mismo, tras el reboot, nvme0n1 y nvme1n1
se intercambiaron respecto de lo anotado en julio. Identificar por TRAN=usb.
· el progreso NO se mide con kill -0 (sudo dd corre como root ⇒ EPERM desde el
usuario y parece que terminó): se mide por /sys/block/sda/stat campo 7.
· no correr os-prober sobre el device que estás dd-eando.
· `pkill -f <patrón>` mata su propio shell si el patrón está en su cmdline. Volvió a
pasar hoy (exit 144), con el gotcha ya escrito. Usar pgrep -x.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Con RUST_LOG=trace el log del backend tiene CINCO LÍNEAS en total y la última es la
de siempre ('Connecting' -> 'Paused'). Después no escribe nada más: ni error, ni
panic, ni progreso. O sea que el trace no aportó — screencast_thread no tiene más
instrumentación y buscar ahí estaba agotado.
La pieza que faltaba mirar era EL COMPOSITOR, que es quien entrega los frames:
15:19:02.301568Z WARN smithay::backend::egl::error: Erroneous EGL call didn't set EGLError
15:19:02.310127Z INFO …screencast_thread: state-changed 'Connecting' -> 'Paused'
↑ OCHO MILISEGUNDOS
Y la correlación no es casual: el contador de "Erroneous EGL" sube DE A DOS por cada
intento de screencast (uno al abrir el diálogo con su miniatura en vivo, otro al
pulsar Share) y no se mueve en ningún otro momento.
`ls /usr/lib/dri/` → iris_dri.so, kms_swrast_dri.so, swrast_dri.so. En QEMU con
virtio-gpu-pci sin virgl carga kms_swrast: mesa por software, SIN exportación dmabuf
real. Que es justo lo que un stream continuo necesita y una captura de una sola toma
no — por eso cosmic-screenshot SÍ funciona (va por shm) y ScreenCast no.
EL FRENTE NO ESTÁ BLOQUEADO POR UNA RECETA SINO POR EL ENTORNO. Esto reencuadra el
día entero: pipewire no era, wireplumber no era, el backend no era. Las tres piezas
están construidas, corriendo y haciendo su trabajo —el nodo `cosmic-screencast` existe
en el grafo— y el que no puede cumplir es el compositor sobre una GPU de software. Es
el MISMO muro que documenta el frente KDE (GBM/EGL de software), llegando por otro
camino.
Y no se puede verificar acá: `qemu-system-x86_64 -display help` da sólo none/gtk/sdl
y no existe virtio-gpu-gl-pci — este binario no se compiló con virgl, aunque
libvirglrenderer.so.1 esté en el host. Las salidas son metal (donde iris_dri.so YA
está en la imagen) o un QEMU con virgl.
Lo honesto: ScreenCast está verificado HASTA DONDE EL ENTORNO PERMITE — la cadena
D-Bus completa funciona, el stream se crea y se negocia, y el último tramo (los
píxeles) depende de una GPU que esta VM no tiene.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
NO SE PUEDE DESDE LA TIENDA. cosmic-store se compila con la feature
xdg-portal, pero su ÚNICO uso del selector (src/update.rs:505,
Message::RepositoryAddDialog) arranca con
`if backend_name == BackendName::FlatpakUser`: el diálogo sólo se abre para
añadir un repositorio flatpak, y esta imagen no construye el backend
flatpak. La feature está compilada y el punto de llamada es INALCANZABLE.
Tener la feature encendida no es tener el flujo disponible.
El camino que sí ejerce la interfaz es llamarla directo por D-Bus, que
además prueba los tres eslabones sin depender de qué app la use:
dbus-send --session --print-reply --dest=org.freedesktop.portal.Desktop \
/org/freedesktop/portal/desktop \
org.freedesktop.portal.FileChooser.OpenFile \
string:"" string:Prueba dict:string:variant:handle_token,string:hammer1 &
Medido: el frontend devuelve el objeto Request con NUESTRO token
(/org/freedesktop/portal/desktop/request/1_113/hammer3) y el backend dibuja
el diálogo REAL — título «Prueba», el FS de verdad (run/tmp/dev/proc/sys),
panel de detalles (inode/directory, 963 items), Cancel/Open. La navegación
anda: doble clic en dev cambia la miga a «Filesystem › dev» y lista sus 129
entradas; seleccionar y pulsar Open lo cierra.
⚠ LO QUE NO QUEDÓ CAPTURADO: la URI devuelta. El Response de la spec de
portales es una señal UNICAST al llamador, y dbus-send termina apenas recibe
el method return, así que ya no está para recibirla; dbus-monitor con un
match rule normal tampoco la ve. Pide un cliente que implemente el ciclo
Request/Response, que es lo que hace ashpd dentro de las aplicaciones.
El diálogo está probado; la entrega del fichero a la aplicación, no.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>