Deja escrito lo que el barrido midió y, sobre todo, CÓMO se midió, para que el recuento se pueda
repetir en vez de re-derivar.
Lo que aporta que no estaba en ningún lado:
1. **La política «catálogo ≠ imagen».** El repo ya la aplicaba de hecho y no la había escrito, y sin
ella «620 hojas sin perfil» se lee como deuda. No lo es: 529 son volcado crudo del importador y
355 son herramientas Go de nube. Una receta sellada y no declarada es CATÁLOGO — se instala por
nombre. Se vuelve deuda sólo cuando lo que falta es una CAPACIDAD.
2. **El método en tres preguntas encadenadas**, y por qué el orden importa: ¿está en algún perfil?
→ ¿podría llegar por clausura ajena (`dependientes_total>0`)? → ¿fue curada alguna vez (la
cabecera «PUNTO DE PARTIDA» del importador)? Las hojas son el hueco; las librerías no.
3. **El triaje completo de las 91 hojas trabajadas a mano**, en seis clases que suman 91 exactas.
Incluye las que NO se declaran de oficio y por qué: `openrc` compite con arje-zero, `uutils` y
coreutils/grep/findutils compiten con los applets de busybox, `waterfox` es un segundo Gecko,
y valkey/opensmtpd/step-ca/qdrant definen QUÉ CLASE de servidor es el perfil `servidor`.
4. **Dos hallazgos que no eran el objetivo del barrido:**
- `networkmanager` existe SÓLO en incoming-kde ⇒ GNOME, COSMIC y sway no lo alcanzan. Con
wpa_supplicant la WiFi se destraba, pero se configura a mano y el indicador de red del
escritorio no tiene con qué hablar.
- `ia-modelo-embeddings` no está en ningún perfil: hay motor (llama-cpp) y hay chat
(ia-modelo-chat) en los cuatro escritorios, y NO hay búsqueda por significado. La mitad
semántica del §6.3 de atuq no viaja.
5. **Una trampa del instrumento**, que casi hace escribir mal las listas de servicios:
`scripts/targets.py <perfil>` imprime las RAÍCES, no la clausura. Cruzar los [[service]] contra
esa salida dejaba fuera a pipewire y colord en GNOME, que sí están en la imagen. La clausura la
da el campo `perfiles` de los build-state*.json.
12 KiB
Plan — barrido de perfiles y servicios, MEDIDO
Fecha: 2026-09-13 · Catálogo contra el que se midió: 905 nodos del corpus (903 recetas
selladas + 2 ajenos) y los cuatro grafos de cola · Instrumento: el campo perfiles de
docs/state/build-state*.json cruzado con dependientes_total, más el [[service]] de las
recetas. No se contó nada a mano.
Por qué existe este documento
Salió de una pregunta lateral. Al declarar doce recetas nuevas en targets.toml hubo que mirar qué
había ya declarado, y el número no se parecía a lo que cualquiera habría dicho de memoria:
De 905 nodos, 631 no estaban en NINGÚN perfil. El 73 % del catálogo estaba sellado, íntegro, reproducible… y no viajaba en ninguna imagen.
Lo grave no es el número: es que nada lo decía. drenaje.json daba deuda 0 —y era cierto, no
hay nada roto—. La métrica de perfil daba N/N —y también era cierta, porque mide la clausura de lo
declarado y no puede ver lo que falta en la declaración. Es la lección de foot (un perfil
121/121 sin emulador de terminal) aplicada al catálogo entero, y llevaba meses ahí.
El método, para poder repetirlo
Tres preguntas encadenadas, y el orden importa porque cada una descarta ruido de la siguiente:
- ¿Está en algún perfil? → campo
perfilesde los cincobuild-state*.json. Hay que mirar los cinco: una receta del corpus puede estar declarada desde una cola. - ¿Podría llegar por la clausura de otra? →
dependientes_total > 0. Una librería sin perfil NO es un hueco: llega como dep de quien la usa. El hueco son las HOJAS: si nadie depende de ellas y ningún perfil las declara, no hay camino por el que entren a una imagen. - ¿Fue curada alguna vez? → la cabecera del importador. Las recetas de
import-nix/import-alpinetraen literalmente «PUNTO DE PARTIDA, no final». Es una marca objetiva y parte el montón en dos mitades con historias distintas.
905 nodos
├── 274 en algún perfil
└── 631 sin perfil
├── 11 con dependientes → llegan por clausura ajena, NO son hueco
└── 620 HOJAS → sin camino a ninguna imagen
├── 529 volcado crudo del importador (328 Go · 195 Rust · 6 C)
└── 91 trabajadas a mano ← éstas se triaron una por una
La política que faltaba escribir: catálogo ≠ imagen
La tentación al ver «620 hojas sin perfil» es declararlas todas. Sería peor que no haber mirado: 355
de ellas son herramientas Go de nube y desarrollo importadas en tanda, y meter kubectl, flyctl y
chartmuseum en la imagen de escritorio de alguien no arregla nada — engorda cuatro imágenes para
servir a nadie.
Lo que faltaba no era declarar: era decir en voz alta que el catálogo tiene dos niveles, cosa que el repo ya hacía de hecho y no había escrito en ningún lado.
| nivel | qué es | cómo llega a una máquina |
|---|---|---|
| imagen | lo que un perfil declara, más su clausura | viene horneado; es lo que arranca |
| catálogo | todo lo demás que el repo sabe construir y tiene sellado | takana install <nombre> desde el repo |
⇒ No estar en un perfil no es deuda. Es deuda sólo cuando lo que falta es una capacidad: algo que la imagen, tal como está, no puede hacer. Ese es el criterio con el que se triaron las 91, y es el que hay que aplicar la próxima vez.
El triaje de las 91, completo
Las seis clases suman 91 exactas; ninguna receta quedó sin clasificar.
A · Declaradas en este barrido (9)
| receta | dónde | por qué |
|---|---|---|
wpa_supplicant |
base |
la imagen no podía asociarse a un WiFi — ver abajo |
wlr-randr |
escritorio-sway |
no había con qué cambiar resolución ni rotar una pantalla |
nano |
cli |
base sólo trae vim |
tig |
cli |
git en TUI |
patch |
cli |
aplicar un diff a mano; userland clásico que faltaba |
socat |
cli |
sockets y diagnóstico de red |
pigz |
cli |
gzip en paralelo |
miller |
cli |
jq para CSV/TSV — el hueco de datos tabulares |
dwarves |
cli |
pahole: esta distro compila su propio kernel |
B · Del proyecto — entran por otro camino, no por perfil (19)
arje-zero, arje-absorb, arje-installer, arje-packager, arje-getty-stub,
arje-net-bring-up, arje-zero-attest, hammerd, hammer-edit, agora-cli, dominium-cli,
cosmos-cli, tinkuy-sim, llimphi-counter, y los cinco kernels (linux, linux-generic,
linux-metal, linux-metal-dual, linux-metal-kexec).
El init y el kernel los hornea el product-rootfs; los binarios de la suite viven en tawasuyu. Que
estén fuera de los perfiles es correcto — pero conviene que esté escrito, porque «arje-zero no
está en ningún perfil» se lee como alarma hasta que alguien explica por qué no lo estará nunca.
C · Andamios y pruebas (6)
adwaita-hello, gtk4-hello, sourceview-hello, poppler-render-check, firefox-instrumentado,
mesa-llvmpipe. Existen para probar una cadena o para alimentar un PGO. No van a una imagen y no
deberían aparecer nunca en este recuento como deuda.
D · Librerías sin consumidor todavía (10)
brotli, libarchive, libsodium, libssh2, libuv, lz4, zlib-ng, musl, bwrap, crun.
⚠ Éstas son las únicas de las 620 que merecen una segunda mirada por sí mismas, y no por falta de
perfil: son librerías con cero dependientes. Alguien las escribió para algo y ese algo no llegó,
o llegó y resolvió contra otra. Un libuv sellado que nadie usa es trabajo hecho y no cobrado —
libuv es, por ejemplo, dep de neovim, que el catálogo no tiene.
E · Decisión abierta — NO se declaran de oficio (17)
Cada una compite con algo ya elegido, o enciende una función que nadie pidió. Declararlas por reflejo sería tomar una decisión de distro dentro de un barrido de limpieza:
| receta(s) | con qué compite / qué decide |
|---|---|
openrc |
compite con arje-zero: los inits no se apilan |
uutils, coreutils, diffutils, findutils, findutils-xargs, grep |
compiten con los applets de busybox que hoy usan las imágenes. Reemplazarlos es una decisión de producto: cambia el tamaño, el comportamiento de las banderas y la superficie de licencias |
waterfox |
segundo navegador Gecko además de atuq. ¿Se shipean los dos? |
valkey, opensmtpd, seaweedfs, step-ca, qdrant |
el perfil servidor hoy sirve web y SSH. Añadir base de datos, MTA, almacenamiento o CA es definir qué clase de servidor es |
ia-modelo-embeddings |
la mitad semántica del §6.3 de atuq no viaja. llama-cpp y ia-modelo-chat sí están en los cuatro escritorios; el modelo de embeddings, no ⇒ hay motor y hay chat, y no hay búsqueda por significado |
steam-runtime-sniper |
es una imagen qorpa, no un paquete: entra por el ADR 0015 |
xorriso, mtools |
hacen medios de instalación. Si el instalador los necesita en la imagen, van; si los usa el hub, no. Hay que mirar el instalador antes de decidir |
F · Catálogo, no imagen (30)
amfora, anew, bom, cargo-audit, cargo-edit, cargo-hack, chartmuseum, cheat, dnsx,
flyctl, fortio, git-absorb, glab, goldilocks, gomi, krew, kubectl, kubescape,
melange, nancy, pint, rekor-cli, revive, sake, scorecard, temporal-cli, timetrace,
topgrade, xsv, yamlfmt.
Se construyen, se sellan y se instalan por nombre. No engordan ninguna imagen. Lo mismo vale para las 529 del volcado crudo: es un catálogo, y un catálogo grande es una virtud siempre que nadie lo confunda con la imagen.
⚠ Una de ellas merece una nota: topgrade actualiza el sistema llamando a gestores de paquetes
ajenos. En una distro declarativa eso no es una comodidad, es un pie de cabra. Que esté en el
catálogo está bien; que entre en una imagen, no.
Lo que el barrido destapó, que era el punto
1. La imagen no podía conectarse a un WiFi
wpa_supplicant estaba sellado y en cero perfiles. dhcpcd —que sí estaba en base— resuelve
la IP de un cable; el handshake WPA2 de 4 vías lo hace un supplicant en userspace y busybox no lo
trae. La receta llevaba meses escrita, con una cabecera que dice literalmente «para asociar el
WiFi del medio live al AP», y nadie la había declarado.
Es el modo de fallo de esta casa en estado puro: nada falla. La imagen arranca, la métrica da
N/N, ip link ve la interfaz — y no hay forma de asociarse. Sólo se ve usándolo, o cruzando dos
ficheros que nadie cruzaba.
⚠ Y esto NO cierra la red inalámbrica, sólo la destraba. networkmanager existe sólo en
incoming-kde: GNOME, COSMIC y sway no lo alcanzan (cola hermana; una receta resuelve
sibling-first y después el catálogo padre, nunca una cola de al lado). O sea que en tres de los
cuatro escritorios la WiFi se configura a mano con wpa_cli, y el indicador de red del escritorio
no tiene con qué hablar. Promover networkmanager al corpus es su propia unidad de trabajo.
2. Tres de los cuatro escritorios no arrancaban NADA
paquetes dice qué se instala; servicios dice qué se levanta. Son dos hechos distintos y
sólo escritorio-gnome tenía el segundo. KDE, COSMIC y sway salían con dbus, pipewire, upower
—y desde ayer cupsd y bluetoothd— instalados y apagados, con la métrica de clausura dando
N/N igual.
Las tres listas se escribieron computando qué ofrece cada clausura: se cruzaron los [[service]] de
las 11 recetas que los declaran (accountsservice, arje-logind-compat, arje-polkit-compat,
bluez, colord, cups, dbus, openssh, pipewire, upower, wireplumber) contra el campo
perfiles de los cinco grafos. Contra la clausura y no contra las raíces: scripts/targets.py
imprime raíces, y la mitad de los demonios llegan como dependencia — mirar la lista equivocada dejaba
fuera a pipewire y colord en GNOME, que sí están en la imagen.
3. Y ese mismo cruce destapó tres ausencias de verdad
Ninguna se arregla declarando servicios; quedan escritas en targets.toml donde toca:
| perfil | ausencia | consecuencia |
|---|---|---|
| KDE, sway | no hay wireplumber |
pipewire sin gestor de sesión no encamina nada: los nodos existen y nadie los conecta |
| COSMIC | no hay upowerd |
dibuja un indicador de batería que no tiene de dónde leerla |
| KDE, sway | no hay logind-compat |
hoy lo cubren seatd y el propio compositor, pero es una suplencia, no un diseño |
Cómo repetirlo
# 1. hojas sin perfil, por clase
python3 - <<'PY'
import json, collections
d=json.load(open('docs/state/build-state.json')); n=d['nodes']
hojas=[k for k,v in n.items() if not v.get('perfiles') and v.get('dependientes_total',0)==0]
print(len(hojas), collections.Counter(n[k]['cls'] for k in hojas))
PY
# 2. partir crudas / trabajadas por la marca del importador
grep -L 'PUNTO DE PARTIDA' recipes/*.toml
# 3. servicios que ofrece la clausura de un perfil (NO las raíces)
# cruzar los [[service]] de recipes/**.toml con el campo `perfiles` de build-state*.json
La regla que sale de todo esto, y es la que conviene recordar cuando el recuento vuelva a subir:
Una receta sellada y no declarada no es deuda: es catálogo. Se vuelve deuda el día que lo que falta es una capacidad — y eso no lo dice ninguna métrica de build, porque las métricas miden lo declarado. Hay que preguntarle a la imagen qué no puede hacer, no al grafo qué le falta.