Files
takana/scripts/licencias-detectar.sh
SergioandClaude Opus 5 dba9ec47c5 licencias: el detector borraba la evidencia que decia registrar
Consulta solo las recetas que HOY no tienen licencia, asi que su cosecha MENGUA
a cada pasada: lo sembrado ayer ya no sale en --faltan. Y reescribia el fichero
entero con la cosecha del dia. Hoy una pasada con CERO detecciones dejo
docs/licencias-detectadas.tsv en 0 filas y se llevo las 610 que documentaban de
donde salia cada licencia ya sembrada.

Salio con exit 0 diciendo «escrito» y «sembrar con». Un vacio que llega hasta el
final afirmando que todo fue bien — la regla 3 de CLAUDE.md, esta vez sobre el
registro de procedencia en vez de sobre un artefacto.

Ahora FUSIONA con lo registrado (la fila nueva gana) y se niega a sobrescribir
si el resultado pierde filas: un registro de evidencia que encoge es un fallo,
no un resultado. Con CON=0 lo dice y no invita a sembrar nada.

Y las cuatro de HashiCorp quedan declaradas: BUSL-1.1.

No es adivinado ni sale de la API —que devuelve NOASSERTION en las cuatro, y por
eso llevaban meses sin licencia—: es el texto del LICENSE del TAG QUE LA RECETA
PINEA, no el de la rama por defecto. consul v1.22.7, vault v1.21.4, nomad
v1.11.3 y packer v1.15.4 abren con «Business Source License 1.1».

⚠ BUSL-1.1 NO es una licencia libre: prohibe el uso en produccion que compita
con el producto de pago, y solo pasa a MPL-2.0 cuatro años despues de cada
version. Cuatro paquetes del catalogo publicable no son redistribuibles como si
fueran libres.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5DComN9Nu47TBpYMBHBn
2026-08-20 17:00:15 +00:00

120 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# licencias-detectar.sh — deriva la licencia de las recetas cuya fuente vive en GitHub,
# preguntándole a GitHub por el fichero LICENSE REAL del repo. Complemento de `licencias.sh`.
#
# ── POR QUÉ ESTO NO VIOLA LA REGLA DE «NO SE ADIVINA» ───────────────────────────────────────────
# `licencias.sh` prohíbe rellenar a ojo, y con razón: declarar mal una licencia convierte un hueco
# visible en una afirmación falsa. Esto NO es adivinar. La API `/repos/{o}/{r}/license` devuelve el
# resultado de **detectar el fichero LICENSE que el repo tiene de verdad** (GitHub usa licensee), no
# una etiqueta que alguien escribió a mano en una web. Es la misma evidencia que miraríamos nosotros
# abriendo el tarball, obtenida sin descargar 686 tarballs por un enlace de 8 Mbps.
#
# ── ⚠ LA LIMITACIÓN, QUE SE REGISTRA EN VEZ DE ESCONDERSE ───────────────────────────────────────
# GitHub detecta sobre la **rama por defecto**, no sobre el tag/commit que la receta pinea. En la
# enorme mayoría de proyectos es lo mismo, pero NO es una garantía: un proyecto pudo relicenciarse
# después de la versión que empaquetamos. Por eso:
# · la salida va a un fichero APARTE (`docs/licencias-detectadas.tsv`) con procedencia y fecha,
# no directo a las recetas — se puede auditar y rehacer;
# · sólo se aceptan SPDX **definidos**: se descartan `NOASSERTION`, `null` y `other`, que es
# justamente GitHub diciendo «no estoy seguro». Un «no sé» de la fuente se propaga como hueco,
# no se redondea a una licencia plausible.
# Si algún día hace falta exactitud a nivel de tag, el cierre correcto es el estructural: capturar
# el COPYING/LICENSE en la fase de fetch, que ya descarga y extrae el tarball pineado.
#
# Uso: scripts/licencias-detectar.sh (escribe docs/licencias-detectadas.tsv)
# luego: TABLA=docs/licencias-detectadas.tsv scripts/licencias.sh --sembrar
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
SALIDA="${SALIDA:-docs/licencias-detectadas.tsv}"
JOBS="${JOBS:-8}"
command -v gh >/dev/null || { echo "!! falta el CLI 'gh'"; exit 1; }
gh auth status >/dev/null 2>&1 || { echo "!! 'gh' no está autenticado"; exit 1; }
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
# 1. Las que NO tienen licencia y cuya fuente es github.com → «nombre<TAB>owner/repo»
./scripts/licencias.sh --faltan 2>/dev/null | while IFS= read -r n; do
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
# Sirve para https://github.com/o/r/... y para git@github.com:o/r.git
slug=$(printf '%s\n' "$u" | sed -E 's#.*github\.com[:/]+([^/"]+)/([^/"]+).*#\1/\2#; s/\.git$//')
case "$slug" in */*) printf '%s\t%s\n' "$n" "$slug" ;; esac
done | sort -u > "$TMP/objetivos.tsv"
TOT=$(wc -l < "$TMP/objetivos.tsv")
echo "==> a consultar: $TOT recetas con fuente en GitHub"
# 2. Preguntar a GitHub. Sólo SPDX definidos.
export TMP
xargs -P "$JOBS" -I{} sh -c '
n=$(printf "%s" "{}" | cut -f1); slug=$(printf "%s" "{}" | cut -f2)
lic=$(gh api "repos/$slug/license" --jq ".license.spdx_id" 2>/dev/null)
# ⚠ VALIDAR LA FORMA, NO SÓLO LOS «NO SÉ» CONOCIDOS. Cuando el repo da 404 (renombrado, borrado,
# privado), `gh api` escribe el CUERPO DEL ERROR JSON en stdout y el filtro --jq falla ⇒ `lic`
# queda valiendo `{"message":"Not Found",...}`. Filtrando sólo ""/null/NOASSERTION/other, ese JSON
# pasaba como si fuera una licencia y se escribía en la receta: comillas incluidas, lo que además
# ROMPE el TOML y hace que el ArtifactHash deje de calcularse. Pasó de verdad en 5 recetas el
# 2026-08-07 y lo cazó la verificación de hashes, no la vista.
# ⇒ Regla: aceptar sólo lo que TIENE FORMA de expresión SPDX (letras, dígitos, . - + y los
# operadores OR/AND/WITH). Todo lo demás es un fallo disfrazado de dato.
case "$lic" in
""|null|NOASSERTION|other) printf "%s\t%s\n" "$n" "$slug" >> "$TMP/sin.tsv" ;;
*[!A-Za-z0-9.+\ -]*) printf "%s\t%s\n" "$n" "$slug" >> "$TMP/sin.tsv" ;;
*) printf "%s\t%s\t%s\n" "$n" "$lic" "$slug" >> "$TMP/con.tsv" ;;
esac
' < "$TMP/objetivos.tsv"
CON=$(wc -l < "$TMP/con.tsv" 2>/dev/null || echo 0)
SIN=$(wc -l < "$TMP/sin.tsv" 2>/dev/null || echo 0)
{
echo "# licencias-detectadas.tsv — GENERADO por scripts/licencias-detectar.sh. No editar a mano."
echo "#"
echo "# Procedencia: API de GitHub /repos/{owner}/{repo}/license, que detecta el fichero LICENSE"
echo "# REAL del repositorio (licensee). Es evidencia, no una etiqueta escrita a mano."
echo "#"
echo "# ⚠ LIMITACIÓN REGISTRADA: la detección es sobre la RAMA POR DEFECTO, no sobre el tag que la"
echo "# receta pinea. Casi siempre coinciden, pero un proyecto pudo relicenciarse después de la"
echo "# versión que empaquetamos. Los SPDX indefinidos (NOASSERTION/other/vacío) se DESCARTAN: un"
echo "# «no sé» de la fuente se propaga como hueco, no se redondea a una licencia plausible."
echo "#"
echo "# Formato: <receta><TAB><SPDX><TAB><owner/repo consultado>"
echo "# Consultadas $TOT · con SPDX definido $CON · sin SPDX utilizable $SIN"
# ── SE FUSIONA CON LO YA REGISTRADO, NO SE REEMPLAZA ─────────────────────────────────────────
# Este script consulta sólo las recetas que HOY no tienen licencia, así que su cosecha MENGUA a
# cada pasada: lo que sembró ayer ya no aparece en `--faltan`. Reescribir el fichero con la
# cosecha del día borraba la procedencia de todo lo anterior — el 2026-08-20 una pasada con CERO
# detecciones dejó el TSV en 0 filas y se llevó por delante las 610 que documentaban de dónde
# salía cada licencia ya sembrada. Y salió con éxito, diciendo «escrito» y «sembrar con»: un
# vacío que llega hasta el final afirmando que todo fue bien (regla 3 de CLAUDE.md).
# El fichero es el REGISTRO DE EVIDENCIA de lo detectado, no el parte de la última pasada.
{ grep -v '^#' "$SALIDA" 2>/dev/null; cat "$TMP/con.tsv" 2>/dev/null; } \
| awk -F'\t' 'NF && !vista[$1]++' | sort
} > "$TMP/salida.nueva"
# El fichero sólo se reemplaza si el resultado NO PIERDE filas. Un registro de evidencia que encoge
# es un fallo, no un resultado: se para y se dice, en vez de sobrescribir y avisar de nada.
ANTES=$(grep -vc '^#' "$SALIDA" 2>/dev/null || echo 0)
DESPUES=$(grep -vc '^#' "$TMP/salida.nueva" 2>/dev/null || echo 0)
if [ "$DESPUES" -lt "$ANTES" ]; then
echo "!! ABORTO: la fusión daría $DESPUES filas y $SALIDA tiene $ANTES. No lo sobrescribo."
echo "!! La versión nueva queda en: $TMP/salida.nueva (se borra al salir; copiála si la querés)"
cp "$TMP/salida.nueva" "$SALIDA.rechazado" 2>/dev/null && echo "!! copia en $SALIDA.rechazado"
exit 1
fi
mv "$TMP/salida.nueva" "$SALIDA"
echo "==> con SPDX definido: $CON · descartadas por indefinido: $SIN"
if [ "$CON" -eq 0 ]; then
echo "==> NADA NUEVO que sembrar: las $SIN consultadas dan SPDX indefinido (NOASSERTION/other)."
echo "==> Ése es el residuo duro: hay que LEER el LICENSE del tag pineado."
echo "==> $SALIDA queda intacto con sus $DESPUES filas de evidencia previa."
else
echo "==> escrito: $SALIDA ($DESPUES filas, $CON nuevas)"
echo "==> sembrar con: TABLA=$SALIDA ./scripts/licencias.sh --sembrar"
fi