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>
118 lines
7.2 KiB
Bash
Executable File
118 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# licencias.sh — mide y puebla el campo `license` de las recetas. SDD 19 §2.1.
|
|
#
|
|
# ── EL PROBLEMA ─────────────────────────────────────────────────────────────────────────────────
|
|
# Medido el 2026-08-07: **0 de 1141 recetas** declaraban licencia. (Un informe anterior dijo «5 de
|
|
# 771»; 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 ... /usr/share/licenses/` y un
|
|
# comentario. Y las recetas son 1141, no 771. Lección: contar con `grep -l <palabra>` sobre TOML
|
|
# cuenta comentarios y nombres, no campos.)
|
|
#
|
|
# Sin ese dato la distro no puede cumplir la GPL cuando redistribuye binarios: la obligación es
|
|
# acompañar el binario del texto de la licencia y poder entregar la fuente correspondiente. Es
|
|
# BLOQUEANTE para publicar, y es la deuda que más barato sale pagar temprano: a 1141 recetas es un
|
|
# rato de trabajo, a 2600 es un proyecto.
|
|
#
|
|
# ── POR QUÉ ESTO NO RE-HASHEA NADA (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`. O sea que se puede poblar
|
|
# en las 1141 recetas YA SELLADAS sin mover un solo ArtifactHash ni invalidar el baseline de
|
|
# reproducibilidad. Está clavado con un test: `licencia_round_trip_y_no_afecta_el_hash`.
|
|
#
|
|
# ── DÓNDE VA LA LÍNEA (trampa de TOML, cuesta una tarde si se ignora) ───────────────────────────
|
|
# En TOML una clave suelta después de un `[table]` pertenece a ESA tabla. `license = "..."` puesto al
|
|
# final del fichero 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`.
|
|
#
|
|
# ── LA REGLA DE HONESTIDAD: NO SE ADIVINA ───────────────────────────────────────────────────────
|
|
# Declarar mal una licencia es PEOR que no declararla — convierte un hueco visible en una afirmación
|
|
# falsa sobre la que alguien va a construir. Por eso este script no infiere de la URL ni del nombre.
|
|
# Sólo puebla desde `docs/licencias-conocidas.tsv`, una tabla curada y auditable a mano, y desde los
|
|
# ficheros COPYING/LICENSE de árboles de fuente realmente extraídos. Lo que no tiene evidencia se
|
|
# queda vacío y SE CUENTA. Un hueco contado es deuda; un hueco rellenado a ojo es una mentira.
|
|
#
|
|
# ── EL ARREGLO ESTRUCTURAL, QUE ES OTRO TICKET ──────────────────────────────────────────────────
|
|
# Poblar a mano tapa el agujero de hoy pero no impide que vuelva a abrirse: cada receta nueva nace
|
|
# sin licencia. El cierre de verdad es capturarla donde ya pasa el dato gratis — hammer YA descarga y
|
|
# extrae cada tarball al construir, así que la fase de fetch puede guardar el COPYING/LICENSE del
|
|
# árbol junto al artefacto, sin descargas extra. Y el texto de la licencia dentro del paquete conviene
|
|
# inyectarlo en `hammer pack`, NO en la fase install de la receta: pack es aguas abajo del
|
|
# ArtifactHash, así que cumplir la GPL no cuesta reconstruir el corpus. Ver SDD 19.
|
|
#
|
|
# Uso: scripts/licencias.sh informe de cobertura (default, no toca nada)
|
|
# scripts/licencias.sh --sembrar escribe las licencias de la tabla curada
|
|
# scripts/licencias.sh --faltan lista las recetas sin licencia, para trabajarlas
|
|
set -uo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
|
TABLA="${TABLA:-docs/licencias-conocidas.tsv}"
|
|
MODO="${1:-informe}"
|
|
|
|
recetas() { ls recipes/*.toml recipes/incoming-*/*.toml 2>/dev/null; }
|
|
|
|
# Un campo `license` de verdad: clave a principio de línea, ANTES del primer `[table]`.
|
|
# (Ver arriba: después de un `[table]` la clave pertenece a la tabla y no la lee el parser.)
|
|
tiene_licencia() {
|
|
awk '/^\[/{exit} /^[[:space:]]*license[[:space:]]*=/{print "si"; exit}' "$1" | grep -q si
|
|
}
|
|
|
|
licencia_de() {
|
|
awk -F'"' '/^\[/{exit} /^[[:space:]]*license[[:space:]]*=/{print $2; exit}' "$1"
|
|
}
|
|
|
|
case "$MODO" in
|
|
# ── sembrar: escribir las licencias de la tabla curada ──────────────────────────────────────────
|
|
--sembrar)
|
|
[ -s "$TABLA" ] || { echo "!! falta la tabla curada: $TABLA"; exit 1; }
|
|
puestas=0; ya=0; sin_receta=0
|
|
while IFS=$'\t' read -r pkg lic; do
|
|
case "$pkg" in ''|'#'*) continue ;; esac
|
|
[ -n "${lic:-}" ] || continue
|
|
hallada=0
|
|
for f in recipes/"$pkg".toml recipes/incoming-*/"$pkg".toml; do
|
|
[ -f "$f" ] || continue
|
|
hallada=1
|
|
if tiene_licencia "$f"; then ya=$((ya+1)); continue; fi
|
|
# Insertar DESPUÉS de la línea `version = ...`, que está en la raíz del TOML y antes de
|
|
# cualquier `[table]`. Es el único sitio seguro (ver la trampa de TOML en la cabecera).
|
|
if grep -qE '^version[[:space:]]*=' "$f"; then
|
|
awk -v L="license = \"$lic\"" '
|
|
{print}
|
|
!done && /^version[[:space:]]*=/ {print L; done=1}
|
|
' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
|
|
puestas=$((puestas+1))
|
|
else
|
|
echo " ?? $pkg: sin línea `version` en la raíz, no toco el fichero"
|
|
fi
|
|
done
|
|
[ "$hallada" = 0 ] && sin_receta=$((sin_receta+1))
|
|
done < "$TABLA"
|
|
echo "==> sembradas $puestas · ya tenían $ya · en la tabla pero sin receta $sin_receta"
|
|
echo "==> verificá que el hash NO se movió: ./scripts/store-gc.sh (debe dar los mismos vigentes)"
|
|
;;
|
|
|
|
# ── faltan: la lista de trabajo ─────────────────────────────────────────────────────────────────
|
|
--faltan)
|
|
recetas() { ls recipes/*.toml recipes/incoming-*/*.toml 2>/dev/null; }
|
|
for f in $(recetas); do tiene_licencia "$f" || basename "$f" .toml; done
|
|
;;
|
|
|
|
# ── informe (default) ───────────────────────────────────────────────────────────────────────────
|
|
*)
|
|
tot=0; con=0
|
|
: > /tmp/.lic-cuenta.$$
|
|
for f in $(recetas); do
|
|
tot=$((tot+1))
|
|
if tiene_licencia "$f"; then con=$((con+1)); licencia_de "$f" >> /tmp/.lic-cuenta.$$; fi
|
|
done
|
|
pct=0; [ "$tot" -gt 0 ] && pct=$((con*100/tot))
|
|
echo "==> licencias declaradas: $con de $tot recetas (${pct}%)"
|
|
if [ "$con" -gt 0 ]; then
|
|
echo "==> reparto:"
|
|
sort /tmp/.lic-cuenta.$$ | uniq -c | sort -rn | head -25 | sed 's/^/ /'
|
|
fi
|
|
rm -f /tmp/.lic-cuenta.$$
|
|
[ "$con" -lt "$tot" ] && echo "==> faltan $((tot-con)). Lista: scripts/licencias.sh --faltan"
|
|
echo "==> recordá: NO se rellena a ojo. Sin evidencia, se deja vacío y se cuenta (ver cabecera)."
|
|
;;
|
|
esac
|