Era la ultima superficie de CLI que quedaba con el nombre viejo. Mismo patron
que forja: el canonico es el nuevo, el viejo sigue aceptandose como alias.
El default pasa a .../release/takana.
El DESTINO no se toca: el binario se sigue instalando en /usr/bin/hammer dentro
del rootfs del producto, porque ese rootfs se hashea. Moverlo cambia el hash del
producto y obliga a rehacer el baseline del selfhost — es etapa 6 y decision
aparte, no efecto colateral de renombrar un flag.
Verificado en el subcomando correcto (bootstrap builder, no product): las dos
formas se aceptan. 605 tests en verde.
Lo destapó empaquetar gnome-tweaks: su meson pide `pygobject-3.0 >= 3.46` y resolvió sin receta
nueva, porque `recipes/incoming-gnome/py3-gobject.toml` ES PyGObject 3.50.0 —la escribió el frente
GNOME para el build de libgweather— y publica `pygobject-3.0.pc`.
Esta mañana la firmé `opcional` razonando que sólo la usan los tests de libsecret y modemmanager.
Eso sigue siendo cierto para esas dos recetas, pero el veredicto estaba mal de CATEGORÍA: `opcional`
dice «no la queremos» y el hecho es «ya la tenemos». La diferencia no es cosmética — un `provisto`
realimenta el sembrador como alias y la saca de la frontera; un `opcional` la deja saliendo en cada
barrido.
OCTAVO alias, y estrena la sexta forma de fallar el cruce por nombre: el prefijo `py3-` de nuestro
catálogo contra el nombre de upstream. Las anteriores eran puntuación (nlohmann_json), mayúsculas
(libxfont_2), prefijo lib (gusb), versión en el nombre (glad2) y homonimia cruzada (libmpc/mpc).
Y la lección de método, que es la que vale: el veredicto de esta mañana lo saqué mirando SÓLO a
quién la pedía y por qué. No miré si el catálogo ya la tenía bajo otro nombre — que es justo la
comprobación que la categoría `provisto` existe para hacer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
GNOME deja de ser la cáscara: tres extensiones y la app que el proyecto no instala y todo el mundo
termina instalando. El perfil queda 190/190 con 28 raíces, deuda 0.
dash-to-panel v73 junta dash, ventanas y bandeja en una sola barra (lo que más cambia el uso)
appindicator v64 devuelve los ICONOS DE BANDEJA que GNOME quitó; sin ella las apps que usan
StatusNotifierItem corren y no tienen dónde mostrarse
blur-my-shell v72 (ya estaba; se le saca el override, ver abajo)
gnome-tweaks 46.1 tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que
GNOME esconde, y el interruptor de las extensiones
⚠ LA LISTA DE ACTIVAS SE MUEVE A UN SOLO SITIO, y esto era una bomba de relojería. `enabled-extensions`
es un ARRAY: un override de GSettings lo escribe ENTERO. Con la primera extensión el override vivía
en su receta y funcionaba; con la segunda NO se habrían sumado — ganaría la que compile última
(orden alfabético) y las otras quedarían instaladas y MUERTAS, sin un solo error. Ahora la arma
`scripts/gnome/gnome-start-qemu.sh`, que ya compilaba los esquemas al arrancar, enumerando los uuid
presentes en /usr/share/gnome-shell/extensions. La política es «lo que la imagen instala, se
activa»; el dconf del usuario gana sobre el default.
Una receta NO puede ser ese sitio: hammer exige `[source]` en toda receta («missing field `source`»),
así que no hay forma de escribir una receta de pura política. Se probó el bloque contra un árbol de
juguete: lista las dos extensiones, salta un directorio sin metadata.json, el override compila con
stderr vacío y `gsettings get` devuelve las dos. Y si la lista cambia borra `gschemas.compiled` para
forzar recompilación — sin eso, una imagen ya arrancada ignoraría la extensión nueva en silencio.
⚠ EL HALLAZGO CARO: `msgfmt` de gettext-tiny REVIENTA con las formas plurales del árabe. SIGILL,
exit 132, «index 6 out of bounds for type 'size_t[6]'», y deja un .mo de 0 bytes. Reproducido FUERA
de la receta con el binario sellado. No es el idioma sino el mensaje: blur-my-shell compila su
ar.po sin problema porque no tiene NINGÚN plural (msgstr[5] = 0); el de tweaks tiene exactamente
uno. Acá se saca `ar` del LINGUAS y se pierde la traducción de UNA app, con el diagnóstico escrito
en la receta. ARREGLARLO ES OTRA UNIDAD DE TRABAJO: gettext-tiny la usan 108 recetas.
Cada receta usa el mecanismo de SU upstream, que no es el mismo en las tres:
blur-my-shell `make build` llama a `gnome-extensions pack` —el CLI que nuestro shell apaga— así
que se copia el árbol a mano, con el layout LEÍDO del zip de EGO y comparado
contra él (idéntico, sin sobras ni faltantes)
dash-to-panel su Makefile tiene un `install` que honra DESTDIR y hace lo de un paquete de
distro: gschema y locale a los directorios GLOBALES. ⚠ `VERSION=73` se pasa a
mano porque sin ella el Makefile hace `git describe` y el árbol viene por
`git archive`, sin `.git` ⇒ metadata quedaría con el marcador "version": 9999
appindicator meson. ⚠ `-Dlocal_install=disabled` explícito: en `auto` decide mirando si el uid
es 0, o sea que hoy acierta POR ACCIDENTE — y si el lab dejara de correr como
root instalaría en $HOME/.local y el artefacto sellaría vacío. Y `jq` va en deps
aunque sea JavaScript puro: su meson saca el uuid del metadata.json con jq
⚠ gnome-tweaks NO costó ninguna receta nueva, y eso hubo que medirlo: su meson declara siete deps
mínimas de runtime y las siete ya estaban selladas. La que parecía traer una cadena de Python
entera —`pygobject-3.0`— la provee `py3-gobject`, que YA ESTABA en el corpus con otro nombre y
publica `pygobject-3.0.pc`. El nombre no es el hecho, otra vez.
Sí costó dos deps que el configure reclamó y que son el patrón `.pc Requires` → `[deps].build`:
`appstream` (la pide `libadwaita-1.pc` y el error culpa a libadwaita, que está bien instalada) y
`desktop-file-utils` (meson la busca como PROGRAMA para `update-desktop-database`).
Sus deps de RUNTIME —python3 y py3-gobject— van declaradas: es una app de Python, si no están en la
imagen se instala y no arranca, y ningún build lo delata.
Las cuatro licencias, verificadas en la fuente pineada y NINGUNA leída del COPYING —cuyo apéndice
«How to Apply» trae siempre la frase «or any later version»—: blur-my-shell del README del commit,
dash-to-panel y appindicator de la cabecera de sus fuentes, gnome-tweaks de su etiqueta SPDX.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
Medido: esta unit era el UNICO sitio fuera del repo que traia una variable
vieja puesta (Environment=HAMMER_DIR=/opt/hammer), y por eso es lo que impide
retirar la caida del codigo. Ahora fija las dos.
Se ponen las dos y no solo la nueva porque un script viejo —o una copia del
repo que todavia no sincronizo— solo mira HAMMER_DIR.
Desplegada y verificada en dev.gioser.net: daemon-reload + restart, servicio
active, y el entorno del servicio muestra las dos.
205 lineas en 73 ficheros de crates, mas la prosa de CLAUDE.md y del skill,
que se me habian quedado afuera de los barridos anteriores (no eran ni recetas
ni docs/ ni scripts/).
EL BARRIDO ANCHO ESTUVO A UN COMMIT DE ROMPER EL CORPUS ENTERO.
El primer intento reescribia los .rs completos, no solo los comentarios. Entre
las lineas de codigo que tocaba estaban SIETE etiquetas de separacion de
dominio, que son ENTRADA DE HASH:
b"hammer-tree-v1" <- el prefijo de ArtifactHash::of_tree (hash.rs:60)
b"hammer-seed-v1" la funcion que hashea TODOS los artefactos:
b"hammer-stage1-rootfs-v2" cambiarla mueve los 4750 hashes del store
b"hammer-product-rootfs-v3"
b"hammer-product-attested-v2"
b"hammer-builder-rootfs-v1"
b"hammer-attest-dev-rootkey-0001!!" <- clave raiz de atestacion, [u8;32]
Revertido y rehecho solo sobre comentarios, esquivando ademas las cadenas
crudas de Rust (r#"..."#) porque el SYSTEM_PROMPT del traductor tiene lineas
que empiezan como comentario.
Controles: las 7 etiquetas siguen ahi, el diff toca CERO lineas de codigo,
605 tests en verde y el hash de zlib sigue en b3:dc363f26.
La leccion es la misma de toda esta etapa: un literal que parece prosa puede
ser entrada de hash, y la unica forma de saberlo es mirar donde se usa.
5a recetas (1427 comentarios, cero hashes movidos y medido), 5b docs (59),
5c scripts (250, sin tocar una sola linea que no empiece por #).
Los dos guardianes que hicieron trabajo real: la linea base de hashes atrapo 3
recetas que se movian porque un comentario de SHELL dentro de una fase parece un
comentario de TOML; y el salteo de heredocs atrapo 3 MOTD que son texto del
producto, no comentarios.
Y queda anotado el bug que introdujo la etapa 4: atribuir-fallos.py casaba
contra el target de tracing, que es el module_path y por lo tanto el nombre del
crate. Quedo casando nada sin fallar.
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.
El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.
Y el hallazgo caro: casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.
Además 14 rutas de módulo en docs, que el barrido anterior no tocó
porque no es frontera de palabra.
645 líneas. Los ADR entran porque en este repo SON documentos vivos, no
registros inmutables: el 0013 tiene 5 commits, el 0009 dos. Eso se comprobó
antes de decidir, no se asumió por convención general.
EXCLUIDOS por ser REGISTRO o generado: docs/evidencia/ (6), el HANDOFF de la
noche de KDE (1) y docs/state/ (24, se regenera solo). Reescribir un comando
dentro de una evidencia la falsifica.
Y el ADR 0016 se excluye de todo barrido, con un aviso adentro para el próximo
que barra: habla SOBRE el renombre, así que necesita seguir diciendo 'hammer'.
El barrido se lo llevó puesto y lo dejó titulado 'Renombre del sistema: takana
→ takana'; revertido.
Congelados, verificados uno por uno con controles: /opt/hammer, /var/lib/hammer,
/usr/bin/hammer, /mnt/vvv/hammer, la URL de gitea, hammer-farm.service,
hammer-live-install.sh, BRIEFING-hammer.md, hammerd, hammer-recover y
HAMMER_LIVE.
Barrido de TEXTO puro: 1427 líneas de comentario TOML. Cero ArtifactHash
movidos, y eso está MEDIDO, no deducido: hasheé las 741 antes y después y los
ficheros de hashes son idénticos byte a byte (723 con hash real + 18 que ya no
hasheaban de antes).
El guardián valió la pena: el primer barrido, filtrando por 'la línea empieza
con #', movió el hash de helix, lsof y steam-runtime-sniper. La causa es que una
fase se escribe como compile = <triple> ... <triple> y sus comentarios de SHELL
también empiezan con #, pero viven dentro del VALOR — y las fases sí entran en
hash_inputs. El barrido ahora calcula los rangos de las cadenas multilínea de
TOML y no entra ahí.
Quedan intactos a propósito: .hammer-zig-cc y .hammer-cargo-vendor (literales
dentro de fases), hammerd, hammer-recover y toda ruta que empiece por /
Con lo que la medición cambió respecto del plan, los dos controles negativos
del helper, y la distinción que importa: la caída sirve al lector NUEVO; donde
el script exporta, hay que seguir poniendo la vieja porque el lector puede ser
un binario viejo.
TAKANA_X con caída a HAMMER_X (ADR 0016). No es un sed: estas variables son
contrato de usuario —knobs del instalador, el entorno del worker, mirror-env.sh—
y viven en perfiles de shell y units FUERA del repo, así que un renombre duro no
falla ruidosamente: la variable no aparece, se toma el default y el build se
comporta distinto sin que nada lo diga.
Rust: takana_core::env::{var,var_os} toma el nombre canónico y deriva el viejo
cambiando el prefijo — se le pasa TAKANA_* para que un grep del nombre nuevo
encuentre todas las lecturas. 5 tests, con DOS controles negativos: sin ninguna
de las dos no hay valor, y un nombre sin prefijo no inventa una caída.
Migrados los 17 sitios directos y los indirectos que el grep no mostraba
(bases_de_mirror, las constantes de kernel_cmd, ROOT_ENV de qorpa, env_path de
recover). takana-recover lleva la caída inline: es un mini-binario que se copia
a /usr/sbin y no vale arrastrarle una dep entera por dos líneas.
Scripts: 30 lecturas pasan a default, manteniendo el
nombre INTERNO de la variable para no tocar sus 190 usos.
Y donde el script EXPORTA en vez de leer, se ponen LAS DOS (mirror-env.sh y el
fragmento in-VM de bootstrap): ahí el lector puede ser un binario viejo —worker
sin recompilar, el /usr/bin/hammer pinado del baseline— que sólo conoce HAMMER_*.
La caída sirve para lectores nuevos; los viejos necesitan que la vieja siga puesta.
Verificado de punta a punta con el binario, no sólo con unit tests: HAMMER_LAB
sigue surtiendo efecto, TAKANA_LAB hace lo mismo, y con las dos gana TAKANA_LAB.
605 tests en verde y el hash de zlib sigue en b3:dc363f26… , intacto.
Cambio de comportamiento que va aparte y hay que decir: el hostname por defecto
de una instalación nueva pasa de 'hammer' a 'takana' (sólo si no se fija ninguna
de las dos variables).
NO se tocan: las rutas /var/lib/hammer de sistemas instalados, el volid
HAMMER_LIVE del ISO, ni el namespace HARKAQ_*, que es de otro subsistema.
La imagen de GNOME traía la CÁSCARA: el shell sabe cargar extensiones —`ui/extensionSystem.js` va
en el gresource sin opción que lo apague y su gschema sellado trae `enabled-extensions`— pero no
había ninguna que cargar. Esta es la primera, y fija el patrón para las otras ~1400.
⚠ NO HIZO FALTA TLS, que era la conclusión fácil del análisis de ayer. glib-networking sólo lo pide
BAJARLAS desde extensions.gnome.org con el descargador del shell. Empaquetada, una extensión se
instala como cualquier receta y no toca la red.
LA FUENTE ES EL REPO DEL AUTOR, NO EL ZIP DE EGO, por dos razones y las dos medidas:
1. hammer no sabe abrir un zip: el fetch extrae con `tar -x` y GNU tar contesta «This does not
look like a tar archive». Ninguna receta del corpus usa .zip.
2. Y aunque lo abriera, el zip de EGO es un ARTEFACTO que construye EGO desde este mismo repo.
Pinear el zip es pinear el binario de un tercero; pinear el commit es pinear el código.
El tag v72 es el que EGO sirve para shell 48 —preguntado a su API, no supuesto— y el metadata.json
declara ['46','47','48','49','50']. ⚠ Eso hay que preguntarlo por extensión: nixpkgs va por GNOME
50 y hay extensiones que ya sólo declaran el major nuevo.
SIN `gnome-extensions pack`, que es lo que usa el Makefile de upstream y es el CLI que nuestra
receta del shell apaga (-Dextensions_tool=false; encenderlo costaría gnome-autoar y re-hashear
gnome-shell entero). No hace falta: `pack` sólo arma un zip que después alguien descomprime, y lo
que el shell lee es un DIRECTORIO. El layout no se inventó — se leyó del zip de EGO v72, que es la
salida de ese mismo `pack`, y el artefacto sellado se comparó contra él: **entradas idénticas, ni
sobras ni faltantes**, 45 .mo compilados.
⚠ INSTALADA ≠ ACTIVA, y esto es lo que hace que la receta sirva. El shell sólo carga lo que esté en
`org.gnome.shell enabled-extensions`: sin eso el artefacto sella, el fichero está en la imagen y NO
PASA NADA al arrancar — el mismo cuadro de «sellado ≠ instalado» que este repo ya se comió con
foot, mpv y libnotify, un escalón más abajo. Se resuelve con un override de GSettings, que cambia
el DEFAULT y deja ganar al dconf del usuario que no la quiera.
PROBADO DE VERDAD, no deducido: se compiló el directorio de esquemas del shell con el override
puesto (`glib-compile-schemas`, exit 0 y stderr vacío — que hay que mirar, porque sale 0 aunque
RECHACE un esquema) y después `gsettings get org.gnome.shell enabled-extensions` devolvió
['blur-my-shell@aunetx'].
⚠ TRAMPA ANOTADA PARA LA SEGUNDA: `enabled-extensions` es un ARRAY y el override lo escribe ENTERO.
Dos recetas con su propio override no se suman — gana la que compile última y la otra queda
instalada y muerta, sin error. Cuando llegue la segunda, la lista pasa a un solo sitio.
Y una corrección sobre la licencia, que casi firmo mal: el LICENSE es el texto del GPLv3, y su
apéndice «How to Apply» CONTIENE la frase «either version 3 … or any later version» siempre, así
que grepearla ahí no distingue -only de -or-later (la trampa que la memoria del repo ya tenía
escrita). La evidencia buena es el README del commit pineado: «This program is distributed under
the terms of the GNU General Public License, version 3 or later». El árbol JS no lleva cabecera de
licencia en ningún fichero.
Va de RAÍZ en escritorio-gnome por la lección de `foot` que targets.toml ya aprendió tres veces.
El perfil queda 183/183 listo con 25 raíces, deuda 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
Decisión del usuario (2026-09-09): X11 se erradica, y eso incluye no construir el servidor que lo
mantiene vivo. La receta se retira; `xwayland` VUELVE a ser lo que sus propios documentos decían.
⚠ LO QUE APARECIÓ AL MIRAR: esto ya estaba decidido, por escrito y DOS VECES, el 2026-09-03
(commit f64859ba):
docs/state/targets.toml:143 «`xwayland` sigue acá como RAÍZ pero su receta NO se va a escribir:
lo provee una imagen ajena enjaulada … el hueco se disuelve sin
receta y sin tocar la postura Wayland-only»
docs/state/qorpa-ajenos.toml «X11 está fuera de alcance para toda la distro … el Xwayland vive
DENTRO de la imagen —Arch y Fedora ya lo traen— y se cuelga de
nuestro kwin por el socket. La receta no se escribe.»
El 2026-09-08 se escribió igual (3d8c6fc6, «cerrar los 3 huecos que quedaban»), y los dos
documentos siguieron diciendo lo contrario durante toda la jornada de hoy. No hubo que cambiar
ninguno de los dos: alcanzó con borrar la receta para que el repo volviera a coincidir con ellos.
MEDIDO ANTES DE SACARLA, no supuesto:
· Sólo kwin podía lanzarlo. mutter va con -Dxwayland=false y wlroots con -Dxwayland=disabled ⇒
GNOME, sway y COSMIC no podían usarlo aunque el binario existiera. Nunca sirvió a más de un
perfil de los cuatro.
· NINGUNA app del catálogo necesita servidor X: todo lo gráfico es Wayland nativo (Qt6 con
qtwayland, GTK3 Wayland-only, mpv, OBS con ENABLE_WAYLAND=ON, foot, fuzzel, swayimg). El
consumidor que lo justificaba está nombrado en qorpa-ajenos.toml y es Steam, que corre
enjaulado y TRAE EL SUYO ADENTRO.
· Y la imagen KDE ya arrancaba sin él: la propia receta contaba que KDE sellaba 1037/1037 con las
apps X11 muertas. Esto no estrena una configuración, vuelve a una ya probada.
EFECTO EN EL GRAFO, verificado regenerando: `xwayland` deja de ser una receta y pasa a clase
`ajeno` —que es como targets.toml decía que había que contarlo— y el perfil escritorio-kde baja de
304 a 299 nodos con deuda 0. Los cinco que se van son la cadena que sólo él usaba.
⚠ QUEDAN CINCO RECETAS HUÉRFANAS, y no las borro de paso: `libfontenc`, `libXfont2`, `libxkbfile`,
`libxshmfence` y `xkbcomp`. Medido: NADA más en el catálogo las declara, y ninguna es raíz de
ningún perfil ⇒ siguen selladas pero no entran en ninguna imagen. Sacarlas es su propia unidad de
trabajo y su propia decisión.
El triaje pasa de `provisto` a `opcional` con la historia entera en su `porque`, y con eso se cae
el alias `xwayland xwayland` de alias-triaje.txt, que habría afirmado que tenemos una receta con
ese nombre.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
Los 10 crates de librería + CLI renombrados, con los dos binarios congelados y
su razón. Anotada la consecuencia real: renombrar hammer-core mueve los bytes
de hammerd igual, así que el baseline of_tree del selfhost hay que rehacerlo.
Y la medición que cambia el plan: 'la variable HAMMER=' no existe como cosa
única — son 14 variables, ~260 apariciones, y el BINARIO las lee (HAMMER_LAB,
HAMMER_ZIG, HAMMER_WORK, HAMMER_ROOTFS, HAMMER_MIRROR_KEY, HAMMER_LLM_*). Con
knobs de instalador entre ellas y el entorno del worker trayéndolas puestas
desde fuera del repo. Eso es contrato de usuario, no churn: pide leer las dos
con caída a la vieja, que es código, no sed.
Sumada la evidencia del worker: tras reiniciar el servicio compiló los dos
binarios y cerró un ciclo real (dunst sellado, 1/1).
hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
instalados y hornea un hook de arranque que lo invoca por ese nombre:
renombrarlo rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
Se commitea DESPUÉS de verificar, no antes: el ADR afirma que el cron corrió
limpio con los scripts migrados, y ese ciclo (18:30:00Z → 18:32:21Z) ya está
en el log — siembra ✓, manifiesto ✓, los 9 JSON del grafo regenerados por
build-state.py invocando takana, estado pusheado, cero errores.
Documenta además cómo converge el worker, que se midió en vez de suponerse:
/opt/hammer no es un clon git sino rsync, hammer-farm.service corre el loop
como servicio largo, y los dos estados intermedios (antes y después de
reiniciarlo) son coherentes porque la 3a puso los dos binarios en los cargo
build antes de tocar ninguna invocación.
Los llamadores EJECUTABLES: scripts/ (incluida toda la granja), los runbooks y
CLAUDE.md. Seguro porque la 3a ya garantiza que el worker emite los dos
binarios, y porque en farm-lab-sync.sh el cargo build remoto precede a la
invocación remota en el mismo script.
Verificado: sintaxis de los 49 (bash -n / py_compile — ojo que
why-differs-barrido.sh es Python con extensión .sh) y `takana hash` devuelve
hash real sobre el store.
NO se toca en esta etapa, a propósito:
- La variable de entorno HAMMER=. Es interfaz de los scripts entre sí y hay
llamadores que la fijan; renombrarla va con la etapa 4.
- docs/evidencia/ y los HANDOFF: son REGISTRO de lo que se corrió ese día.
Reescribir un comando dentro de una evidencia la falsifica.
- docs/state/: es generado, se regenera solo.
- Los ADR y los docs de diseño: texto, y `hammer` sigue funcionando. Van con
la etapa 5, que es la de churn de texto.
Va solo y ANTES de cambiar ninguna invocación, a propósito. El worker compila
desde fuente (farm-worker-loop.sh) y la siembra excluye /target: si primero
cambiara las llamadas a `takana` y después el build, habría una ventana en la
que el worker sincroniza scripts nuevos y sigue teniendo sólo el binario viejo
— y eso no falla ruidosamente, deja de cosechar en silencio.
Con los dos emitidos, cualquier orden de sincronización queda sano.
Etapa 2 (ADR 0016): el binario canónico es `takana` y `hammer` se sigue
emitiendo. Son DOS [[bin]] al mismo main.rs, no un symlink: la siembra de la
granja excluye /target (un symlink del hub no existiría en el worker) y
`cargo clean` lo borraría. Ningún llamador tocado; los 124 siguen andando.
Adoptados los dos puntos de la hoja de marca que chocaban con contratos:
- `forja` como ALIAS de clap sobre `build`, no como reemplazo. El canónico
sigue siendo el inglés, que es lo que usan scripts, cron y runbooks. Y se
enmienda la regla 4 de CLAUDE.md en el mismo commit: cambiar el
comportamiento dejando escrito el contrato viejo es lo peor de las dos
opciones, porque el otro agente del repo aplica lo que lee.
- `.tkn` como extensión de paquete. Salió barato y por una razón medida: la
extensión no es lógica sino salida — se escribe en UN solo lugar
(main.rs:1800) y el descubrimiento va por índice, no por glob
(PackageEntry.file, repo.rs:75). Los repos con entradas .swm siguen
resolviendo y un repo mixto es válido; cero ficheros .swm versionados.
Los tipos Swm/SwmBuild/swm_bridge no se tocan: son internos, van en la
etapa 4.
287 tests en verde (hammer-cli + hammer-core), incluidos los que fabrican
repos con nombres .swm a mano — que son justamente la prueba de que la
compatibilidad hacia atrás se sostiene.
El sistema pasa de hammer a takana (martillo en quechua/aimara): traducción
literal, conserva la metáfora de forja y mantiene el registro agentivo del
resto de la familia (khipu/yupana/harkaq/qorpa/churay).
Medido antes de tocar nada: hash_inputs es una LISTA DE CAMPOS, no el fichero
crudo (recipe.rs:535) ⇒ los comentarios de las 692 recetas importadas se pueden
renombrar GRATIS, sin mover un solo ArtifactHash. Pero las fases SÍ entran al
hash: las 10 recetas con .hammer-zig-cc dentro de una fase se congelan, igual
que los 5 cargo_vendor_dir (que ni siquiera están en hash_inputs y aun así
pueden cambiar bytes).
El renombre va por etapas con alias; el directorio /mnt/vvv/hammer es lo último
porque el cron de la cosecha lo referencia por ruta absoluta y moverlo mata el
latido en silencio.
No se adoptan dos ejemplos de la hoja de marca: 'takana forja' (viola la regla 4,
los verbos van en inglés) y .tkn (el formato es .swm, 266 menciones).
El documento decía que `lsof` y `tzdata` «siguen vetando a propósito», y ya no: las 26 recetas que
faltaban están hechas desde su fuente pineada. Se agrega la sección del cierre, con el fallo del
guardián que apareció al medir (resolvía por nombre de FICHERO y el paquete se llama por su campo
`name`: 14 falsos vetos) y los dos paquetes que NO se pueden redistribuir, que ahora el veto ve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
CAMPAÑA CERRADA: 1166/1166 recetas declaran `license`. Ninguna adivinada — cada una sale del
fichero de licencia de su fuente PINEADA (tarball del sha256 de la receta, o el commit exacto en
la forja), y la cita queda como comentario en la propia receta.
⚠ Y EL GUARDIÁN ESTABA MAL, que es el hallazgo que vale más que las 26. `licencias-rootfs.sh`
resolvía la receta por NOMBRE DE FICHERO (`ls recipes/$pkg.toml`), y el paquete se llama por su
campo `name`, que en 34 recetas NO coincide: nu.toml→`nushell`, dust.toml→`du-dust`,
incoming-kde/qtbase.toml→`qt6-qtbase`… Medido: **14 paquetes que SÍ declaran licencia salían como
«licencia desconocida»** y el guardián vetaba una imagen perfectamente publicable.
El falso veto se nota; el hermano silencioso NO: si existe un `<pkg>.toml` que pertenece a OTRO
paquete, la versión vieja reportaba la licencia EQUIVOCADA sin decir nada. Hoy no pasa —medido,
0 casos, y de los 34 nombres duplicados CERO declaran licencias distintas—, pero ahora es
imposible en vez de improbable.
El arreglo tuvo que ser por LOS DOS lados, y el primer intento rompió el otro: el grafo de estado
nombra sus nodos por el fichero (`dust`) y el artefacto del store por `name` (`du-dust`), así que
resolver sólo por `name` dejaba a `dust` sin licencia. Ahora busca por `name` y cae al fichero.
Medido en los dos sentidos: corpus entero 1128/1128, perfil base+cli 81/81, cero sin licencia.
Y probado CON ROTURA A PROPÓSITO además del control, que es lo único que distingue a un guardián
que sirve de uno que nunca salta:
paquete inexistente en la lista → exit 1 y «ESTA IMAGEN NO SE PUEDE PUBLICAR»
control (zlib nushell qt6-qtbase lsof tzdata) → exit 0, y escribe los textos
(⚠ ojo al medir: `script | tail` devuelve el exit de `tail`. La primera corrida dijo exit=0 sobre
la rotura y no era el guardián, era el pipe.)
SE LEVANTA EL VETO QUE SDD 20 DEJÓ ESCRITO. Decía que `base` y `cli` iban con 2 paquetes cada una
con binarios y licencia desconocida: `lsof` y `tzdata`, «que necesitan la vía LicenseRef- y siguen
vetando a propósito». Hechos los dos, con su texto real en licenses/:
lsof → LicenseRef-lsof (licencia propia de Purdue, sin identificador SPDX)
tzdata → LicenseRef-tz-public-domain (su LICENSE: «all files in the tz code and data … are in
the public domain»; los tres ficheros BSD-3-Clause que menciona NO se instalan — la
receta sólo compila zic y deja /usr/share/zoneinfo)
⚠ DOS QUE NO SE PUEDEN REDISTRIBUIR, y ahora el veto los ve:
duplicacy NO ES LIBRE. Su LICENSE.md: «Free for personal use or commercial trial; non-trial
commercial use requires per-computer CLI licenses … $50 per year»
waybackurls NO DECLARA LICENCIA: en el commit pineado la raíz es .gitignore, README.mkd,
go.mod, main.go y script/ — sin LICENSE ni COPYING, y el README no la menciona. Sin
concesión expresa, el defecto es «todos los derechos reservados»
Los dos con LicenseRef y un texto en licenses/ que explica qué hay, en vez de dejar el campo vacío,
que se lee como «todavía no lo poblamos». Ninguno está hoy en un perfil de imagen; si alguien los
mete, el guardián corta.
De paso queda escrito el texto de `LicenseRef-qorpa-ajena-no-enumerable`, que ya se usaba en
steam-runtime-sniper y no tenía fichero; y `licencias-textos.sh` bajó los canónicos nuevos
(BSL-1.0 para boost, GCC-exception-3.1 que ya hacía falta).
Hueco conocido y anotado en la receta: `XFree86-1.0` (rama del OR de hwdata) se queda sin texto —
SPDX no publica ese identificador, sólo XFree86-1.1, que es otra licencia, y el tarball lo nombra
sin incluirlo. El guardián avisa y no veta, que es correcto: la otra rama del OR es la GPL y su
texto sí está.
NADA SE RE-HASHEA: `license` está fuera de `hash_inputs`. Verificado, no supuesto — `hammer hash`
sobre pigz, lsof y boost después de editarlas devuelve el hash cuyo artefacto YA está en el store.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
El triaje sirve para algo o no sirve, y eso se mide corriendo el sembrador otra vez. Regenerados
los SIETE perfiles de `seed-frontera.json` (1,3 s cada uno: el eval de nix estaba cacheado).
perfil antes ahora
base 41 41 +0
cli 44 44 +0
escritorio-cosmic 148 143 -5
escritorio-gnome 191 181 -10
escritorio-kde 276 266 -10
escritorio-mirada 39 38 -1
escritorio-sway 175 171 -4
UNIÓN 334 319 -15
Los 15 que se fueron son EXACTAMENTE los que el lazo cerrado puede sacar: los siete alias
(bubblewrap, mesa-gl-headers, nlohmann_json, libxfont_2, gusb, glad2, libmpc), los nix-ismos
(CUnit, ffmpeg-headless, lzip, validate-pkg-config, python3-3.14.7-env) y tres que dejaron de ser
candidatos porque ahora TIENEN receta (bash-completion, desktop-file-utils, purpose).
Y **cero candidatos nuevos**, que era la otra mitad de la pregunta: el barrido no se movió por
debajo mientras lo triábamos.
⚠ LO QUE ESTE NÚMERO NO DICE. Los 327 veredictos `opcional` NO encogen la salida del sembrador —
sólo `nix-ismo` y `provisto` realimentan. Es correcto que así sea (un `opcional` es una decisión
nuestra, no un error de nixpkgs), pero significa que la frontera va a seguir saliendo con ~319
candidatos de los que 316 ya están contestados. Quien la lea tiene que cruzarla con
`frontera-triaje.toml`, no leerla sola: `scripts/triaje.py` sin argumentos ya lo hace y dice
«0 nuevos».
De paso: 46 candidatos quedaron marcados `ausente = true` (estaban en barridos viejos y ya no
salen). El fichero de triaje los conserva con su veredicto en vez de borrarlos, que es lo correcto
— si alguno vuelve, vuelve ya contestado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
Cierran las 24 que quedaban de una vuelta. El barrido de la frontera queda así: 327 opcional,
25 provisto, 10 nix-ismo, 3 pendientes. Arrancó la jornada en 152 pendientes.
⚠ SÉPTIMO ALIAS, y resuelve el enigma que dejé abierto esta mañana. libmpc → mpc: nixpkgs llama
`libmpc` a GNU MPC y `mpc` al cliente de MPD; acá GNU MPC se llama `mpc`. O sea que el «lo piden
mpc y yambar» de libmpdclient no era un error del sembrador cruzando mal: es que los dos catálogos
usan el MISMO nombre para paquetes DISTINTOS. Las dos puntas de esa confusión quedan cerradas.
Van siete alias y cinco formas: puntuación, mayúsculas, prefijo `lib`, versión en el nombre, y
ahora homonimia cruzada.
Lo demás, cada uno con su prueba en la receta o en el fuente: libev cae por el mismo
--enable-lib-only de nghttp2 que c-ares (configure.ac:230); graphite2 por -Dgraphite=disabled;
libliftoff por -Dlibliftoff=disabled; libtasn1 por -Dtrust_module=disabled; libasyncns por
-Dasyncns=disabled; inih por -DEXIV2_ENABLE_INIH=OFF contra un default ON; rdma-core por
--disable-rdma. nanosvg y resvg caen juntas porque fuzzel VENDORIZA nanosvg (su receta ya lo
decía) y fcft va con -Dsvg-backend=none. boost-build no hace falta porque la receta de boost es
sólo cabeceras. validate-pkg-config no es una librería sino un setup hook de nixpkgs → nix-ismo.
CAPACIDADES AUSENTES que quedan dichas: sin polkitd no corren las reglas .rules de JavaScript
(duktape); no se regula el brillo de un monitor EXTERNO por DDC/CI (ddcutil); los PDF con
tipografías CJK no incrustadas se ven mal (poppler-data); dolphin navega sin panel de metadatos
ni etiquetas (baloo-widgets).
⚠ LOS TRES QUE NO FIRMO, y por qué. No son «opcional»: son huecos de RUNTIME que ninguna receta
delata al construir, y decidirlos es elegir alcance de la distro, no triaje. Los tres con la
medición hecha y escrita en su `porque`:
gnome-keyring NINGÚN artefacto del store declara org.freedesktop.secrets (grep sobre el store
entero), y el artefacto de kwallet trae sólo libKF6Wallet.so, sin kwalletd6. Los
dos escritorios tienen el PROMPTER sellado y ninguno tiene el ALMACÉN
glib-networking los usr/lib/gio/modules/ de los cuatro artefactos de glib están VACÍOS ⇒ GIO no
tiene TLS, y libsoup 3 delega el TLS en GIO ⇒ HTTPS mudo en el stack GNOME
xdg-desktop-portal-kde los únicos backends de portal sellados son el de cosmic y el de gnome; la
cola kde no tiene ni receta de xdg-desktop-portal ⇒ en Plasma la captura de
pantalla de obs-studio (linux-pipewire → portal ScreenCast) no tiene con quién
hablar, mientras que en GNOME y COSMIC sí
Los tres son el mismo patrón que ya nos costó una noche con las fuentes: una raíz que no resuelve
queda `wanted` y NINGUNA métrica lo dice. Marcarlos `hueco` los mete como raíz en targets.toml y
al drenaje; eso lo decide el usuario.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 53 pendientes a 27. Cierran okular, libsndfile, libpsl, libtiff, colord, libplacebo, zxing-cpp,
glib, modemmanager, plasma-nm y evolution-data-server.
⚠ EL HALLAZGO QUE ABARATA MÁS: qtwebengine. En plasma-nm, Qt6WebEngineWidgets se pide REQUIRED —
pero SÓLO dentro de `if(BUILD_OPENCONNECT)` (CMakeLists.txt:54-56). O sea que un Chromium entero
colgaba de la vista web de SSO de una VPN, y el -DBUILD_OPENCONNECT=OFF que la receta ya pasaba lo
saca del grafo. Vale la pena mirar dónde MÁS aparece qtwebengine antes de darlo por deuda.
⚠ DOS ALIAS MÁS, y cada uno estrena una FORMA nueva de fallar el cruce por nombre:
gusb → libgusb el prefijo `lib` (libgusb 0.4.9, ya en el [deps].build de colord)
glad2 → glad la VERSIÓN metida en el nombre: recipes/glad.toml pinea la 2.0.8, que ES glad2.
nixpkgs parte glad(v1) y glad2(v2) en dos paquetes; acá hay uno solo
Van seis alias y cuatro formas distintas: puntuación, mayúsculas, prefijo y versión. Ya no es
casualidad — cuando el sembrador vuelva a tocarse, normalizar por ahí.
DOS BUNDLEOS que parecían deps y no lo son, los dos verificados en el fuente:
zint zxing-cpp lo trae adentro (CMakeLists.txt:7, ZXING_USE_BUNDLED_ZINT ON por defecto, y
core/CMakeLists.txt:532 compila core/src/libzint)
publicsuffix-list la lista VIENE en el tarball de libpsl y --enable-builtin la hornea en el .so.
Traerla aparte sería meter un dato mutable en un store direccionable por contenido
Y autogen es andamiaje, no dep: configure.ac:68 busca el PROGRAMA y si falta sólo hace
`touch tests/*.c`; el Makefile.am:414 dice que los ficheros generados vienen en el tarball
«to prevent stale files from calling autogen in tarball releases».
CAPACIDADES AUSENTES que quedan dichas: okular abre PDF pero no PostScript (libspectre), ni DjVu,
ni previsualiza Markdown; no hay cliente VPN AnyConnect (openconnect); no hay calibración de
pantalla por colorímetro (argyllcms) ni stack de escáner (sane-backends).
Quedan 27 pendientes, todos de 1×. Dos de ellos NO son «opcional» y no los firmo de paso —
gnome-keyring y glib-networking, los dos con la medición hecha y escrita.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 78 pendientes a 53. Caen enteras opencv, swayimg, networkmanager, gwenview, appstream y
plasma-desktop.
⚠ CORRECCIÓN, y es de las que cambian lo que uno haría. En dos commits de hoy avisé que cairo
(lzo) y xwayland (libdecor) piden esas libs con `auto`/`required:false` y que «si entran al
catálogo, el ArtifactHash cambia sin que nadie lo pida». El mecanismo está mal: el sandbox de
hammer monta SÓLO las deps declaradas (docs/02-build-lab.md §3.4 — raíz tmpfs y bind read-only de
las deps del store), así que meterla en [deps] mueve hash_inputs y SE VE. La vía por la que un
`auto` sí muerde en silencio es otra y ya está documentada: que la lib aparezca en el LAB, que no
entra en hash_inputs. Los dos `porque` quedan corregidos en el fichero; el aviso sigue valiendo,
pero apunta al lab y no al catálogo.
Lo demás, cada uno con su prueba:
opencv -DBUILD_LIST=core,imgproc ⇒ de todo OpenCV se construyen DOS módulos: gflags y
glog son del dnn y hdf5-cpp del hdf, ninguno en la lista. eigen y openblas caen
por -DWITH_EIGEN=OFF y -DWITH_LAPACK=OFF, explícitos
swayimg -Davif/-Dheif/-Draw/-Dsixel=disabled, los cuatro escritos en la receta
networkmanager newt es la dep de nmtui y el propio meson lo dice en su assert (meson.build:806:
«Use -Dnmtui=false to disable it»), que es justo lo que pasamos; slang es su
back-end. bpftools cae por -Debpf=false
gwenview cfitsio (FITS de astronomía), libkdcraw (RAW) y kimageannotator son TYPE OPTIONAL
en su CMakeLists; qtimageformats no aparece ahí — son plugins de Qt de runtime
appstream -Dstemming=false para libstemmer
plasma-desktop kaccounts-integration es TYPE OPTIONAL y su PURPOSE lo dice («OpenDesktop
integration plugin»); xf86-input-evdev y xf86-input-libinput son drivers del
SERVIDOR X y la distro no corre uno; plasma-sdk no aparece en su CMakeLists
⚠ QUINTO, SEXTO Y SÉPTIMO DESFASE DE VERSIÓN: xapian, libfyaml y libblake3 no aparecen en NINGÚN
meson de AppStream 1.0.5, que es la que pineamos. Ya van siete candidatos que salen de expresiones
de nixpkgs más nuevas que nuestros pines (openapv, libebur128, spandsp, libglycin y estos tres).
Es un patrón, no una casualidad: cuando el `porque` heurístico dice «lo pide una sola receta»,
mirar primero si la versión pineada la nombra sale más barato que razonar sobre la feature.
⚠ dnsmasq no es una librería: NM la declara `type: 'string'` (meson_options.txt:10), una RUTA a un
binario que ejecuta. Capacidad ausente —compartir la conexión y el DNS con caché— no dep rota.
Y gnome-keyring sigue pendiente, ahora con la medición terminada escrita en su `porque`: el grep
sobre el store ENTERO dice que NINGÚN artefacto declara org.freedesktop.secrets, y del lado KDE el
artefacto de kwallet trae sólo libKF6Wallet.so — la librería cliente— sin kwalletd6. Los dos
escritorios tienen el prompter sellado y ninguno tiene el almacén.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 87 pendientes a 78. Las 4 de mutter y 5 de las 6 de gnome-shell. La sexta —gnome-keyring—
queda PENDIENTE a propósito y con motivo, abajo.
Las de mutter caen contra su meson leído, no contra su comentario:
argcomplete tools/meson.build:17 lo usa como PROGRAMA para generar el completado de bash de
`gdctl`, dentro del bloque `if bash_completion` ⇒ -Dbash_completion=false lo mata.
No se enlaza: no es una librería, es un generador
sysprof meson.build:453, `if have_profiler` ⇒ -Dprofiler=false
libstartup-notification default TRUE en meson.options:114 y la receta lo apaga a mano. Es el
cursor «ocupado» de X11; en Wayland lo reemplaza xdg-activation, que mutter
implementa nativamente ⇒ no se pierde la función, cambia de dónde sale
libglycin ⚠ CUARTO DESFASE DE VERSIÓN del barrido: mutter 48.8 no nombra glycin en ningún
fichero. El cargador de imágenes enjaulado entra en la serie 49 de GNOME
Y las de gnome-shell:
gdm aparcado POR DISEÑO, targets.toml:263. La sesión la lanza arje
gnome-autoar sólo la pide extensions-tool (su meson.build:32) y va -Dextensions_tool=false:
desempaqueta el .zip de una extensión, no pinta escritorio
gnome-clocks cero referencias en los meson: es una APP que el panel consulta en runtime para
los relojes del mundo
gnome-bluetooth cero referencias, y el hueco de fondo es el mismo de ayer: no hay bluez
libnma ⚠ otra vez la etiqueta y el hecho: gnome-shell 48.8 NO la nombra. Su camino de red
es libnm + libsecret-1 (meson.build:106-108), apagado con -Dnetworkmanager=false
⚠ POR QUÉ gnome-keyring SIGUE PENDIENTE. Es el primero del barrido que no parece «opcional» sino
falta de verdad, y decidirlo cambia el trabajo de la granja (un `hueco` nace como raíz en
targets.toml y entra al drenaje), así que no lo firmo de paso. Lo medido hasta acá:
js/ui/components/keyring.js:221 hace `own_name('org.gnome.keyring.SystemPrompter')` ⇒ el shell es
el que PREGUNTA la contraseña, no el que guarda el secreto. El almacén es el demonio, y no hay
receta de gnome-keyring en el catálogo. libsecret está sellada, pero libsecret es el CLIENTE.
Del lado KDE hay kwallet y plasma-workspace publica org.kde.secretprompter.service, o sea que ese
perfil sí parece tener con qué. Queda una medición corriendo sobre el store entero (quién declara
org.freedesktop.secrets) y con eso se decide; si nadie lo declara, es hueco y hay que decirlo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 105 pendientes a 87. Cuatro de las cinco recetas que más candidatos arrastraban ya están
cerradas (ffmpeg, obs-studio, xwayland, nodejs, pipewire).
Las 8 de nodejs son todas BUNDLEADAS, y no se dio por bueno el comentario de la receta: se listó
el propio node-v24.18.1.tar.gz y ahí están, deps/googletest, deps/histogram, deps/llhttp,
deps/merve, deps/nbytes, deps/simdjson, deps/uvwasi. simdutf es la excepción que confirma que
mirar sirve — NO está en deps/ de primer nivel sino en deps/v8/third_party/simdutf, que es
exactamente el fichero que la receta parchea para apagar el kernel AVX-512.
⚠ CAPACIDAD AUSENTE, y más honda que la perilla: ldacbt, libfreeaptx y liblc3 son los codecs de
audio Bluetooth (LDAC de Sony, aptX de Qualcomm, LC3 de LE Audio). Caen por -Dbluez5=disabled,
pero el hecho de fondo es que NO HAY receta de bluez en todo el catálogo: la distro no tiene
Bluetooth, ni de audio ni de nada. Eso conviene que esté dicho acá y no descubierto por alguien
que enchufa unos auriculares.
⚠ SEGUNDO Y TERCER DESFASE DE VERSIÓN del barrido (el primero fue openapv/ffmpeg): libebur128 y
spandsp no aparecen en NINGÚN fichero de pipewire 1.2.7, que es la que la receta pinea. Salen de
una expresión de nixpkgs para una pipewire más nueva. Van como 'opcional' y no 'nix-ismo' a
propósito, igual que openapv: mandarlas al descarte del sembrador las haría invisibles el día que
subamos de versión, que es justo cuando vuelven a ser una pregunta legítima.
El resto de pipewire son perillas apagadas a mano y verificadas contra su meson_options.txt:
libffado:350 (audio FireWire), libcamera:180, libmysofa:229 (HRTF de audio espacial), lv2:261
(lilv, el host de plugins) y roc:237 (audio en tiempo real por red).
Nota sobre el commit anterior: dije que convenía normalizar guiones y mayúsculas en seed-graph.py.
Medido después — normalizando nombre de candidato contra el campo `name` de las 1141 recetas, de
los 105 pendientes CERO son ese caso. nlohmann_json y libxfont_2 eran los únicos. Sigue siendo
buena profilaxis, pero no hay backlog que la pague: no se toca el sembrador por ahora.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 125 pendientes a 105. Las dos recetas más pedidas del ranking quedan sin un solo candidato
pendiente. Las de obs ya estaban medio contestadas en los comentarios de su propia receta; lo que
faltaba era mirar el ARTEFACTO y no sólo la línea de cmake.
⚠ DOS ALIAS MÁS, y los dos son el mismo defecto del sembrador: cruza por nombre literal.
nlohmann_json → nlohmann-json guion bajo vs guion
libxfont_2 → libXfont2 minúsculas vs mayúsculas (primero de esta forma)
Las dos recetas existen, están selladas y ya figuran en el [deps].build de quien «las pedía».
Van tres alias por puntuación o caso (con mesa-gl-headers, cuatro en total): conviene normalizar
en seed-graph.py antes que seguir triándolos de a uno.
MEDIDO, NO DEDUCIDO — el artefacto sellado de obs-studio:
· usr/lib/obs-plugins NO tiene obs-websocket.so ⇒ asio, websocket++ y qrcodegencpp no tienen a
quién servir. El `touch plugins/obs-websocket/CMakeLists.txt` de la receta funciona.
· readelf sobre los 13 plugins: CERO NEEDED a libcjson. El JSON que OBS enlaza es jansson.
· obs-x264.so está y enlaza libx264.so.165 ⇒ la codificación H.264 del escritorio existe y no
pasa por ffmpeg. Es lo que hace que -DENABLE_QSV11=OFF (libvpl) no cueste capacidad.
⚠ libxres es «la etiqueta no es el hecho» otra vez: lo que xwayland usa es `resourceproto`
(meson.build:90), las CABECERAS del protocolo XRes, que vienen en xorgproto y ya están en [deps].
libXres es la librería CLIENTE — el servidor IMPLEMENTA la extensión, no la consume.
⚠ Y libxaw/libxmu/libxpm/libxt no aparecen en NINGÚN meson del árbol: sólo en .appveyor.yml y
.gitlab-ci/debian-install.sh, que instalan las deps de todos los servidores del repo xserver
(Xorg, Xnest, Xvfb). El sembrador leyó la expresión de nixpkgs, que hereda esa lista entera.
⚠ SEGUNDA ESCOTILLA 'auto' de la jornada (la primera fue cairo/lzo): xwayland pide libdecor con
`required: false` y la opción en 'auto' (meson.build:210). Si libdecor entra al catálogo y cae en
su clausura, xwayland cambia de ArtifactHash sin que nadie lo haya pedido. Sólo decoraría la
ventana en modo ROOTFUL, que no es como lo lanza el compositor.
dri-pkgconfig-stub cierra limpio: include/meson.build:9 lo pide como
`dependency('dri', required: build_glx)` ⇒ obligatorio SÓLO con glx, y la receta va -Dglx=false
porque ninguna cola publica gl.pc. font-util aparece una vez y es el default de `fontrootdir`, la
raíz de las fuentes de mapa de bits legacy que la distro no publica.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 140 pendientes a 125. Todas caen por el mismo criterio ya establecido (--disable-autodetect ⇒
sólo entra lo que se pide, y no se pide nada), pero el veredicto NO se firmó de memoria: se midió
con nm/strings sobre store/…-ffmpeg/usr/lib/libavcodec.so.61.19.100, que es el artefacto que la
distro publica hoy.
LO QUE LA MEDICIÓN CAMBIA. La lectura fácil era «sin libaom/libvpx/x265 la distro no reproduce
AV1/VP9/HEVC», y es FALSA: los decoders nativos están compilados (aparecen «AV1 decoder», los bsf
vp9_*, hevc y theora). Lo que falta es CODIFICAR. El artefacto trae 34 encoders y ninguno de vídeo
moderno: AAC, FLAC, Opus, MPEG4, mjpeg, ffv1, prores, H.263, jpeg2000. O sea: la distro decodifica
lo de hoy y codifica lo de ayer.
Y esa frase tampoco alcanza sola, porque la codificación H.264 del escritorio NO pasa por ffmpeg:
obs-studio enlaza x264 —que SÍ está en el catálogo, recipes/x264.toml— por su propio plugin
obs-x264. Antes de anunciar «no se puede grabar vídeo» convenía mirar quién codifica de verdad.
⚠ openapv NO es una perilla que no encendimos: es DESFASE DE VERSIÓN. Sale de la expresión de
nixpkgs para ffmpeg 8.x y nuestra receta pinea 7.1, cuyo `configure` no nombra apv en ningún sitio
(verificado en el tarball). Queda 'opcional' y no 'nix-ismo' a propósito: el día que ffmpeg suba de
major vuelve a ser una pregunta legítima, y mandarla al descarte del sembrador la haría invisible.
⚠ ocl-icd y opencl-headers tienen el hueco una capa MÁS ABAJO: mesa va con -Dllvm=disabled
-Dgallium-rusticl=false ⇒ la distro no publica NINGÚN runtime de OpenCL. Un ICD loader sin ICD no
filtra nada; encender --enable-opencl en ffmpeg no compraría capacidad.
CAPACIDADES AUSENTES, dichas en voz alta y no descubiertas después:
libbluray no se navegan discos Blu-ray (menús, títulos, BD-J); los ficheros sueltos se leen
libopenmpt no suena música de módulos (MOD/XM/S3M/IT)
amf-headers / libvdpau codificación y decodificación por hardware de AMD y NVIDIA: piden driver
propietario, que la distro no trae ⇒ discutible sólo si eso cambia
El resto no cuesta nada: libtheora es el encoder de un formato de 2004 (su decoder es nativo),
xvidcore es REDUNDANTE con el «MPEG4 encoder» nativo que ya está, vid.stab agrega dos filtros de
estabilización y zimg sólo el filtro zscale (el escalado lo hace libswscale, compilada).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 152 pendientes a 140. Con esto el ranking del triaje queda entero en 1×: ninguna candidata
la piden ya dos recetas. Cada veredicto con la prueba en el fuente o en la receta, no de memoria.
⚠ mesa-gl-headers NO faltaba — SEGUNDO alias del barrido (el primero fue bubblewrap→bwrap). Es
el split de cabeceras de nixpkgs: el artefacto de mesa instala usr/include/{GL,GLES2,GLES3,KHR,
EGL}, verificado en el store y no deducido. El único hueco es GLES1, apagado a propósito en la
receta (-Dgles1=disabled).
⚠ libmpdclient destapa una FALSA ATRIBUCIÓN del sembrador: dice que la piden mpc y yambar, y
recipes/mpc.toml es GNU MPC (aritmética compleja, --with-gmp --with-mpfr), no el cliente de MPD.
Homónimo. El sembrador cruza por NOMBRE, así que este no va a ser el único; conviene mirar el
`piden` antes de creerle cuando el nombre es corto y genérico.
⚠ lzo deja una escotilla abierta que conviene que esté dicha: libarchive pasa --without-lzo2
explícito, pero cairo NO la apaga — meson.options:19 la declara feature 'auto' y meson.build:203
la tomaría si apareciera en el sandbox (sólo la usa util/cairo-script). Si algún día lzo entra al
catálogo y cae en la clausura de cairo, cairo cambia de ArtifactHash sin que nadie lo pida.
El resto, con su prueba:
c-ares configure.ac:225 de nghttp2 la fuerza a `no` DENTRO de --enable-lib-only, que es
lo que la receta pasa ⇒ ni presente se usaría; nodejs bundlea la suya
ada nodejs la bundlea a propósito y la receta lo dice con nombre y apellido
CUnit → nix-ismo: nghttp2 1.64.0 no la nombra en ningún fichero, sus tests usan munit
VENDORIZADO en tests/munit/. Dep rancia de nixpkgs; no vuelve ni con tests ON
egl-wayland EGLStream de NVIDIA: mutter meson.options:84 la trae en false y no la pedimos, y
xwayland 24.1.13 ya no la soporta (hw/xwayland/meson.build:171 have_eglstream=false)
glu CERO referencias en phonon 4.12.0 y xwayland 24.1.13, grep sobre el árbol entero
libssh ffmpeg --disable-autodetect sin --enable-libssh; konsole no la nombra: su plugin
SSHManager (ON, y lo construimos) lanza el binario ssh, que el corpus SÍ tiene
libxcb-errors wlroots -Dxcb-errors=disabled y yambar -Dbackend-x11=disabled, las dos escritas
dbus-python
pygobject sólo tests: mock-service*.py de libsecret; modemmanager -Dtests=false
Granja: las cinco colas selladas y deuda 0 (corpus 868, kde 1046, gnome 920, cosmic 894, wlr 879);
el worker está idle. Esto es trabajo de hub, que es lo que queda cuando no hay cola que drenar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
De 166 pendientes a 152.
⚠ bubblewrap NO faltaba: es el MISMO paquete con otro nombre. nixpkgs lo llama 'bubblewrap',
acá es 'bwrap' (recipes/bwrap.toml) y su artefacto instala /usr/bin/bwrap. Verificado —receta,
artefacto y binario— y marcado 'provisto' con su alias, que es justo para lo que existe esa
categoría. Es el primer alias real que aparece en todo el barrido.
ffmpeg-headless → nix-ismo: variante de empaquetado, ya tenemos ffmpeg. Mismo criterio que
git-minimal.
gnome-settings-daemon → aparcado POR DISEÑO y ya documentado en targets.toml:263: 'SIN
gnome-session / gnome-settings-daemon / gdm'. El camino vivo de GNOME es mutter → gnome-shell.
⚠ SEXTA capacidad ausente: mobile-broadband-provider-info destapa que la distro NO TIENE BANDA
ANCHA MÓVIL — networkmanager con modem_manager=false y ppp=false, modemmanager con mbim=false
qmi=false. De ahí caen también ppp y sbc (éste por el bluez ya apagado).
El resto son el cubo de ffmpeg --disable-autodetect (nv-codec-headers, librist, lame, fdk-aac,
zvbi) más openexr (opencv WITH_OPENEXR=OFF), libjxl (swayimg jxl=disabled) y libjack2
(jack=disabled en las tres que lo piden).
⚠ Y una corrección de método: mi primera búsqueda de alias fue por subcadena y produjo basura
—'nv-codec-headers' casaba con 'unconvert.toml'—. Sólo se marcó el que se verificó de verdad.
libssh tampoco se tocó: tenemos libssh2, que es OTRA librería, y konsole no lo menciona.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
De 178 pendientes a 166. Todos opcionales, todos con la prueba en la receta. Un hallazgo
transversal: ffmpeg lleva '--disable-autodetect', o sea que sólo entra lo que se pide
explícitamente y no se pide nada. Eso explica de golpe srt, speex, soxr y sdl2-compat.
xorg-server mutter x11=false xwayland=false, plasma-desktop X11=OFF
unzip sólo desempaqueta datos de test, y los tests están apagados
umockdev simula dispositivos en tests; sin tests no hay nada que simular
tinysparql gtk4 tracker=disabled ⇒ el selector de ficheros sin búsqueda por contenido
sphinx libtiff docs=OFF
srt/speex/soxr/sdl2-compat ffmpeg --disable-autodetect, obs ENABLE_NEW_MPEGTS_OUTPUT=OFF,
pulseaudio soxr=disabled
⚠ TRES CON COSTE que conviene que estén dichos, no descubiertos:
webrtc-audio-processing pipewire echo-cancel-webrtc=disabled y pulseaudio webrtc-aec=disabled
⇒ SIN CANCELACIÓN DE ECO. Se nota en videollamadas con altavoz.
webkitgtk ya estaba decidido: la cabecera de evolution-data-server dice que
apagar GTK4 'también borra ENABLE_OAUTH2_WEBKITGTK, que arrastraba
WebKitGTK — el paquete más caro'. Y gnome-shell portal_helper=false
⇒ sin login OAuth2 en Evolution y sin ventana de portal cautivo.
shaderc ⇒ ESTA DISTRO NO TIENE VULKAN. gtk4 vulkan=disabled, libplacebo
shaderc=disabled vulkan=disabled. Todo va por GL/EGL sobre mesa.
⚠ Y un detalle que el barrido destapó: mutter lleva 'xwayland=false'. KDE tiene Xwayland desde
ayer (receta propia) pero GNOME no ⇒ las apps X11 correrán en KDE y no en GNOME. Asimetría entre
imágenes que nadie había mirado.
Con bluez (sin audio Bluetooth) y la ausencia de OpenGL de escritorio ya anotadas, son cinco
capacidades que la distro no tiene. Todas por decisiones de construcción anteriores, ninguna
por olvido de esta frontera — pero ahora están escritas en un solo sitio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
purpose 6.27.0 — el marco de «compartir» de KDE. 23 plugins (código de barras, bluetooth,
portapapeles, correo, imgur…) más sharefileitemaction.so, que añade «Compartir» al menú de
Dolphin.
Era el último hueco. La receta de okular lo degradaba con el flag oficial
FORCE_NOT_REQUIRED_DEPENDENCIES y su cabecera decía «los que no tenemos sellados» — un ESTADO,
no una preferencia. Ahora sale de esa lista.
⚠ Y va ENGANCHADO, no sólo autorado: una receta que nadie alcanza no arregla nada. okular sale
de la lista de degradados; gwenview y spectacle lo declaran (ninguna lo mencionaba: construían
sin él y el CMake se lo saltaba en silencio). Coste medido antes de tocar: las tres son hojas
con 0 dependientes transitivos ⇒ 3 builds, sin cascada.
La prueba es el enlace, no la intención: las tres referencian libKF6Purpose (2 cada una).
Un tropiezo con su lección: el CMake abortó pidiendo tres MÓDULOS QML —org.kde.prison,
org.kde.kitemmodels, org.kde.kcmutils— que no salen de ningún find_package. Los pide con
ecm_find_qmlmodule, o sea que comprueba que el IMPORT exista, no que la librería esté enlazada.
Las tres recetas ya existían y publicaban su qmldir; sólo faltaba pedirlas. Un requisito que no
se ve leyendo los find_package.
KAccounts6 NO se pide: su find_package va sin REQUIRED y arrastraría toda la torre de
kaccounts/signond que esta distro no tiene.
Verificado: REPRODUCE bit a bit · tarball en el mirror · escritorio-kde 304/304 sellado, deuda 0.
FRONTERA: 0 HUECOS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
update-desktop-database construye la caché MIME→APLICACIÓN (mimeinfo.cache). Comprobado antes
de escribir la receta: NINGÚN artefacto del store lo traía, así que esa caché no se construía
nunca. Con shared-mime-info (cerrado ayer) el escritorio sabe QUÉ TIPO es un fichero; sin ésta
seguía sin saber CON QUÉ abrirlo — el menú «abrir con» vacío y sin aplicación por defecto.
Misma familia que aquel hueco: falla en RUNTIME, no en build. gnome-shell, libadwaita y mutter
lo piden en nixpkgs y acá construyen tan contentos sin él, así que ninguna métrica de deuda
puede verlo.
Prueba del consumidor con 14 .desktop REALES de okular/dolphin/kate/konsole/spectacle:
mimeinfo.cache de 2.368 bytes · 47 asociaciones
application/pdf=okularApplication_pdf.desktop
text/plain=okularApplication_txt.desktop;org.kde.kate.desktop;org.kde.kwrite.desktop
Los 4 binarios estáticos con 0 NEEDED (--prefer-static desde el principio, no tras el error).
REPRODUCE bit a bit · tarball en el mirror.
Va como RAÍZ en los CUATRO escritorios y no en , y la razón importa: escritorio-kde y
escritorio-gnome comparten CERO raíces con base y bash NO está en su clausura. escritorio-mirada
documenta que no hereda «a propósito, es slim»; esos dos no dicen nada, y sway sí hereda cli.
Declararla en los cuatro funciona con cualquiera de las dos lecturas. La asimetría en sí queda
para decidir aparte: hoy «KDE 302/302» significa «la capa de escritorio está completa», no «la
imagen está completa».
Triaje: huecos 2 → 1 (queda ). Clausuras 303/182/160/205, todas selladas.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2