tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

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.

cbecd0ba12bc9c42236a587fee621fcbbdaf7cd0 · por Sergio · (hace 1 mes) · padre 9ac4e1727 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

1 archivos · +202 −0

diff --git a/docs/plan-barrido-perfiles-y-servicios.md b/docs/plan-barrido-perfiles-y-servicios.md
new file mode 100644
index 00000000..6b36741b
--- /dev/null
+++ b/docs/plan-barrido-perfiles-y-servicios.md
@@ -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.