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

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

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

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

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

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

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

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