Commit Graph
1065 Commits
Author SHA1 Message Date
Sergio 98ded1dd92 estado: cosecha granja 2026-09-05T22:31:57Z — avance del árbol KDE 2026-09-05 22:31:57 +00:00
Sergio d4e2b8f1a7 estado: cosecha granja 2026-09-05T22:02:17Z — avance del árbol KDE 2026-09-05 22:02:17 +00:00
Sergio 8a2c6fad39 repro: «no pude comparar» tampoco es no-determinismo — segundo sitio, misma lección
Una tanda volvió a llenar el libro de veredictos falsos: 47 recetas sanas anotadas como
NO-DETERMINISMO. Esta vez la cadena empezó antes de donde yo había mirado.

El `mv` que APARTA el artefacto falló (el directorio de apartado no estaba) y yo nunca comprobaba que
hubiera funcionado. A partir de ahí todo lo que sigue es basura: el artefacto se queda en su sitio,
`hammer why-differs` compara contra una ruta que no existe y devuelve `Error: store: no existe…`, y
mi código leía cualquier salida no-cero como «difieren». Un error de comparación disfrazado de
veredicto, 47 veces.

Es la MISMA lección que arreglé hace un rato para el build, en un segundo sitio que no miré:
**no poder medir no es un resultado negativo**. Dos guardas nuevas:

  · el apartado se comprueba (`mv || SIN VEREDICTO`) y la receta se salta ruidosamente;
  · `why-differs` se lee, no sólo su exit code: si dice `Error:`/`no existe`, es SIN VEREDICTO, se
    restaura el artefacto y NO se anota nada.

Probadas las dos: con el apartadero sin permiso de escritura sale «no pude apartar el artefacto ⇒
SIN VEREDICTO», el artefacto queda intacto y el libro no crece.

Comprobado además que la tanda mala no perdió NADA: como el `mv` fallaba, los 47 artefactos nunca
salieron del store (`faltan: 0`). El daño fue sólo el registro, y está purgado — el libro queda con
51 verificaciones buenas.

La regla, ya por triplicado hoy: cuando un guardián no puede establecer algo, el estado es *sin
veredicto*, y eso no se escribe. Un libro que anota lo que no midió se cree igual que uno que sí.
2026-09-05 21:44:50 +00:00
Sergio 6c1f80204a repro: cobertura 92 de 1156 — la tanda sólo-C es la sostenible
18 recetas C verificadas de una, en unos nueve minutos, 15 REPRODUCEN y 3 eran DERIVA (ya al día).
Cero no-determinismos. Y `work/sources` se quedó en 4 KB toda la tanda gracias a la poda por
veredicto: antes una tanda así llevaba el disco del 95% al 98%.

Lo que hace sostenible a esta y no a la anterior es la SELECCIÓN. El filtro `MAX_SECONDS` sobre los
tiempos del worker no transfiere a Rust —`zola` se comió 40 minutos y no dio un solo veredicto— pero
en C sí: sin caché de cargo de por medio, un build de 40 s allá son 40 s acá. Lista explícita de
clase `c`, no muestreo ciego.
2026-09-05 21:41:41 +00:00
Sergio 2277d17b13 estado: cosecha granja 2026-09-05T21:32:15Z — avance del árbol KDE 2026-09-05 21:32:15 +00:00
Sergio 1505bfad78 licencias: ffmpeg y LLVM salen del árbol — «lo decide un humano» era «lo decide la receta»
Tres que había clasificado como decisión legal y no lo eran. Las dos causas son distintas y las dos
me las estaba perdiendo por mirar sólo el árbol:

**ffmpeg → LGPL-2.1-or-later.** Su raíz trae cuatro COPYING.* y por eso el detector se planta, con
razón: el árbol solo no puede decidir. Pero el `LICENSE.md` del propio ffmpeg explica la regla —«In
combination the LGPL v2.1+ applies to FFmpeg» y «None of these parts are used by default, you have to
explicitly pass `--enable-gpl`… In this case, FFmpeg's license changes to GPL v2+»— y la respuesta la
da NUESTRA receta: no pasa `--enable-gpl` ni `--enable-version3`, y encima va con
`--disable-autodetect`. O sea que la licencia sale de cruzar el árbol con los flags, no de elegir uno
de los cuatro ficheros.

**llvm18 y clang18 → Apache-2.0 WITH LLVM-exception.** Acá el detector veía «tres licencias
distintas» en un solo `LICENSE.TXT` y se plantaba, pero el fichero ABRE diciendo «The LLVM Project is
under the Apache License v2.0 with LLVM Exceptions»: lo demás es la sección legada. La regla de
«varias licencias ⇒ decide un humano» es correcta como default y aquí el humano sólo tuvo que leer la
primera línea.

`licenses/` pasa de 48 a 49 textos: `LLVM-exception` hacía falta para poder declararla, que es el
lazo de siempre —no se declara lo que no se puede entregar—. El único sin texto canónico sigue siendo
`LicenseRef-qorpa-ajena-no-enumerable`, que es identificador nuestro y SPDX no publica.

97% (1135/1165); la cola baja de 27 a 24. Los 3 ArtifactHash, idénticos.
2026-09-05 21:26:53 +00:00
Sergio 8b43b44c11 estado: cosecha granja 2026-09-05T21:02:03Z — avance del árbol KDE 2026-09-05 21:02:03 +00:00
Sergio a2e5ebd6d7 estado: cosecha granja 2026-09-05T20:32:01Z — avance del árbol KDE 2026-09-05 20:32:01 +00:00
Sergio 058162c6d3 licencias: la cola de pendientes también se escribe — «lo que falta y por qué» es dato
Las 33 que quedan sólo existían en la salida del script, o sea que se perdían al cerrar la terminal.
Para quien tiene que decidirlas —y son decisiones legales, no mecánicas— «qué falta y POR QUÉ no se
puede afirmar» es tan dato como los veredictos. `docs/licencias-pendientes.tsv` las deja listadas con
su motivo: cuáles traen varios ficheros de licencia que dicen cosas distintas (gmp, ffmpeg, clang18,
llvm18, los `gi-*`), cuáles tienen un LICENSE que no reconozco y con qué frase empieza (fuse3, lsof),
y cuáles no traen nada en el árbol (boost, pigz, sqlite-shared).

Al revés que `licencias-evidencia.tsv`, ésta se REGENERA entera en cada corrida. No es un registro de
lo que se probó sino una foto de lo que queda, y una pendiente resuelta tiene que DESAPARECER de acá
—si se acumulara, la lista de trabajo mentiría hacia arriba para siempre.
2026-09-05 20:18:21 +00:00
Sergio d7d9cf8fd2 repro: «la 2ª reconstrucción no construyó» NO es no-determinismo — mi propio bug, y lo pagó el libro
Corriendo una tanda con `MAX_SECONDS=45` se llenó el disco a mitad, los rebuilds empezaron a morir
por ENOSPC, y la rama que yo mismo había escrito para distinguir DERIVA de NO-DETERMINISMO metió
«falló» y «difiere» en la misma condición ⇒ **21 recetas sanas quedaron anotadas como
NO-DETERMINISMO** en un libro que pretende ser autoritativo. Un registro falso y persistente es peor
que no tener registro: el propósito del libro es que nadie tenga que volver a mirar, así que una
entrada equivocada se cree para siempre.

Es la trampa de siempre —un disco lleno se lee como receta rota, y la pista es que mueren varias
seguidas— y el script ORIGINAL ya la evitaba: «no es lo mismo «no reproduce» que «no construye
acá». Distinguirlos importa: mezclarlos inventaría un problema de determinismo». Lo rompí al añadir
la segunda reconstrucción. Restituido: si la 2ª no construye, se restaura el artefacto, se cuenta
como fallo y **no se anota nada** — sin veredicto es un estado legítimo.

Reparado además el daño: las 21 entradas falsas purgadas del libro (quedan 27 verificaciones
buenas), y `angle-grinder` repuesto desde `store/.divergen/`, donde lo había dejado la clasificación
errónea. Eso sí funcionó como debía: el ejemplar estaba guardado y entero, no perdido.

Y queda anotado en el propio script el límite del filtro por tiempo que provocó todo: el sidecar de
`store/.times/` lo escribe el WORKER, así que acota el build de esa receta EN LA MÁQUINA QUE LO
MIDIÓ. Una receta Rust de 40 s allá con la caché de cargo caliente son muchos minutos acá en frío
—`angle-grinder` y `amp` entraron por el filtro y se comieron el disco— y encima el número no cuenta
la CASCADA de deps que falten en el store local, que es la misma sorpresa que dio `gjs`. Con disco
justo conviene pasar las recetas a mano.
2026-09-05 20:05:33 +00:00
Sergio 118ba2e2c3 estado: cosecha granja 2026-09-05T20:03:46Z — avance del árbol KDE 2026-09-05 20:03:46 +00:00
Sergio 4067bf817e estado: cosecha granja 2026-09-05T19:32:01Z — avance del árbol KDE 2026-09-05 19:32:01 +00:00
Sergio efe6e001d2 repro: un libro de lo verificado, y por fin un número de cobertura — era 2 de 1157
`verificar-repro.sh` sorteaba una muestra y se olvidaba. Sin registro no había forma de contestar la
pregunta que importa —¿qué fracción del corpus se comprobó alguna vez que reproduce?— y encima las
mismas recetas salían sorteadas una y otra vez mientras otras no se miraban jamás. La primera medida
con el libro puesto: **2 de 1157**. El invariante central del proyecto no se había verificado de
forma acumulativa prácticamente en nada, y eso no se veía porque cada corrida daba « PUERTA
SUPERADA» sobre su propia muestra.

`docs/state/repro-verificado.tsv` va con la misma clave auto-invalidante que el libro de fuzz:
(receta, **ArtifactHash**). El hash resume la receta MÁS su cierre de deps, así que una entrada deja
de casar en cuanto algo aguas arriba cambia. Un «ya lo verifiqué» que sobreviviera a un re-hash sería
una mentira; éste no puede serlo.

Con eso el muestreo pasa a sortear entre lo NO verificado, así que corridas sucesivas ACUMULAN
cobertura en vez de repetir (con `TODO=1` se sortea entre todas, para re-verificar a propósito), y
`--coverage` da el número sin construir nada.

Verificadas en esta tanda: itstool, markupsafe, musl-obstack, hicolor-icon-theme, musl-fts, npth,
poppler-render-check, doas, libffi y packaging REPRODUCEN; `when` derivaba y quedó al día. Cero
no-determinismos. Van 11 de 1157.

⚠ Y una trampa que casi me come, anotada acá porque el próximo la va a pisar: elegí candidatos «por
artefacto chico» y la lista empezó con **nodejs**, cuyo artefacto pesa 0,2 MB y cuyo build son horas
de V8. El tamaño del artefacto NO dice nada del costo de construirlo. Para elegir muestra barata hay
que mirar tiempos de build, no bytes de salida.
2026-09-05 19:31:33 +00:00
Sergio 5420d2db0d estado: cosecha granja 2026-09-05T19:02:04Z — avance del árbol KDE 2026-09-05 19:02:04 +00:00
Sergio 3ddac0269c SDD 26 §2.sexies: tres afirmaciones que el documento hacía sin medir, y cómo se cerraron
El re-empaque determinista, la capa de configuración y el branding visible eran afirmaciones, no
mediciones. Quedan con su prueba al lado: why-differs entre dos builds de atuq (85 idénticas, 0
divergen), el testigo escrito como pref de usuario, y la captura de la pantalla real.

De la primera salió el contraste que importa: el DERIVADO reproducía y la BASE no. El BuildID de
firefox era la hora del build, y build-state.json no lo podía ver porque el hash es input-addressed.
Verde y mintiendo. Cerrado, con guardián.

La regla que sale del día entero: cuando la duda es «¿llegó al artefacto?», la respuesta no está en
el log ni en la receta — está dentro del artefacto.
2026-09-05 18:39:35 +00:00
Sergio e0dce7c4b7 estado: cosecha granja 2026-09-05T18:33:07Z — avance del árbol KDE 2026-09-05 18:33:07 +00:00
Sergio 59a0e56a16 licencias 97% (1132/1165): «no hay fichero» y «no reconozco el texto» no son lo mismo
El script decía «sin COPYING ni declaración en el árbol» para `lsof`, `when`, `wpa_supplicant`,
`perl-xml-parser` y `fuse3` — y las cinco SÍ tienen un COPYING o un LICENSE en la raíz. Lo que
pasaba es que ninguna de mis huellas reconocía su texto, que es un problema distinto y se acciona
distinto: uno pide buscar la licencia en otro sitio, el otro pide una huella nueva o leerla. Decir lo
mismo de los dos casos mandaba a mirar donde no era. Ahora el mensaje nombra el fichero y cita su
primera línea, y con eso tres se resolvieron leyéndolas:

  · `wpa_supplicant` — su COPYING no es una licencia sino un aviso: «the project has chosen to use
    only the BSD license option for future distribution», y remite al README. El README trae las tres
    cláusulas clásicas ⇒ BSD-3-Clause. Sin seguir esa remisión no había veredicto posible.
  · `when` — «under the same terms as Perl, or, at your option, under version 2 of the GPL». «Los
    mismos términos que Perl» ES la disyunción Artistic-1.0-Perl OR GPL-1.0-or-later, y la opción de
    GPL-2 ya queda cubierta por el `-or-later`.
  · `perl-xml-parser` — su LICENSE es el texto íntegro de la Artistic License 2.0.

Ese último obligó a cerrar un lazo que yo mismo había dejado abierto: el script se niega a declarar
un SPDX cuyo TEXTO no tengamos en `licenses/`, porque la obligación legal es entregar el texto y no
el nombre. Pero `licencias-textos.sh` deriva qué textos bajar DE las licencias ya declaradas ⇒ una
licencia genuinamente nueva no podía entrar por ningún lado. El orden que lo resuelve es: declarar
con evidencia, bajar el texto, comprobar que llegó. `licenses/` pasa de 44 a 48 textos y el único que
queda sin texto canónico es `LicenseRef-qorpa-ajena-no-enumerable`, que es un identificador nuestro y
SPDX no publica.

Quedan 33. `lsof` (licencia propia de Purdue) y `fuse3` (aviso compuesto: LGPL para la librería, GPL
para las utilidades) siguen pendientes a propósito, y el resto son expresiones que decide una
persona: gmp, ffmpeg, clang18, llvm18 y los `gi-*` traen VARIOS ficheros de licencia que dicen cosas
distintas, y en ffmpeg la respuesta ni siquiera está en el árbol —la deciden los flags de la receta.

Los 3 ArtifactHash, idénticos antes y después.
2026-09-05 18:10:52 +00:00
Sergio b87835f719 licencias 96% (1129/1165): la fuente git también es evidencia, y el script deja de llamarse «tarball»
Faltaban tres recetas cuya fuente es un repo, no un tarball: `fcft`, `foot` y `glab`. El commit
pineado cumple exactamente el mismo papel que el sha256 —es el árbol EXACTO que construimos— así que
la evidencia estaba igual de disponible, sólo que por otra puerta.

`miembros_de_git()` la abre sin clonar el árbol entero: `--depth 1 --filter=blob:limit=64k` sobre el
commit. Un COPYING nunca pasa de 64k y los fuentes donde vive la concesión tampoco; los que sí pasan
—binarios, assets— son justo los que no interesan. Un repo de cientos de megas se resuelve con unos
pocos. Las tres salieron del `license:` que el propio autor escribe en su `meson.build` (fcft, foot)
y del texto del LICENSE (glab).

Los repos por SSH se saltan diciéndolo: este script hace fetch anónimo y no tiene claves. Son
`hammerd` y `portal-probe`, y ya están resueltas por otra vía (su repo es este mismo).

`analizar()` pasa a consumir un iterable de `(ruta, bytes)` en vez de abrir el tar ella misma, así
que las dos fuentes comparten TODA la lógica de veredicto: la jerarquía de evidencia, la regla de
sólo-la-raíz, la unión de identificaciones y la exigencia de que la concesión hable de la misma
versión que el COPYING. Un criterio, dos puertas.

Y el script se renombra `licencias-tarball.py` → `licencias-fuente.py`, porque el nombre viejo pasó a
ser mentira en el momento en que aprendió a leer git. Un nombre que describe la implementación de
ayer manda a la gente a buscar en el sitio equivocado.

Los 3 ArtifactHash, idénticos antes y después.
2026-09-05 18:07:17 +00:00
Sergio 9e6e3d5aa6 estado: cosecha granja 2026-09-05T18:02:52Z — avance del árbol KDE 2026-09-05 18:02:52 +00:00
Sergio db7fd81236 parches: los 8 FUZZ del catálogo, mirados uno por uno — y un libro que se auto-invalida
`vigia-parches.py --all` sobre las 99 aplicaciones de parche del catálogo: **0 FALLA**, y 14 líneas
FUZZ (8 pares parche/fuente distintos). FUZZ significa que `patch` metió el cambio ADIVINANDO dónde
porque el contexto no casaba, y si cayó en el sitio correcto sólo lo dice el diff. Nadie los había
mirado. Los miré todos:

  · **libxml2 / CVE-2026-6732** — el que más importaba, un parche de seguridad con fuzz 2 en sus dos
    hunks. Cayó bien: los dos dentro de `xmlParseReference()`, las 4 llamadas pasan `ctxt->userData`
    y no sobrevive ningún `sax->characters(ctxt,` en el fichero. El fuzz era por un offset de 226
    líneas, no por el sitio.
  · **doas / rowhammer** — el otro sensible, toca la decisión de privilegio. Cae dentro de
    `checkconfig()` y queda `rv=permit(...); if(rv==0)→permit`, coherente con el `if(rv!=0)→EPERM`
    de `main()`. Y el fuzz lo causa un parche ANTERIOR de la propia cadena (el `#ifdef DOAS_CONFDIR`
    que inserta `configuration-directory.patch`), no un cambio de upstream — que es una causa que no
    se me habría ocurrido sin abrirlo.
  · **wayland**, **mesa** (×3), **cairo** (×2), **firefox** (time64 y fix-rust-target): todos en su
    sitio, cada uno comprobado contra lo que el propio parche declara querer.
  · **gnupg / 0001-include-unistd** — hallazgo: el parche está OBSOLETO. Añade `#include <unistd.h>`
    y upstream YA lo trae dos líneas más abajo, así que sólo lo duplica. Inocuo (el header tiene
    guardas) y por eso el fuzz 2: cambió el contexto porque upstream lo incorporó. Quitarlo re-hashea
    gnupg, así que se paga cuando se re-selle por otro motivo.

Y para que esto no se repregunte en cada corrida —lo que vuelve ruido al vigía, y así es como se
pierde el FALLA del día que aparezca— los veredictos van a `docs/state/fuzz-verificado.tsv` con un
cuarto estado, FUZZ✓.

La clave del libro NO es (receta, parche) sino (receta, parche, HUELLA), donde la huella resume el
texto del parche MÁS el pin de la fuente. Tocá el parche o subí la versión y la huella cambia, la
entrada deja de casar y el vigía vuelve a preguntar. Es lo contrario de una lista de excepciones: no
hay forma de silenciar algo y que siga silenciado cuando cambió. Y el vigía imprime la línea lista
para pegar debajo de cada FUZZ sin verificar, porque calcular la huella a mano es justo la fricción
que hace que nadie lo anote.

Los dos FUZZ de waterfox quedan FUERA del libro a propósito: no los verifiqué en esta ronda y
waterfox está fuera de alcance. Van a seguir saliendo como FUZZ, que es lo honesto — el libro dice
lo que se miró, no lo que se supone.
2026-09-05 17:34:28 +00:00
Sergio 2a62c44b19 estado: cosecha granja 2026-09-05T17:32:08Z — avance del árbol KDE 2026-09-05 17:32:08 +00:00
Sergio 5c8d5ba956 estado: cosecha granja 2026-09-05T17:02:10Z — avance del árbol KDE 2026-09-05 17:02:11 +00:00
Sergio 3a83d2eda4 gnome: xz-shared — sin ella NINGUNA app de la imagen carga, no una
`scripts/vigia-sonames.py` sobre los cinco perfiles marcaba `liblzma.so.5` en GNOME pedido por
`libadwaita` y por `python3`. La columna de QUIÉN lo pide es la que decide, y acá decidió fuerte:
mirando el artefacto sellado, el `NEEDED` no lo lleva un binario suelto sino
**`usr/lib/libadwaita-1.so.0`**, que es la librería contra la que enlaza toda app GTK4/GNOME de la
imagen. Sin proveedor del SONAME el loader falla en TODAS, no en una.

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

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

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

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

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

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

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

Verificado: mirada pasa de 10 sonames sin proveedor a 7, y los 7 que quedan son todos de `python3` y
`perl`.
2026-09-05 16:37:02 +00:00
Sergio 058c5231dc estado: cosecha granja 2026-09-05T16:32:14Z — avance del árbol KDE 2026-09-05 16:32:14 +00:00
Sergio b1a0b3323e estado: cosecha granja 2026-09-05T16:02:07Z — avance del árbol KDE 2026-09-05 16:02:07 +00:00
Sergio ab7cdf25e3 licencias: hammerd y portal-probe son MIT — su repo es el propio hammer
Las dos recetas apuntan a `ssh://gitea@git.gioser.net:2345/sergio/hammer.git`, o sea a este mismo
árbol, que declara MIT en `Cargo.toml` y trae su `LICENSE`. No hace falta clonar nada para saberlo:
la evidencia estaba en el directorio de trabajo. Los dos ArtifactHash, idénticos.
2026-09-05 15:44:55 +00:00
Sergio 96855001c6 licencias: 96% (1124/1164) — --fetch para los tarballs que no estaban en caché
Ocho paquetes más (giflib, pcre2, nghttp2, libogg, libvorbis, speexdsp, libuv, tea) con la misma
regla de evidencia. Su tarball no estaba en `work/tarballs/`, así que el modo `--fetch` lo baja a un
temporal, lo verifica contra el sha256 QUE LA RECETA PINEA —o es el árbol exacto o no se mira— y lo
borra. No se escribe en la caché de hammer a propósito: es suya, y un fichero puesto ahí por otro
camino es una vía de envenenamiento que nadie audita.

Dos correcciones al criterio, las dos porque produjo una afirmación falsa:

  · `gmp` salía GPL-3.0-or-later. Su `COPYING` es la GPLv3, pero al lado trae `COPYING.LESSERv3` y
    `COPYINGv2` porque la biblioteca es LGPL. La regla anterior —«el `COPYING` a secas gana al
    sufijado»— arregla socat, cuyo `COPYING.OpenSSL` es una excepción, y rompe gmp, donde el extra
    nombra otra VERSIÓN y no una dep. Esa diferencia es demasiado fina para codificarla sin
    equivocarse, así que ahora se identifican TODOS los ficheros de la raíz y sólo hay veredicto si
    dicen lo mismo. gmp, ffmpeg y los tres `gi-*` quedan pendientes, que es lo correcto: en ffmpeg
    la respuesta ni siquiera está en el árbol, la deciden los flags de la receta.

  · Pero «varios ficheros» no es «ambigüedad»: el `COPYING` de pcre2 son dos líneas apuntando a
    `LICENCE.md`, y los dos dicen BSD-3. Por eso se unen las identificaciones en vez de contar
    ficheros — ambiguo es que digan cosas DISTINTAS.

Y el TSV pasa a ser ACUMULATIVO. Una receta ya sembrada sale del conjunto de entrada porque ya tiene
`license`, así que regenerar el fichero entero borraba su cita — y la cita es el rastro de
auditoría, lo único que deja revisar un veredicto sin repetir el trabajo. Un fichero de evidencia
que se olvida de lo que ya probó no es evidencia.

Los 10 ArtifactHash afectados, idénticos antes y después.
2026-09-05 15:43:51 +00:00
Sergio b606bf988b targets.toml: atuq entra en los cuatro escritorios
Hasta ahora atuq estaba sellado y NO estaba en ninguna imagen. Es exactamente la lección que este
fichero ya aprendió con `foot` en el perfil de sway: una receta sellada que ningún perfil declara no
la lleva nadie, y la métrica de clausura no lo puede ver porque mide lo DECLARADO.

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

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

NOTA sobre la métrica: el grafo del hub sólo reporta base/cli/escritorio-mirada — los cuatro
escritorios no aparecen en `by_profile` porque sus raíces viven en las colas. Es previo a este
cambio y no lo introduce; queda anotado porque significa que esta declaración NO se ve todavía en
`build-state.json`.
2026-09-05 15:36:08 +00:00
Sergio ba1fb2d3a8 licencias: 95% (1114/1164) — la evidencia sale del tarball pineado, offline
`licencias.sh --sembrar` escribe desde una tabla curada a mano y `licencias-desambiguar.sh` resuelve
`-only` vs `-or-later` preguntándole a la búsqueda de código de GitHub. Los dos dejan fuera lo mismo:
lo que nadie curó y lo que no vive en GitHub.

Pero `work/tarballs/` indexa cada tarball por su sha256, así que el árbol EXACTO que la receta pinea
ya está en disco: la declaración del propio autor, en el commit que construimos, sin red. De las 76
sin licencia, 55 tenían su tarball cacheado y 25 salieron con evidencia citable.

`scripts/licencias-tarball.py` la busca en tres niveles: la declaración del autor (`Cargo.toml`,
`meson.build`), un único fichero en `LICENSES/` (REUSE, que usa KDE), y el texto del COPYING más la
CONCESIÓN buscada en las cabeceras de los fuentes — excluyendo COPYING/LICENSE, porque el apéndice
de la GPL trae literalmente «or (at your option) any later version» y buscarla ahí da siempre
positivo siendo plantilla. Es la regla que ya fijó `licencias-desambiguar.sh`, aplicada al árbol
pineado en vez de a GitHub.

La primera versión resolvía 40, y CUATRO estaban mal. Las dejo escritas porque son la forma del
error, no accidentes:

  · `socat` salía BSD-2 por su `COPYING.OpenSSL`, que es la excepción, no la licencia. Un `COPYING`
    a secas gana ahora a cualquier sufijado.
  · `pigz` salía Apache-2.0 por `zopfli/COPYING`: la licencia de una pieza VENDORIZADA leída como la
    del contenedor. Sólo se mira la raíz.
  · `openssh` salía MIT porque su `LICENCE` es un compendio de cuatro y me quedaba con la primera
    que pegara. Si el texto trae varias, no hay veredicto: lo compone un humano.
  · `nano` salía GPL-3.0-**or-later** citando el «either version 2» de su `aclocal.m4` — plantilla de
    autotools, no del proyecto, y encima de otra versión. Ahora la cita tiene que venir de un fichero
    del autor Y hablar de la misma versión mayor que el COPYING.

Y dos bugs míos que producían el mismo daño en silencio: la marca de BSD-3 era «Neither the name of»,
que libpcap y libzip no usan («The names of the authors may not be used to endorse») ⇒ se declaraban
BSD-2; y el mayor de versión lo sacaba de `"GPL-3.0".rsplit("-",1)[0][-1]`, que da «L», así que la
comprobación de coherencia rechazaba TODA cita válida.

La validación final no es una regex: un identificador vale si tenemos su texto en `licenses/`. La
obligación legal es acompañar el binario del TEXTO, así que un SPDX que no podemos entregar no
adelanta nada y sí crea una afirmación que no se sostiene. Eso es lo que atrapa el «GPL2+» que meson
deja escribir en `gsd-schemas` y `libgdm`, que no es un SPDX sino taquigrafía.

Nada se sembró a ojo: cada línea de `docs/licencias-evidencia.tsv` lleva la cita que la decidió, y
las 50 que quedan salen listadas con lo que SÍ se encontró en vez de rellenarse.

Comprobado que no se invalida nada: los 26 ArtifactHash afectados son idénticos antes y después
(`license` no está en `hash_inputs`) — medido, no supuesto.
2026-09-05 15:34:35 +00:00
Sergio 33f7c37312 SDD 26 §2.quinquies: la v0.3 destapó que la unidad 6 dependía de ésta sin decirlo
Tres mecanismos medidos para poner una página de inicio: `distribution/extensions/` no instala nada
(sideloading retirado, y el fallo es MUDO), `policies.json` sí se lee y falla nombrándose, y la pref
de firma no alcanza porque la exigencia viene compilada.

Lo que destrabó el diagnóstico fue un TESTIGO: `defaultPref` no deja rastro en prefs.js, así que un
autoconfig que no corre es indistinguible de uno que corre y no hace nada. Con una pref de usuario
escrita desde el .cfg se pudo separar las dos hipótesis sin abrir el navegador.

Y el hallazgo que cambia el plan: sin `MOZ_REQUIRE_SIGNING` vacío, el `sct` del §6.1 —el
diferenciador que nadie más tiene— tampoco se podría shipear nunca. La unidad 6 colgaba de ésta y el
documento no lo decía.
2026-09-05 15:33:54 +00:00
Sergio eb20e1d91e estado: cosecha granja 2026-09-05T15:33:38Z — avance del árbol KDE 2026-09-05 15:33:38 +00:00
Sergio 6a5408adae estado: cosecha granja 2026-09-05T15:05:08Z — avance del árbol KDE 2026-09-05 15:05:08 +00:00
Sergio 9f79362201 estado: cosecha granja 2026-09-05T14:32:59Z — avance del árbol KDE 2026-09-05 14:32:59 +00:00
Sergio 5db1c7ec89 estado: cosecha granja 2026-09-05T14:02:07Z — avance del árbol KDE 2026-09-05 14:02:07 +00:00
Sergio 4b9f8aa392 estado: cosecha granja 2026-09-05T13:32:14Z — avance del árbol KDE 2026-09-05 13:32:14 +00:00
Sergio 323475a6f1 estado: cosecha granja 2026-09-05T13:02:07Z — avance del árbol KDE 2026-09-05 13:02:07 +00:00
Sergio af0e90ba8d estado: cosecha granja 2026-09-05T12:32:10Z — avance del árbol KDE 2026-09-05 12:32:10 +00:00
Sergio 0bda91a374 estado: cosecha granja 2026-09-05T12:02:20Z — avance del árbol KDE 2026-09-05 12:02:20 +00:00
Sergio 00910be94d SDD 26 §2.quater: abrirlo en una pantalla destapó tres fallos que la clausura no veía
Con atuq sellado y verificado, se hidrató y se abrió en waypipe. No arrancó, y los tres motivos son
justamente la diferencia entre «sellado» y «usable»: el EXDEV de hidratar a otro mount, el lanzador
que no puede ser un symlink porque los ELF no traen RPATH, y un bug del CORPUS —atk se había tragado
GObject entero por declarar la glib estática siendo una .so—.

El tercero es el que vale escribir: el síntoma (45 GLib-GObject-CRITICAL y un SIGSEGV) no nombra a
atk por ningún lado, y no se ve mirando el rootfs porque había UNA sola libgobject. Se caza
preguntando quién DEFINE el símbolo, no quién lo usa.

Y una corrección de número: firefox se reconstruye en ~50 minutos, no en las cuatro horas que este
documento venía repitiendo. Ese número era folclore de la receta, no una medición.
2026-09-05 11:47:51 +00:00
Sergio 9250f07a56 estado: cosecha granja 2026-09-05T11:01:56Z — avance del árbol KDE 2026-09-05 11:01:56 +00:00
Sergio 14fbb42834 estado: cosecha granja 2026-09-05T10:31:58Z — avance del árbol KDE 2026-09-05 10:31:58 +00:00
Sergio 6d8bb7d901 estado: cosecha granja 2026-09-05T10:02:14Z — avance del árbol KDE 2026-09-05 10:02:14 +00:00
Sergio 901ed3ea02 estado: cosecha granja 2026-09-05T09:32:20Z — avance del árbol KDE 2026-09-05 09:32:20 +00:00
Sergio 9d0ed92b60 estado: cosecha granja 2026-09-05T09:02:08Z — avance del árbol KDE 2026-09-05 09:02:09 +00:00
Sergio 1728fd65e5 firefox NO reproduce bit a bit: el BuildID es la fecha del build — medido y anotado, no arreglado de paso
Al abrir `application.ini` para el branding de atuq apareció esto:

    BuildID=20260905035306

Es la fecha y hora del build. Dos construcciones de la MISMA receta, con el mismo ArtifactHash,
producen bytes distintos. Es justo el agujero que el invariante de reproducibilidad existe para
cerrar, y no se ve en `build-state.json` porque el hash es input-addressed y no se mueve por esto.

El arreglo es conocido y lo usa Alpine: exportar `MOZ_BUILD_DATE` con un valor determinista.

NO se aplica de paso, a propósito: vive en una FASE, así que re-hashea la receta y cuesta otro build
de cuatro horas más el re-sellado de atuq, que cuelga de ella. Es su propia unidad de trabajo y
queda como 4.c en el plan del SDD 26.

El comentario se comprobó que NO mueve el hash antes de commitearlo (b3:aa90192a antes y después):
un comentario fuera de las fases no entra en `hash_inputs`, así que el firefox sellado sigue válido.

Y el §2.ter del SDD queda con el estado real de atuq v0.2 y su evidencia.
2026-09-05 08:50:17 +00:00
Sergio 4843756757 estado: cosecha granja 2026-09-05T08:32:09Z — avance del árbol KDE 2026-09-05 08:32:09 +00:00
Sergio fcc99aabfb estado: cosecha granja 2026-09-05T08:02:07Z — avance del árbol KDE 2026-09-05 08:02:07 +00:00
Sergio 03f442de11 estado: cosecha granja 2026-09-05T07:32:10Z — avance del árbol KDE 2026-09-05 07:32:10 +00:00
Sergio e15be575ab estado: cosecha granja 2026-09-05T07:01:53Z — avance del árbol KDE 2026-09-05 07:01:53 +00:00