1# Plan — barrido de perfiles y servicios, MEDIDO
2
3**Fecha:** 2026-09-13 · **Catálogo contra el que se midió:** 905 nodos del corpus (903 recetas
4selladas + 2 ajenos) y los cuatro grafos de cola · **Instrumento:** el campo `perfiles` de
5`docs/state/build-state*.json` cruzado con `dependientes_total`, más el `[[service]]` de las
6recetas. No se contó nada a mano.
7
8## Por qué existe este documento
9
10Salió de una pregunta lateral. Al declarar doce recetas nuevas en `targets.toml` hubo que mirar qué
11había ya declarado, y el número no se parecía a lo que cualquiera habría dicho de memoria:
12
13> **De 905 nodos, 631 no estaban en NINGÚN perfil.** El 73 % del catálogo estaba sellado, íntegro,
14> reproducible… y no viajaba en ninguna imagen.
15
16Lo grave no es el número: es que **nada lo decía**. `drenaje.json` daba deuda 0 —y era cierto, no
17hay nada roto—. La métrica de perfil daba N/N —y también era cierta, porque mide la clausura de lo
18**declarado** y no puede ver lo que falta en la declaración. Es la lección de `foot` (un perfil
19121/121 sin emulador de terminal) aplicada al catálogo entero, y llevaba meses ahí.
20
21## El método, para poder repetirlo
22
23Tres preguntas encadenadas, y el orden importa porque cada una descarta ruido de la siguiente:
24
251. **¿Está en algún perfil?** → campo `perfiles` de los cinco `build-state*.json`. Hay que mirar los
26 cinco: una receta del corpus puede estar declarada desde una cola.
272. **¿Podría llegar por la clausura de otra?** → `dependientes_total > 0`. Una librería sin perfil
28 NO es un hueco: llega como dep de quien la usa. **El hueco son las HOJAS**: si nadie depende de
29 ellas y ningún perfil las declara, no hay camino por el que entren a una imagen.
303. **¿Fue curada alguna vez?** → la cabecera del importador. Las recetas de `import-nix` /
31 `import-alpine` traen literalmente «PUNTO DE PARTIDA, no final». Es una marca objetiva y parte
32 el montón en dos mitades con historias distintas.
33
34```
35905 nodos
36├── 274 en algún perfil
37└── 631 sin perfil
38 ├── 11 con dependientes → llegan por clausura ajena, NO son hueco
39 └── 620 HOJAS → sin camino a ninguna imagen
40 ├── 529 volcado crudo del importador (328 Go · 195 Rust · 6 C)
41 └── 91 trabajadas a mano ← éstas se triaron una por una
42```
43
44## La política que faltaba escribir: **catálogo ≠ imagen**
45
46La tentación al ver «620 hojas sin perfil» es declararlas todas. Sería peor que no haber mirado: 355
47de ellas son herramientas Go de nube y desarrollo importadas en tanda, y meter `kubectl`, `flyctl` y
48`chartmuseum` en la imagen de escritorio de alguien no arregla nada — engorda cuatro imágenes para
49servir a nadie.
50
51Lo que faltaba no era declarar: era **decir en voz alta que el catálogo tiene dos niveles**, cosa
52que el repo ya hacía de hecho y no había escrito en ningún lado.
53
54| nivel | qué es | cómo llega a una máquina |
55|---|---|---|
56| **imagen** | lo que un perfil declara, más su clausura | viene horneado; es lo que arranca |
57| **catálogo** | todo lo demás que el repo sabe construir y tiene sellado | `takana install <nombre>` desde el repo |
58
59⇒ **No estar en un perfil no es deuda.** Es deuda sólo cuando lo que falta es una **capacidad**: algo
60que la imagen, tal como está, no puede hacer. Ese es el criterio con el que se triaron las 91, y es
61el que hay que aplicar la próxima vez.
62
63## El triaje de las 91, completo
64
65Las seis clases suman 91 exactas; ninguna receta quedó sin clasificar.
66
67### A · Declaradas en este barrido (9)
68
69| receta | dónde | por qué |
70|---|---|---|
71| **`wpa_supplicant`** | `base` | **la imagen no podía asociarse a un WiFi** — ver abajo |
72| `wlr-randr` | `escritorio-sway` | no había con qué cambiar resolución ni rotar una pantalla |
73| `nano` | `cli` | `base` sólo trae `vim` |
74| `tig` | `cli` | git en TUI |
75| `patch` | `cli` | aplicar un diff a mano; userland clásico que faltaba |
76| `socat` | `cli` | sockets y diagnóstico de red |
77| `pigz` | `cli` | gzip en paralelo |
78| `miller` | `cli` | `jq` para CSV/TSV — el hueco de datos tabulares |
79| `dwarves` | `cli` | `pahole`: esta distro compila su propio kernel |
80
81### B · Del proyecto — entran por otro camino, no por perfil (19)
82
83`arje-zero`, `arje-absorb`, `arje-installer`, `arje-packager`, `arje-getty-stub`,
84`arje-net-bring-up`, `arje-zero-attest`, `hammerd`, `hammer-edit`, `agora-cli`, `dominium-cli`,
85`cosmos-cli`, `tinkuy-sim`, `llimphi-counter`, y los cinco kernels (`linux`, `linux-generic`,
86`linux-metal`, `linux-metal-dual`, `linux-metal-kexec`).
87
88El init y el kernel los hornea el `product-rootfs`; los binarios de la suite viven en tawasuyu. Que
89estén fuera de los perfiles es correcto — **pero conviene que esté escrito**, porque «`arje-zero` no
90está en ningún perfil» se lee como alarma hasta que alguien explica por qué no lo estará nunca.
91
92### C · Andamios y pruebas (6)
93
94`adwaita-hello`, `gtk4-hello`, `sourceview-hello`, `poppler-render-check`, `firefox-instrumentado`,
95`mesa-llvmpipe`. Existen para probar una cadena o para alimentar un PGO. No van a una imagen y no
96deberían aparecer nunca en este recuento como deuda.
97
98### D · Librerías sin consumidor todavía (10)
99
100`brotli`, `libarchive`, `libsodium`, `libssh2`, `libuv`, `lz4`, `zlib-ng`, `musl`, `bwrap`, `crun`.
101
102⚠ **Éstas son las únicas de las 620 que merecen una segunda mirada por sí mismas**, y no por falta de
103perfil: son librerías con **cero dependientes**. Alguien las escribió para algo y ese algo no llegó,
104o llegó y resolvió contra otra. Un `libuv` sellado que nadie usa es trabajo hecho y no cobrado —
105`libuv` es, por ejemplo, dep de `neovim`, que el catálogo no tiene.
106
107### E · Decisión abierta — NO se declaran de oficio (17)
108
109Cada una compite con algo ya elegido, o enciende una función que nadie pidió. Declararlas por
110reflejo sería tomar una decisión de distro dentro de un barrido de limpieza:
111
112| receta(s) | con qué compite / qué decide |
113|---|---|
114| `openrc` | **compite con `arje-zero`**: los inits no se apilan |
115| `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 |
116| `waterfox` | segundo navegador Gecko además de `atuq`. ¿Se shipean los dos? |
117| `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** |
118| `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 |
119| `steam-runtime-sniper` | es una imagen **qorpa**, no un paquete: entra por el ADR 0015 |
120| `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 |
121
122### F · Catálogo, no imagen (30)
123
124`amfora`, `anew`, `bom`, `cargo-audit`, `cargo-edit`, `cargo-hack`, `chartmuseum`, `cheat`, `dnsx`,
125`flyctl`, `fortio`, `git-absorb`, `glab`, `goldilocks`, `gomi`, `krew`, `kubectl`, `kubescape`,
126`melange`, `nancy`, `pint`, `rekor-cli`, `revive`, `sake`, `scorecard`, `temporal-cli`, `timetrace`,
127`topgrade`, `xsv`, `yamlfmt`.
128
129Se construyen, se sellan y se instalan por nombre. No engordan ninguna imagen. **Lo mismo vale para
130las 529 del volcado crudo**: es un catálogo, y un catálogo grande es una virtud siempre que nadie lo
131confunda con la imagen.
132
133⚠ Una de ellas merece una nota: **`topgrade` actualiza el sistema llamando a gestores de paquetes
134ajenos.** En una distro declarativa eso no es una comodidad, es un pie de cabra. Que esté en el
135catálogo está bien; que entre en una imagen, no.
136
137## Lo que el barrido destapó, que era el punto
138
139### 1. La imagen no podía conectarse a un WiFi
140
141`wpa_supplicant` estaba **sellado y en cero perfiles**. `dhcpcd` —que sí estaba en `base`— resuelve
142la IP de un cable; el handshake WPA2 de 4 vías lo hace un supplicant en userspace y **busybox no lo
143trae**. La receta llevaba meses escrita, con una cabecera que dice literalmente *«para asociar el
144WiFi del medio live al AP»*, y nadie la había declarado.
145
146Es el modo de fallo de esta casa en estado puro: **nada falla**. La imagen arranca, la métrica da
147N/N, `ip link` ve la interfaz — y no hay forma de asociarse. Sólo se ve usándolo, o cruzando dos
148ficheros que nadie cruzaba.
149
150⚠ **Y esto NO cierra la red inalámbrica, sólo la destraba.** `networkmanager` existe **sólo en
151`incoming-kde`**: GNOME, COSMIC y sway **no lo alcanzan** (cola hermana; una receta resuelve
152sibling-first y después el catálogo padre, nunca una cola de al lado). O sea que en tres de los
153cuatro escritorios la WiFi se configura a mano con `wpa_cli`, y el indicador de red del escritorio
154no tiene con qué hablar. Promover `networkmanager` al corpus es su propia unidad de trabajo.
155
156### 2. Tres de los cuatro escritorios no arrancaban NADA
157
158`paquetes` dice qué se **instala**; `servicios` dice qué se **levanta**. Son dos hechos distintos y
159sólo `escritorio-gnome` tenía el segundo. KDE, COSMIC y sway salían con `dbus`, `pipewire`, `upower`
160—y desde ayer `cupsd` y `bluetoothd`— **instalados y apagados**, con la métrica de clausura dando
161N/N igual.
162
163Las tres listas se escribieron computando qué ofrece cada clausura: se cruzaron los `[[service]]` de
164las **11 recetas que los declaran** (`accountsservice`, `arje-logind-compat`, `arje-polkit-compat`,
165`bluez`, `colord`, `cups`, `dbus`, `openssh`, `pipewire`, `upower`, `wireplumber`) contra el campo
166`perfiles` de los cinco grafos. **Contra la clausura y no contra las raíces**: `scripts/targets.py`
167imprime raíces, y la mitad de los demonios llegan como dependencia — mirar la lista equivocada dejaba
168fuera a `pipewire` y `colord` en GNOME, que sí están en la imagen.
169
170### 3. Y ese mismo cruce destapó tres ausencias de verdad
171
172Ninguna se arregla declarando servicios; quedan escritas en `targets.toml` donde toca:
173
174| perfil | ausencia | consecuencia |
175|---|---|---|
176| **KDE**, **sway** | no hay `wireplumber` | `pipewire` sin gestor de sesión **no encamina nada**: los nodos existen y nadie los conecta |
177| **COSMIC** | no hay `upowerd` | dibuja un indicador de batería que no tiene de dónde leerla |
178| **KDE**, **sway** | no hay `logind-compat` | hoy lo cubren `seatd` y el propio compositor, pero es una suplencia, no un diseño |
179
180## Cómo repetirlo
181
182```sh
183# 1. hojas sin perfil, por clase
184python3 - <<'PY'
185import json, collections
186d=json.load(open('docs/state/build-state.json')); n=d['nodes']
187hojas=[k for k,v in n.items() if not v.get('perfiles') and v.get('dependientes_total',0)==0]
188print(len(hojas), collections.Counter(n[k]['cls'] for k in hojas))
189PY
190
191# 2. partir crudas / trabajadas por la marca del importador
192grep -L 'PUNTO DE PARTIDA' recipes/*.toml
193
194# 3. servicios que ofrece la clausura de un perfil (NO las raíces)
195# cruzar los [[service]] de recipes/**.toml con el campo `perfiles` de build-state*.json
196```
197
198**La regla que sale de todo esto**, y es la que conviene recordar cuando el recuento vuelva a subir:
199
200> Una receta sellada y no declarada **no es deuda**: es catálogo. Se vuelve deuda el día que lo que
201> falta es una capacidad — y eso no lo dice ninguna métrica de build, porque las métricas miden lo
202> declarado. Hay que preguntarle a la imagen qué **no puede hacer**, no al grafo qué le falta.
plan-barrido-perfiles-y-servicios.md
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
perfiles de los cinco build-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-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 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.