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.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# 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 `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
|
||||
|
||||
```sh
|
||||
# 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.
|
||||
Reference in New Issue
Block a user