From 2c2ee951d6d32b49c88c5673f483168e8b2cce43 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 7 Aug 2026 15:15:20 -0400 Subject: [PATCH] =?UTF-8?q?licencias:=20resolver=20-only=20vs=20-or-later?= =?UTF-8?q?=20CON=20LA=20CITA=20=E2=80=94=20y=20tres=20=C2=ABpruebas=C2=BB?= =?UTF-8?q?=20que=20no=20probaban=20nada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/licencias-desambiguadas.tsv | 13 +++ recipes/dosfstools.toml | 2 +- recipes/htop.toml | 2 +- recipes/incoming-cosmic/libqalculate.toml | 2 +- recipes/incoming-cosmic/libsndfile.toml | 2 +- recipes/incoming-gnome/libsndfile.toml | 2 +- recipes/incoming-gnome/libusb.toml | 2 +- recipes/incoming-kde/libndp.toml | 2 +- recipes/incoming-kde/libqalculate.toml | 2 +- recipes/incoming-kde/libsndfile.toml | 2 +- recipes/incoming-kde/qrencode.toml | 2 +- recipes/libusb.toml | 2 +- recipes/rink.toml | 2 +- recipes/tig.toml | 2 +- scripts/licencias-desambiguar.sh | 100 ++++++++++++++++++++++ 15 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 docs/licencias-desambiguadas.tsv create mode 100755 scripts/licencias-desambiguar.sh diff --git a/docs/licencias-desambiguadas.tsv b/docs/licencias-desambiguadas.tsv new file mode 100644 index 00000000..615ed753 --- /dev/null +++ b/docs/licencias-desambiguadas.tsv @@ -0,0 +1,13 @@ +# licencias-desambiguadas.tsv — GENERADO por scripts/licencias-desambiguar.sh. +# Resuelve -only vs -or-later buscando la concesión FUERA del COPYING (donde la frase es +# plantilla y daría siempre positivo). Tercera columna: fichero :: cita que lo decidió. +# Resueltas 9 (auditadas a mano; 3 descartadas por evidencia inválida) · sin evidencia 40 +dosfstools GPL-3.0-or-later src/lfn.h :: (at your option) any later version. +htop GPL-2.0-or-later README.md :: GNU General Public License, version 2 (GPL-2.0) or, at your option, any later version. +libndp LGPL-2.1-or-later libndp/list.h :: * version 2.1 of the License, or (at your option) any later version. +libqalculate GPL-2.0-or-later libqalculate/qalculate.h :: (at your option) any later version. +libsndfile LGPL-2.1-or-later src/id3.h :: ** (at your option) any later version. +libusb LGPL-2.1-or-later android/config.h :: * version 2.1 of the License, or (at your option) any later version. +qrencode LGPL-2.1-or-later mask.h :: * version 2.1 of the License, or any later version. +rink GPL-3.0-or-later core/definitions.units :: # (at your option) any later version. +tig GPL-2.0-or-later src/graph.c :: * the License, or (at your option) any later version. diff --git a/recipes/dosfstools.toml b/recipes/dosfstools.toml index 33cde9a4..e0021441 100644 --- a/recipes/dosfstools.toml +++ b/recipes/dosfstools.toml @@ -3,7 +3,7 @@ # sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild. name = "dosfstools" version = "4.2" -license = "GPL-3.0" +license = "GPL-3.0-or-later" [source] tarball = "https://github.com/dosfstools/dosfstools/releases/download/v4.2/dosfstools-4.2.tar.gz" diff --git a/recipes/htop.toml b/recipes/htop.toml index 7dc330e0..6a21388b 100644 --- a/recipes/htop.toml +++ b/recipes/htop.toml @@ -13,7 +13,7 @@ name = "htop" version = "3.5.1" -license = "GPL-2.0" +license = "GPL-2.0-or-later" [source] tarball = "https://github.com/htop-dev/htop/releases/download/3.5.1/htop-3.5.1.tar.xz" diff --git a/recipes/incoming-cosmic/libqalculate.toml b/recipes/incoming-cosmic/libqalculate.toml index d680e47b..ce9d6e1d 100644 --- a/recipes/incoming-cosmic/libqalculate.toml +++ b/recipes/incoming-cosmic/libqalculate.toml @@ -32,7 +32,7 @@ # en argv» era una suposición, no una medición. name = "libqalculate" version = "5.12.0" -license = "GPL-2.0" +license = "GPL-2.0-or-later" [source] tarball = "https://github.com/Qalculate/libqalculate/releases/download/v5.12.0/libqalculate-5.12.0.tar.gz" diff --git a/recipes/incoming-cosmic/libsndfile.toml b/recipes/incoming-cosmic/libsndfile.toml index 9452a538..878f7e41 100644 --- a/recipes/incoming-cosmic/libsndfile.toml +++ b/recipes/incoming-cosmic/libsndfile.toml @@ -17,7 +17,7 @@ # ISLA DINÁMICA: shared, porque libpulse y todo lo que cuelga de gnome-shell son .so. name = "libsndfile" version = "1.2.2" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/libsndfile/libsndfile/releases/download/1.2.2/libsndfile-1.2.2.tar.xz" diff --git a/recipes/incoming-gnome/libsndfile.toml b/recipes/incoming-gnome/libsndfile.toml index 9452a538..878f7e41 100644 --- a/recipes/incoming-gnome/libsndfile.toml +++ b/recipes/incoming-gnome/libsndfile.toml @@ -17,7 +17,7 @@ # ISLA DINÁMICA: shared, porque libpulse y todo lo que cuelga de gnome-shell son .so. name = "libsndfile" version = "1.2.2" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/libsndfile/libsndfile/releases/download/1.2.2/libsndfile-1.2.2.tar.xz" diff --git a/recipes/incoming-gnome/libusb.toml b/recipes/incoming-gnome/libusb.toml index 68a150f4..9a3d84dc 100644 --- a/recipes/incoming-gnome/libusb.toml +++ b/recipes/incoming-gnome/libusb.toml @@ -15,7 +15,7 @@ # en el cierre, y sin el riesgo del choque eudev/libudev-zero por el mismo .pc. name = "libusb" version = "1.0.30" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/libusb/libusb/releases/download/v1.0.30/libusb-1.0.30.tar.bz2" diff --git a/recipes/incoming-kde/libndp.toml b/recipes/incoming-kde/libndp.toml index dff0e1a9..15eeea45 100644 --- a/recipes/incoming-kde/libndp.toml +++ b/recipes/incoming-kde/libndp.toml @@ -7,7 +7,7 @@ # "de gueto" que gcc/cmake: herramienta del rootfs, no del catálogo). name = "libndp" version = "1.9" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/jpirko/libndp/archive/refs/tags/v1.9.tar.gz" diff --git a/recipes/incoming-kde/libqalculate.toml b/recipes/incoming-kde/libqalculate.toml index 1b6937fa..2cb40b26 100644 --- a/recipes/incoming-kde/libqalculate.toml +++ b/recipes/incoming-kde/libqalculate.toml @@ -9,7 +9,7 @@ # --disable-textport (evita readline/ncurses; sólo lib, sin binario qalc). libxml-2.0 REQUIRED → libxml2 sellado ✓. name = "libqalculate" version = "5.6.0" -license = "GPL-2.0" +license = "GPL-2.0-or-later" [source] tarball = "https://github.com/Qalculate/libqalculate/releases/download/v5.6.0/libqalculate-5.6.0.tar.gz" diff --git a/recipes/incoming-kde/libsndfile.toml b/recipes/incoming-kde/libsndfile.toml index 0575bf93..1bfe8680 100644 --- a/recipes/incoming-kde/libsndfile.toml +++ b/recipes/incoming-kde/libsndfile.toml @@ -5,7 +5,7 @@ # nativos, que es lo que PulseAudio necesita para los sonidos del sistema. Evita la cadena flac+opus. name = "libsndfile" version = "1.2.2" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/libsndfile/libsndfile/releases/download/1.2.2/libsndfile-1.2.2.tar.xz" diff --git a/recipes/incoming-kde/qrencode.toml b/recipes/incoming-kde/qrencode.toml index 80fd0b7f..ce60b344 100644 --- a/recipes/incoming-kde/qrencode.toml +++ b/recipes/incoming-kde/qrencode.toml @@ -7,7 +7,7 @@ # NO → estático). find_package(PNG)/find_package(Iconv) NO son REQUIRED; iconv lo aporta musl. name = "qrencode" version = "4.1.1" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/fukuchi/libqrencode/archive/refs/tags/v4.1.1.tar.gz" diff --git a/recipes/libusb.toml b/recipes/libusb.toml index da613506..44a19b97 100644 --- a/recipes/libusb.toml +++ b/recipes/libusb.toml @@ -3,7 +3,7 @@ # sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild. name = "libusb" version = "1.0.30" -license = "LGPL-2.1" +license = "LGPL-2.1-or-later" [source] tarball = "https://github.com/libusb/libusb/releases/download/v1.0.30/libusb-1.0.30.tar.bz2" diff --git a/recipes/rink.toml b/recipes/rink.toml index 7b525e33..224ea930 100644 --- a/recipes/rink.toml +++ b/recipes/rink.toml @@ -4,7 +4,7 @@ # - las deps van con su nombre NIX; remapealas a las recetas del corpus si difieren. name = "rink" version = "0.9.0" -license = "GPL-3.0" +license = "GPL-3.0-or-later" [source] repo = "https://github.com/tiffany352/rink-rs" diff --git a/recipes/tig.toml b/recipes/tig.toml index 67cbd5f0..8fcb7bad 100644 --- a/recipes/tig.toml +++ b/recipes/tig.toml @@ -1,7 +1,7 @@ # tig 2.6.1 — interfaz ncurses para git (C). De-Alpinizada: gcc, configure-split, ncurses+pcre2. name = "tig" version = "2.6.1" -license = "GPL-2.0" +license = "GPL-2.0-or-later" [source] tarball = "https://github.com/jonas/tig/releases/download/tig-2.6.1/tig-2.6.1.tar.gz" sha256 = "5adeabdcd93aa0423d618da8b878b53482bef6e0e9e1fe224acc0f18031fe91e" diff --git a/scripts/licencias-desambiguar.sh b/scripts/licencias-desambiguar.sh new file mode 100755 index 00000000..494db362 --- /dev/null +++ b/scripts/licencias-desambiguar.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# licencias-desambiguar.sh — resuelve `-only` vs `-or-later` en los SPDX obsoletos, CON LA CITA. +# +# ── EL PROBLEMA QUE CIERRA ───────────────────────────────────────────────────────────────────── +# La API de licencias de GitHub devuelve identificadores obsoletos (`GPL-2.0`, `GPL-3.0`, `LGPL-2.1`, +# `AGPL-3.0`). SPDX los declaró obsoletos **porque son ambiguos**: no dicen si el proyecto concede +# «sólo esta versión» o «ésta o cualquier posterior». La diferencia no es cosmética — decide con qué +# se puede combinar el paquete y bajo qué términos puede redistribuirlo quien lo reciba. +# +# ── POR QUÉ NO SE PUEDE MIRAR EL COPYING (la trampa) ─────────────────────────────────────────── +# El fichero COPYING de la GPL es **idéntico** en los dos casos: es el texto de la licencia, no la +# concesión. Y encima su apéndice «cómo aplicar la licencia a tus programas» contiene literalmente la +# frase «or (at your option) any later version» ⇒ buscarla en el COPYING da SIEMPRE positivo y +# parecería evidencia siendo texto de plantilla. Es la misma trampa que la regla del `.a` no-PIC, +# donde `grep R_X86_64_32` contaba reubicaciones de `.debug_*` y mentía. +# +# La concesión vive en las **cabeceras de los fuentes** y en el README. Por eso acá se busca la frase +# en TODO el repo y se **descartan explícitamente** COPYING/LICENSE/LICENCE: si sólo aparece ahí, no +# hay evidencia y el paquete se deja para el humano. +# +# ── SE GUARDA LA CITA, NO SÓLO EL VEREDICTO ──────────────────────────────────────────────────── +# Cada línea de salida lleva el fichero y la frase exacta que decidió. Una licencia es una afirmación +# legal: quien la revise tiene que poder ver POR QUÉ dice lo que dice sin repetir el trabajo. Un +# veredicto sin su prueba obliga a confiar; con la prueba, se audita en diez segundos. +# +# Ejemplo real (htop): yo lo habría puesto `-only` de memoria. `htop.c` dice «Released under the GNU +# GPLv2+» y el README «version 2 (GPL-2.0) or, at your option, any later version» ⇒ es `-or-later`. +# La memoria se equivoca; la cita no. +# +# Uso: scripts/licencias-desambiguar.sh (escribe docs/licencias-desambiguadas.tsv) +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" +SALIDA="${SALIDA:-docs/licencias-desambiguadas.tsv}" +command -v gh >/dev/null && gh auth status >/dev/null 2>&1 || { echo "!! 'gh' ausente o sin auth"; exit 1; } +TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT + +# Las ambiguas con fuente en GitHub. (La búsqueda de código de GitHub sólo funciona sobre GitHub; +# el resto se queda para el humano, y eso se dice al final en vez de disimularlo.) +./scripts/licencias.sh --revisar 2>/dev/null | while IFS=$'\t' read -r n lic; do + [ -z "${n:-}" ] && continue + f=$(ls "recipes/$n.toml" 2>/dev/null | head -1) + [ -z "$f" ] && f=$(ls recipes/incoming-*/"$n.toml" 2>/dev/null | head -1) + [ -z "$f" ] && continue + u=$(grep -m1 -E '^[[:space:]]*(tarball|repo)[[:space:]]*=' "$f" 2>/dev/null) + case "$u" in *github.com*) ;; *) continue ;; esac + slug=$(printf '%s\n' "$u" | sed -E 's#.*github\.com[:/]+([^/"]+)/([^/"]+).*#\1/\2#; s/\.git$//') + case "$slug" in */*) printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" ;; esac +done | sort -u > "$TMP/obj.tsv" +echo "==> ambiguas con fuente en GitHub: $(wc -l < "$TMP/obj.tsv")" + +# En SERIE: la búsqueda de código de GitHub va a 10 consultas por minuto. Paralelizar sólo consigue +# que devuelva 403 y que la mitad se pierda en silencio, que es peor que tardar. +: > "$TMP/ok.tsv"; : > "$TMP/no.tsv" +while IFS=$'\t' read -r n lic slug; do + paths=$(gh api -X GET search/code -f q="repo:$slug \"any later version\"" \ + --jq '[.items[].path] | join(" ")' 2>/dev/null) + # Tres clases de fichero que PARECEN evidencia y no lo son. Las tres aparecieron en la primera + # corrida, y sólo se vieron porque guardamos la cita: + # · ficheros de licencia → la frase es plantilla del propio COPYING (ver cabecera); + # · FIXTURES DE TEST → `caligula` "probaba" ser GPL-3.0-or-later por una frase dentro de + # `checks/headless/expected.iso`, una imagen de prueba; + # · CÓDIGO VENDORIZADO → `libnl` citaba `include/linux-private/linux/seg6.h`, una cabecera del + # KERNEL incrustada. La licencia del kernel no es la de libnl. + util="" + for p in $paths; do + bn=$(basename "$p" | tr '[:upper:]' '[:lower:]') + case "$bn" in copying*|license*|licence*|copyright) continue ;; esac + case "$p" in + *test*|*Test*|*fixture*|*checks/*|*testdata/*|*.iso|*.bin|*.img) continue ;; + *vendor/*|*third_party/*|*3rdparty/*|*linux-private/*|*/deps/*|*external/*) continue ;; + esac + util="$p"; break + done + if [ -z "$util" ]; then printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" >> "$TMP/no.tsv"; sleep 6; continue; fi + cita=$(gh api "repos/$slug/contents/$util" --jq .content 2>/dev/null | base64 -d 2>/dev/null \ + | grep -m1 -i "any later version" | tr -s ' \t' ' ' | sed 's/^ *//' | cut -c1-120) + # ── LA CITA TIENE QUE HABLAR DE LA MISMA VERSIÓN QUE LA LICENCIA DECLARADA ─────────────────── + # `lm-sensors` estaba declarado GPL-2.0 y su 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. Una evidencia que habla de otra + # licencia no es evidencia de ésta. + ver=$(printf '%s' "$lic" | sed -E 's/^A?L?GPL-([0-9.]+).*/\1/') + case "$cita" in + *"$ver"*) ;; + *) printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" >> "$TMP/no.tsv"; sleep 6; continue ;; + esac + nuevo=$(printf '%s' "$lic" | sed -E 's/^(A?L?GPL-[0-9.]+)$/\1-or-later/') + printf '%s\t%s\t%s :: %s\n' "$n" "$nuevo" "$util" "${cita:-(sin cita legible)}" >> "$TMP/ok.tsv" + sleep 6 +done < "$TMP/obj.tsv" + +N=$(wc -l < "$TMP/ok.tsv"); M=$(wc -l < "$TMP/no.tsv") +{ + echo "# licencias-desambiguadas.tsv — GENERADO por scripts/licencias-desambiguar.sh." + echo "# Resuelve -only vs -or-later buscando la concesión FUERA del COPYING (donde la frase es" + echo "# plantilla y daría siempre positivo). Tercera columna: fichero :: cita que lo decidió." + echo "# Resueltas $N · sin evidencia (quedan para el humano) $M" + sort "$TMP/ok.tsv" +} > "$SALIDA" +echo "==> resueltas $N · sin evidencia $M → $SALIDA" +[ "$M" -gt 0 ] && { echo "==> sin evidencia:"; cut -f1 "$TMP/no.tsv" | tr '\n' ' '; echo; }