Files
takana/docs/plan-barrido-perfiles-y-servicios.md
Sergio cbecd0ba12 docs: el barrido de perfiles y servicios, con su método y la política que faltaba escribir
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.
2026-09-13 01:08:17 +00:00

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:

  1. ¿Está en algún perfil? → campo perfiles de los cinco build-state*.json. Hay que mirar los cinco: una receta del corpus puede estar declarada desde una cola.
  2. ¿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.
  3. ¿Fue curada alguna vez? → la cabecera del importador. Las recetas de import-nix / import-alpine traen 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 bluetoothdinstalados 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.