466972e3010bda77ec242cf40c53abc43c948992
86
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
466972e301 |
busybox: 79 → 69 applets sin otro dueño, y ninguno de los diez costó escribir userland
Cuatro artefactos sellados y el paso 2 del plan cerrado. Los diez applets que dejan de depender de busybox salieron de arreglar recetas, no de escribir código: xz unxz xzcat lzma unlzma lzcat → recipes/xz-tools.toml (nueva, b3:194c7d69…) vi → un symlink en recipes/vim.toml cpio → un symlink en recipes/libarchive.toml arch hostname → los trajo el feature de uutils (b3:614ff653…) xz-tools es el TERCER caso de la familia de zstd-cli, y eso ya estaba escrito en targets.toml: «la receta canónica construye sólo lib/ y su artefacto no tiene usr/bin; declararla no habría arreglado nada». Igual que musl-shared, zlib-shared y libffi-shared. Cuatro veces la misma forma — la receta publica la lib, la imagen declara el paquete, y el binario no está. Va como variante porque ampliar la canónica re-hashearía a sus 14 consumidores. ⚠ xz-tools selló DINÁMICO en la primera corrida pese al link = "static", y funcionaba: comprimía y descomprimía sin una queja. Es el relink de libtool, que libarchive.toml y bluez.toml ya documentan — hace falta LDFLAGS=-all-static en compile Y en install. Lo delató el `file` del binario, no una prueba que fallara. Paso 2 del plan: uutils, findutils, findutils-xargs, diffutils, gzip, grep y xz-tools declarados en el perfil `base` — seis recetas que llevaban meses `sealed` con `perfiles: []`. Hasta hoy, `find`, `xargs`, `diff`, `cmp`, `gzip`, `zcat` y `grep` en una imagen de takana eran el applet de busybox, no porque faltara escribirlos sino porque nadie los declaró. `grep` entra como PUENTE declarado: ripgrep ya viaja pero publica `rg` y no es grep POSIX. El censo gana modo --guardian, y vigila PÉRDIDAS, no un umbral: un umbral hay que subirlo cada vez que se retira un applet y a la tercera nadie lo sube con criterio; que un applet con proveedor medido deje de tenerlo es siempre una regresión. Probado en los dos sentidos — rc=0 contra /store, rc=1 contra un store mutilado, nombrando applet y proveedor perdido. Los cuatro sellaron con el mismo hash en el store tirable y en /store: dos work_root distintos, bytes idénticos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c7097e4a91 |
atuq §7.duodecies: el sembrador entra a las cuatro imágenes — y cifraba la seed de todos con una palabra pública
El §7.undecies dejó la bóveda declarada y a NADIE capaz de abrirla: ninguna imagen traía un binario que sembrara `pacha_llavero::SEED_IDENTIDAD`. Esta es esa unidad. De las dos formas posibles entra `agora-cli`, y el motivo no es que sea mejor: el wizard `churay-welcome-llimphi` SÍ tiene binario (medido: `src/main.rs` sin `[[bin]]`, o sea que cargo lo descubre), pero decide además backend de IA, dotfiles, fondo de pantalla y chasqui — la experiencia de primer arranque entera, que no se decide dentro de una unidad del navegador. ⚠ Y antes de poder declararlo apareció lo que lo volvía imposible: sin `AGORA_PASSPHRASE`, `Sesion::abrir()` caía en la frase de desarrollo "agora-dev" con un aviso por stderr y un ✓ en pantalla. La cadena que eso toca: frase → Argon2id → ChaCha20-Poly1305 que cifra la seed → la clave con la que `boveda` descifra su base. O sea, en una imagen de escritorio, la bóveda de todo el mundo cerrada con una palabra escrita en el fuente, y nada que falle. Arreglado en tawasuyu (`fd08dc03a`): variable > terminal (se pregunta, sin eco, y DOS veces en la génesis, donde un error de tipeo no se nota hasta que la seed ya no se recupera) > desarrollo sólo si no hay a quién preguntarle. La decisión vive en una función pura con cuatro tests, probada AL REVÉS: con el brazo `Preguntar` borrado falla con `left: Desarrollo / right: Preguntar`. Pin `9967b02c` → `da5fb8968` ⇒ `b3:46529e14`, 1,9 M, sellado en el worker con la guarda PEGADA al build. Mirado por dentro (regla 3) y probado como artefacto, con control negativo: `identity new` + `unlock` deja `user pacha:id:default: 32` en `/proc/keys`, y con la frase equivocada contesta «autenticación fallida» y NO re-siembra. El muro del `Cargo.lock` por cuarta vez, con la causa cambiada: esta vez no la puso quien tocó el lock sino otro agente que metió `shuma-taller` en un `Cargo.toml`. Cerrado en el worker, donde el registro está completo: +1 línea. Y el lock del árbol compartido traía otra vez el malo (índice y árbol con dos versiones distintas, las dos rotas), así que el commit se armó con `commit-tree` sin pasar por el índice. Corrección al §7.undecies: el verbo es `agora-cli unlock`, no `agora-cli identity unlock`. El guardián de coherencia pasa de SEIS lugares a SIETE, con su cuarto control negativo; los cuatro, en verde. Queda: la herencia del llavero de SESIÓN entre procesos hermanos (sin medir — y `/proc/keys` como root no la mide), y `pacha`/`pacha-secretos` en `perfil.servidor` con el mismo hueco. |
||
|
|
c33de60012 | gnome: 6 raíces — shared-mime-info (sellada y en ninguna imagen), la sesión barata y nautilus+gvfs; g-c-c fuera con su porqué | ||
|
|
e6ab5a5016 |
atuq: la bóveda estaba SELLADA y en ninguna imagen — y estar en la imagen no es poder abrirla
El §7.novies dio la función por cerrada: las seis etapas del guardián de metal en verde, con el
navegador de verdad y el diálogo a la vista. Lo que seguía abierto era la decisión 1 del §7.sexies
—«en qué imágenes se declaran»—, escrita como NO mientras ninguna app llimphi pudiera pintar. Ese
motivo se cayó el 2026-09-18, así que antes de tomarla se volvió a medir en vez de darla por sabida:
atuq sealed perfiles=[cosmic, gnome, kde, sway]
puriy-costura sealed perfiles=[cosmic, gnome, kde, sway]
boveda sealed perfiles=[]
shuma-pregunta sealed perfiles=[]
`sealed` con `perfiles: []` es sellado ≠ instalado: la lección de `foot`, que targets.toml repetía
QUINCE veces antes de hoy y que igual volvió a morder. Las dos entran a los cuatro perfiles de
escritorio, las dos o ninguna —sin el dueño `vault.match` no ofrece nada; sin el diálogo,
`Command::new` falla y TODO `vault.fill` se deniega—: media bóveda es una que niega todo en
silencio. ~43 M por imagen (22 M + 21 M medidos), contra los ~1,25 GiB que ya lleva el §6.7.
Y al declararlas apareció el hueco de una capa más arriba: la receta instalaba `/usr/bin/boveda` y
nada más, y los lanzadores de los cuatro escritorios leen `/usr/share/applications`. La app viajaría
en la imagen sin existir para quien la usa — la misma forma de fallo que esto viene persiguiendo.
Entra `boveda.desktop`, con tres cosas medidas antes de escribirlo:
· el icono existe: `dialog-password` está en breeze-icons (6), adwaita (1) y cosmic-icons (2). El
cuarto perfil lleva sólo hicolor, que no trae iconos: ahí cae al genérico, que es degradarse;
· lo acepta el `desktop-file-validate` del store, con `atuq.desktop` de control. Deja un hint sobre
`Security`, y las dos formas de callarlo lo cambian por uno PEOR (dos categorías principales ⇒ la
app aparece dos veces en el menú). Se queda como está;
· ⚠ y lo que NO puede hacer: emparejar la ventana con el lanzador. `llimphi_ui::run` no llama nunca
a `with_name` ⇒ winit no manda `set_app_id` y la ventana sale SIN app_id y con el título
"llimphi". Por eso no hay `StartupWMClass`. Vale para toda app llimphi; se arregla en llimphi.
La receta se reconstruyó en el worker con la guarda del §7.quinquies puesta (`### receta verificada
3f1072cc` antes de compilar nada, porque el latido revierte la receta cada media hora y un acierto
de caché sobre la vieja imprime SELLADA en cero segundos): `b3:b0c6adc4` ⇒ `b3:3f1072cc`, 22 M, con
el árbol mirado por dentro y la entrada dentro del artefacto.
Y el guardián de coherencia pasa de CINCO lugares a SEIS: el sexto es `targets.toml` —quién DECLARA
al dueño en la imagen—, con control positivo (`atuq` tiene que estar, o el chequeo está leyendo el
campo equivocado) y su propio control negativo, el tercero. Probado en los dos sentidos: cuatro
perfiles en verde, y `--negative-control-perfil` en rojo.
Abierto, y dicho como lo que es: quién levanta la app con la sesión (atado a la decisión 2 del
§7.sexies, la raíz de las claves), y que el único proveedor de GL de las cuatro imágenes es iris
—mesa-llvmpipe en ningún perfil—, que la bóveda hereda y no agrega.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ec469aef6b |
los nueve daemons propios declarados — y la línea entre «se muda» y «se INTERCAMBIA»
Nueve de las diez sellaron y están promovidas. Cinco corren en la caja (matilda, tupu, pacha, pacha-secretos, willay-daemon, más el par de shuma de ayer); cuatro quedan instaladas y FRENADAS a propósito, cada una con su guarda diciendo qué falta. ⚠ LA PAUTA DE LOS VHOSTS NO SIRVE PARA UNA IDENTIDAD. Con los sitios la regla fue «mudá el DNS y dejá el origen encendido, así volver cuesta un minuto» (§6.26). Con `tejido` es justo lo contrario: su README dice que la clave que el roster atesta ES la identidad de transporte libp2p (`~/.tejido/device.seed`), así que dos máquinas con la misma semilla no son dos réplicas — son el MISMO PeerId en dos sitios, dos impostores mutuos para la flota. `tejido` se INTERCAMBIA: se apaga allá, se enciende acá, en ese orden. Todo listo para el intercambio (binario, cuenta 964, identidad 700 instalada) y su Card en `cards.d` pero NO en el `genesis`, para que un reinicio no lo encienda. Y arrastra a `willay-crosscheck`, que no es independiente: corriendo su línea a mano dice «no hay roster en /root/.tejido/roster.postcard — emparejá primero». Vive dentro de la red de tejido. `thasnuna` tampoco puede: su INSTALAR.md —escrito hoy por el frente tawasuyu para esta mudanza— pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es glibc de ~300 M (jaula qorpa). De ahí sale un hallazgo que vale para el respaldo entero: la Card `openrc-openclaw` de gioser lleva la API key de su proveedor EN CLARO dentro del JSON de /etc/arje/cards.d/, que se respalda y se copia. Un secreto dentro de una Card viaja a todas partes. `--label` de willay-crosscheck NOMBRA A LA MÁQUINA: en gioser decía `momento`. Ahora sale de `hostname` y la guarda frena si no hay. Y tres veredictos FALSOS en una tarde, los tres por probar con lo que la caja no tiene: `/dev/tcp` (busybox ash no lo tiene) dio los 5 puertos de gioser «cerrados»; la expansión de llaves dijo que los artefactos no habían llegado; y `find -newermt "-2 minutes"` dijo que tupu no escribía. Tupu SÍ escribe: su fichero tiene tamaño CONSTANTE —es una serie fija— así que ni los bytes ni ese find prueban nada; lo que decide es el mtime, medido dos veces con 40 s de por medio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
11a9ead671 |
perfiles: metal-tigerlake es una CAPA DE HARDWARE componible, no otra lista por escritorio
El SDD 27 §2 dice que el repo mezcla tres capas —piso, escritorio, extras— en una sola
lista por escritorio, y que esa es la causa de que la composición no esté definida. El
firmware es un cuarto caso que el SDD no nombra y que sigue la misma lógica: no es un
escritorio ni un toolbox, es lo que hace falta para que un METAL CONCRETO arranque con todo
su hardware vivo.
Por eso va como perfil propio y se compone, en vez de duplicarse en los cuatro escritorios:
scripts/targets.py cli metal-tigerlake escritorio-kde
Otro metal = otro perfil de estos con su propia receta firmware-<plataforma> derivada del
mismo linux-firmware. Ese es el punto de haber partido el firmware en base + derivada.
La máquina, medida con lspci/cpuinfo sobre el metal real:
CPU i7-11370H · family 6 · model 140 (0x8c) · stepping 1 ⇒ intel-ucode/06-8c-01
GPU TigerLake-LP GT2 [Iris Xe] [8086:9a49] ⇒ i915 / xe
WiFi Intel Wi-Fi 6 AX201 [8086:a0f0] ⇒ iwlwifi
Audio 500 Series HD Audio [8086:a0c8] ⇒ snd_sof_pci_intel_tgl
Y zsh entra en `cli`, no en `base`: base define el shell del SISTEMA (bash) y esto es el
del usuario. Dos hechos distintos, como `paquetes` y `servicios` un piso más abajo.
El runbook deja el encargo para el worker, y su parte útil son los dos muros que aparecieron
al cruzar ficheros que nadie había leído juntos:
1. takana-live-install.sh:275 bifurca «UEFI ⇒ rama EFI-stub soberana», y metal-usb-sdboot.sh
dice que el EFI-stub «en el firmware del usuario se cuelga tras Measured initrd PCR 9».
ES EL MISMO FIRMWARE. El lazo de instalación EFI está validado en OVMF, que no tiene ese
quirk ⇒ el instalador va a pasar la prueba y colgarse en el metal de destino. La salida
es llevar el instalador a systemd-boot, que es el camino ya probado en esta máquina.
2. El instalador reparticiona en MBR el disco entero, y ese disco tiene /home con 893 G
usados. Si la instalación conserva /home o se lleva el disco con respaldo previo es
decisión del usuario, no del runbook — y hasta que se decida, no correrlo sobre el NVMe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
f32b28c63e |
CUTOVER de sergio.gioser.net: el último vhost sale de gioser — ya no queda ninguno
`https://sergio.gioser.net/` y `/shuma/` contestan 200 contra 2.29.29.217, con TLS público. **Ningún dominio resuelve ya a gioser.** ⚠ Y el 200 de `/shuma/` había que mirarlo dos veces: el bloque termina en `try_files … /index.html`, así que CUALQUIER ruta inexistente devuelve 200 con la SPA — un `curl -o /dev/null` habría dado el mismo verde con el proxy mal puesto. Lo que decide es el cuerpo (17 bytes: «shuma-gateway ok») y el control es compararlo con lo que el origen viejo sigue sirviendo. Igual con `/shuma/rpc`: las dos máquinas contestan el mismo 400 con el mismo JSON. Lo construido, en orden: las dos recetas sellaron en el worker con los hashes anticipados y salen `statically linked` (nada de cargador, que era lo que le faltaba al binario glibc de gioser) · la cuenta `shuma` declarada con `[[user]]`, uid 967, y el descenso con `setuidgid` en el argv de la Card porque el payload Native de arje NO tiene campo de usuario · la config que no se regenera (`gateway-token`, `identity.x25519`) instalada, y la guarda de la Card la EXIGE en vez de crearla: un token nuevo deja fuera a los clientes ya emparejados · las dos Cards en `cards.d` Y en el genesis de la semilla · las dos recetas y los dos labels declarados en `perfil.servidor`. ⚠ LA SHELL DE LA CUENTA NO ES UN DETALLE: el default de `[[user]]` es `/bin/false` —correcto para gitea o squid, exactamente lo contrario para un demonio que abre PTYs—. Con la shell inerte el servicio arranca, se supervisa, contesta 200 y cada pestaña muere al instante: un fallo que no se ve al desplegar, se ve al usarlo. Va `shell = "/bin/sh"`. `[[user]]` y `[[service]]` están fuera de `hash_inputs`: declarar todo eso no movió ningún ArtifactHash, comprobado antes y después en los dos. Y una corrección al §6.25: para cambiar el VALOR de un rrset no hace falta DELETE+POST (que deja una ventana sin registro). La API tiene `POST /v1/zones/<id>/rrsets/<n>/<t>/actions/set_records`, que es atómica. Con TTL 60 la resolución pública cambió en menos de 8 segundos. El origen de gioser sigue corriendo a propósito, como en §6.26: volver atrás es una llamada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e903f486d9 |
el enlazador va DENTRO del compilador — /etc/cargo/config.toml es una ruta que cargo no lee
La prueba de la caja recién instalada —`cargo build` en un proyecto nuevo, sin flags ni rutas a mano— falló con `linker `cc` not found`, y destapó que la pieza que había empaquetado era falsa: **cargo NO LEE `/etc/cargo/config.toml`**. Sólo lee `$CARGO_HOME/config.toml`, los `.cargo/config.toml` del proyecto y sus padres, y lo que se le pase con `--config` — que es lo que yo venía pasando a mano en cada prueba, y por eso «funcionaba». Y el arreglo no era mover el fichero: **el que enlaza es `rustc`, y rustc no lee la config de cargo**. Las flags van DENTRO del compilador, en el spec del triple: · `linker = "ld.lld"` + `linker_flavor = Gnu(Cc::No, Lld::Yes)` — sin esto rustc busca `cc`, que una caja takana no tiene. · `crt_static_default = true` — sin esto enlaza dinámico y pide `-lgcc`, que no existe (la distro se construye con zig, que trae compiler-rt). · `crt_static_allows_dylibs = true` — su pareja obligatoria: sin ella `crt-static` apaga los dylibs y con ellos los PROC-MACROS (serde_derive, clap_derive, las 44 recetas `cargo-*` con `derive`). El compilador en sí sigue dinámico: el `bootstrap.toml` fija `crt-static = false` para el build, y eso gana sobre el default del spec. ⇒ `cargo-config` (receta, árbol y copia en scripts/) se van enteros: un paquete que instala un fichero que nadie abre es peor que no tenerlo. Y de paso, `lld21` instala los cuatro alias como SYMLINKS y no como copias: cmake los duplicaba, 5 × 96 M = 478 M de artefacto para 97 M de enlazador. Ahora 108 M, y `ld.lld` sigue despachando por `argv[0]`, que es como upstream lo diseñó. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0a894cfac1 |
perfil.servidor: fuera el prebuilt ajeno, entra el toolchain propio
Sale `rust-toolchain-bin` —799 M de bytes AJENOS sellados tal cual (`foreign`)—, que era el escalón 1 del SDD 31 y cumplió su función: con él se construyó el escalón 2, y el escalón 2 ya compila, enlaza y corre. Entran TRES, que son el mismo hecho («esta caja compila Rust») partido en piezas: · `rust` (353 M) — el compilador y cargo, de la granja, desde fuente. · `lld21` — el enlazador. NO es opcional: una caja takana no tiene `cc`, y sin enlazador `rustc` compila objetos y no produce un ejecutable. · `cargo-config` — receta nueva que publica `/etc/cargo/config.toml` con las tres flags que unen a los dos. Sin ellas `cargo build` muere con «linker `cc` not found»: el toolchain estaría completo y MUDO, que es [[subcomando-sin-driver]] otra vez. Es config de SISTEMA y no de usuario a propósito — un `~/.cargo/config.toml` funcionaría para quien lo escribió y no para el siguiente. ⚠ Y `rust` se re-hashea otra vez (702094a8 → fe277b32) por una línea que descubrió el control de PATH: **x.py instala en `/usr/local` por defecto, y el PATH de una caja takana es `/bin:/usr/bin:/sbin:/usr/sbin`** — medido en la caja. O sea que el perfil habría proyectado un `rustc` perfecto que nadie puede invocar: «existe ≠ se encuentra». Ahora `[install] prefix = "/usr"`, como el resto del corpus. Control previo que sí pasó: `rustc` y `cargo` corren SIN `LD_LIBRARY_PATH` — su `RUNPATH` es `$ORIGIN/../lib`, así que la proyección del artefacto basta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0a7afebe9c |
la caja tiene HORA, LATIDO y ROTACIÓN — y el respaldo del gitea dejó de ser a mano
`planear.py --revisar` sobre el censo ordena lo que queda, y lo primero no eran los servicios del usuario sino TRES CAPACIDADES que ninguna metrica reclama: el disparador periodico, la hora y la rotacion de logs. Los tres paquetes ya estaban sellados y declarados en `perfil.servidor` desde que ese perfil nacio — lo que faltaba era el eslabon que los ARRANCA, igual que con squid. · `chronyd`: la caja iba **15 s atrasada** y nadie la corregia. Ahora stratum 3 contra los NTP de Hetzner, 0,000005 s de NTP. Sin hora no hay TLS ni firmas, y el sintoma no se parece a la causa. · `crond`: no existia el latido. Verificado con una entrada `* * * * *` en el crontab REAL. · `logrotate`: `/var/log/squid` sin rotar. Diario, con `squid -k rotate` (squid mantiene los ficheros abiertos: un rename a secas lo deja escribiendo en un inode que nadie puede leer). · `scripts/respaldo-gitea.sh` + `17 3 * * *`: cierra el hueco del §6.15 — los 44 repos vivian en UNA copia y el respaldo se hacia a mano. Sube 23+21 repos (1,5 G) y un `gitea.db` de 332 M tomado con `.backup`, consistente con el servidor vivo. El control no es que el script salga 0: es CONTAR los repos de los dos lados. `chrony` y `cronie` NO declaraban `[[service]]` — el paquete llegaba a la imagen y no lo arrancaba nadie. Ahora lo declaran (fuera de `hash_inputs`: los hashes no se movieron) y el perfil los habilita. TRES MEDICIONES que valen mas que el resultado: 1. **La deriva era de gioser, no de la caja**: despues de sincronizar, el HUB quedo 3 s adelantado. Medir «contra el otro» sin un tercero no dice quien esta mal. 2. **El spool de cronie es `/var/spool/cron/<usuario>`**, no `…/crontabs/<usuario>`: `crontab -l` mostraba las entradas y `crontabs/` estaba vacio. Un crontab restaurado en el sitio equivocado es un fichero perfecto que nadie lee. 3. 🧨 **`sqlite3` seguia INERTE en la caja** (`compressBound: symbol not found`): el ultimo de los 23 del §6.4. Le faltaba `zlib-shared`, sellado y declarado en `perfil.base` desde el 11-09 — pero la caja se instalo antes. Sin el no habia snapshot consistente. Un paquete declarado no es un paquete instalado: en una caja viva vale lo que el `upgrade` proyecto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a394dda26a |
receta: squid 7.7 entra al corpus — y el --export-dynamic que desactivaba el estático
`b3:54070b26…`, 8,8 M, **estático de verdad** (binario y los tres helpers), y probado con la CONFIG REAL de produccion: parsea sin FATAL, tunela `CONNECT claude.ai` y `CONNECT api.anthropic.com` con TCP_TUNNEL/200, y su `basic_ncsa_auth` lee el `passwd` de los tres usuarios (control negativo: con una clave mala contesta `ERR Wrong password`). Es la respuesta definitiva a lo de ayer: el proxy se levanto primero en una jaula qorpa porque estaba EN USO y eso lo destrabo en minutos, pero squid es C++ y el lab lo construye. La diferencia con php-fpm importa: PHP no queremos que entre al corpus, squid si. ⚠ LA FUENTE NO SALE DE squid-cache.org: esa URL devuelve **200 con una pagina HTML de 8985 bytes**, no el tarball. Pinear su sha256 habria anclado la receta a una pagina de error. Va el release de GitHub, que ademas trae `configure` ya generado. 🧨 EL MURO, y es del lab: el primer sellado salio con `usr/sbin/squid` **dinamico y `NEEDED libc.so`** mientras sus helpers salian estaticos — o sea inerte en cualquier imagen sin cargador ([[needed-colgante-libstdcxx]]) pese a `link = "static"`. La cadena: `squid_LDFLAGS` trae `-export-dynamic` (para modulos eCAP, que estan apagados) ⇒ **el wrapper del lab quita `-static` de cualquier enlace que mencione `--export-dynamic`**, a proposito, para poder enlazar las `.so` dlopen-ables de Python y companhia. Y no alcanza con sacarlo de la variable: **libtool lo vuelve a anhadir solo** en cuanto hay un `-dlopen`. Se vacia `export_dynamic_flag_spec` en el `libtool` GENERADO (local al build, sin tocar la fuente) y con guarda: si el campo no aparece, aborta — un `sed` que no acierta deja pasar el problema y el binario sale dinamico sin que nada falle. ⚠⚠ Y `-dlopen force` NO sobra, aunque lo parezca: vaciar `squid_LDFLAGS` entero rompe el enlace con `undefined symbol: lt__PROGRAM__LTX_preloaded_symbols`, que emite el propio libtool **porque hay un `-dlopen`**. De los dos flags, el que estorba es uno solo. Distinguirlos costo un build; adivinar cuesta lo mismo y no ensenha nada. Declarado en `perfil.servidor` como paquete Y en `servicios`, con su `[[user]] proxy` y su `[[service]]` (SDD 30) — que comprueba la config del sitio y la cuenta, y sale 78 nombrando cual falta en vez de dejar un bucle de reinicios. El hash NO cambio al declararlos: `[[user]]` y `[[service]]` estan fuera de `hash_inputs`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
dbfb7d492b |
la jaula no viajaba en NINGUNA imagen: bwrap y harkaq-exec entran a perfil.base
Mudando `api.sergio.gioser.net` a la caja de produccion, `takana qorpa provision` aborto:
Error: no encuentro harkaq-exec en /root/.cache/harkaq/harkaq-exec
construilo: gcc -O1 -Wall -static -o ... scripts/harkaq/harkaq-exec.c
El mensaje es bueno y la receta que propone es IMPOSIBLE de seguir: en una caja instalada no hay
gcc, ni `scripts/`, ni arbol de desarrollo. El binario solo existia como un `gcc` a mano en la
cache de `$HOME` del hub, o sea que la jaula del ADR 0015 funcionaba unicamente en la maquina
donde alguien la habia compilado.
Y su companero estaba igual, medido en `build-state.json`: `bwrap` sellado desde hace meses con
`"perfiles": []` — CERO perfiles. Lo invocan por PATH tanto `qorpa` como el sandbox de
`takana build` (`takana-build/src/sandbox.rs`), asi que **ninguna imagen de takana podia enjaular
nada, ni construir**. Es [[subcomando-sin-driver]] un piso mas abajo: el CLI que los llama viaja
en todas las imagenes y sus herramientas en ninguna.
Tres piezas:
· `recipes/harkaq-exec.toml` — nueva. Estatico musl, `b3:cd34954f...`, 269 K. El pin va al commit
que toco la FUENTE (`1d9ddcee`, 2026-09-03) y no a HEAD: el `.c` no se mueve desde entonces y el
repo commitea cada media hora por el cron de la cosecha — pinear HEAD re-hashearia la receta cada
media hora sin que su fuente cambiara. La fuente sigue en `scripts/harkaq/` y no en un arbol
propio porque tres scripts la compilan desde ahi y dos copias divergen en silencio.
· `qorpa.rs` — busca el binario tambien en `/usr/bin`, que es de donde sale en cualquier maquina
que no sea el hub. El orden es HARKAQ_BIN (lo que el operador declara) → arbol de desarrollo →
paquete, para que un cambio en la jaula se pruebe sin instalar nada. Y el error ya nombra las
dos salidas, no solo la del hub.
· `targets.toml` — los dos en `perfil.base`.
Medido en la caja de produccion tras aplicarlos: `bwrap 0.11.0` + harkaq-exec responden, y
`takana qorpa provision sergioh-api` instala python 3.14.7 con pacman DENTRO de la jaula
(`[harkaq] jaula puesta: ABI 9`). El 3.14 no es casualidad: el venv de gioser trae extensiones
`cpython-314-...-gnu.so`, asi que la imagen de Arch pineada da la misma serie.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
41ab76c8a0 |
✅ RUST COMPILA EN LA CAJA — paso 1 cerrado, y tres paquetes que estaban en el perfil equivocado
`rustc 1.97.0` y `cargo 1.97.0` responden en `2.29.29.217`, y un `fn main(){println!("ok");}`
compila y **corre**. El toolchain oficial de rust-lang, verificado por su sha256 y sellado tal cual
con `foreign = true` (son bytes ajenos, no un build nuestro: clase `ajeno`, fuera del recuento del
corpus).
Lo que costó hacerlo correr, y ninguna de las dos cosas se ve en el grafo porque las dos estaban
`sealed` y en el perfil equivocado:
1. **El cargador musl.** `rustc` es PIE dinámico (`NEEDED librustc_driver-*.so`, `NEEDED libc.so`).
En el worker ni arranca —`cannot execute: required file not found`, que es como se ve la falta
del intérprete—; en la caja sí, porque ahí está `musl-shared`.
2. **`libgcc_s.so.1`**: `Error loading shared library … _Unwind_Resume: symbol not found`. Lo
publica `gcc-libs`, sellado y declarado **sólo en los cuatro perfiles de escritorio**. Una línea
en `base`. Es la tercera vez HOY que aparece la misma figura —`tar`, `os-release`, `gcc-libs`—:
el paquete existe, está sellado, y no viaja en la imagen que lo necesita.
Y una propiedad que vale anotar: **no hace falta un `cc`**. El primer `rustc` falla con
`linker \`cc\` not found` y no hay que traer un compilador de C — el toolchain **trae su propio
lld** (`rustlib/<target>/bin/rust-lld`), y con `-C linker-flavor=ld.lld -C link-self-contained=yes`
enlaza y el binario corre. Una distro sin compilador de C puede compilar Rust igual.
Va en `perfil.servidor` y no en `base`: son 799 M y una imagen de escritorio no compila nada.
Pendiente, que es config del SITIO y no de la receta: que esas flags sean el default
(`/etc/cargo/config.toml`), para que `cargo build` funcione sin recordarlas.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
cd78bb98c9 |
el logo OFICIAL de takana, extraído del PNG — y rustc entra al corpus como foreign
EL LOGO. El usuario pasó el PNG oficial. El ASCII no se inventa: se MIDE. Extraído celda por celda
del original — bbox 348×348 en un lienzo de 512, cuadrícula **7×7**, celda de 49,7 px — con cuatro
colores exactos y ninguno más:
#a63a25 ladrillo · #df6b2a naranja · #e3a63d dorado · #fff6de crema
La primera versión que puse era un martillo dibujado a ojo: era *un* martillo, no *el* logo. Cada
celda son DOS caracteres de bloque porque un carácter de terminal es alto y angosto, y `██` es lo que
conserva la geometría cuadrada del original; reconstruirlo a ojo la pierde y deja de ser el logo.
Van los dos ficheros: `logo.png` (el oficial, tal cual) y `logo.txt` (el mismo en ANSI de color
verdadero), más la config global de fastfetch con `file-raw` — con `file` los escapes se imprimirían
como texto. Y `os-release` se mueve de `cli` a **`base`**: toda imagen de takana debe saber decir qué
es, incluida la más pelada. «No sólo para esta máquina, para siempre en takana».
De paso, dos cosas que sólo se ven construyendo: `cp -a` aborta en el sandbox («failed to preserve
ownership»), va `cp -r`; y un config de fastfetch que sólo define `logo` dibuja el logo y NI UN DATO
— `modules` hay que listarlo aunque parezca redundante.
RUSTC, PASO 1. `rust-toolchain-bin` 1.97.0, el tarball oficial de rust-lang verificado por su sha256
y sellado tal cual, con `foreign = true` porque son bytes ajenos y no un build nuestro (clase
`ajeno`: no infla el recuento del corpus, ADR 0015). Target **musl**, no gnu: un toolchain gnu
traería el cargador de glibc y sería needed-colgante otra vez.
Por qué importa, y no es gusto: TODO el instrumental de takana es Rust —12 crates, 42 555 líneas— y
el `rustc` que los construye sale del LAB, un rootfs ajeno que ENTRA en el ArtifactHash. La distro
depende de bytes de otro para reconstruirse a sí misma. Y el corpus tenía **44 recetas `cargo-*` y
CERO de rustc**: subcomando-sin-driver a escala de toolchain.
Es el paso 1 de tres, y los otros dos no son opcionales: (2) rustc desde fuente con el `llvm18` que
YA está sellado (379 M), y (3) la cadena mrustc→1.91.1 que ya existe documentada en el selfhost pero
vive en el bootstrap, no en el corpus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
0d0ab79092 |
el tar de busybox no sirve para un rootfs ajeno — y takana ya no dibuja un pingüino
`qorpa pull` de un `.tar.zst` moría imprimiendo **la ayuda de busybox** y un exit 1: ni una palabra sobre `--zstd`, que es la opción que no entiende. El código ya elegía el descompresor por MAGIC y pasaba las flags correctas; lo que faltaba era un `tar` de verdad. Dos arreglos, y el segundo es de una línea: · `untar` comprueba `tar --version` y, si no es GNU, ABORTA nombrando la causa y el arreglo. Diez minutos de diagnóstico se vuelven una línea. · **`tar` (GNU) entra en `perfil.base`.** Estaba sellado desde hace meses y declarado en UN solo perfil (`escritorio-kde`). Es la lección de `foot` otra vez: el paquete existe y no viaja en la imagen que lo necesita. Ya había costado dos veces — la caja tampoco podía desempacar su propio LAB por lo mismo. Y el logo: fastfetch dibujaba **el pingüino genérico de Linux** porque elige por el `ID` de os-release y no conoce `takana`. Ahora la receta `os-release` publica también `/usr/share/takana/logo.txt` (un martillo, que es lo que significa el nombre en quechua) y `/etc/xdg/fastfetch/config.jsonc` — la config GLOBAL por XDG, no la de un usuario. Van en la receta de la IDENTIDAD y no en la de fastfetch a propósito: mañana el que dibuje puede ser otro programa y el logo seguirá siendo el mismo fichero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
93362fc21b |
el TLS arreglado en la caja, la distro con nombre, y el ENOSPC que no era de la raíz
**curl ya valida**: `ca-certificates` re-sellado con **119 hash-links en el capath**, generados en el build, y la caja baja por HTTPS sin apaño (200). Tardó tres intentos y los dos primeros los cazó la guarda de la propia receta: `c_rehash` no estaba en el PATH (lo instala esa misma receta en `/out/usr/bin`) y los certificados no están sueltos en `usr/share/ca-certificates/` sino en `mozilla/`, así que sólo veía el bundle y lo saltaba —correctamente— con «does not contain exactly one certificate». Sin la guarda habría sellado un capath vacío las tres veces. **fastfetch 2.68.1 corre en la caja** (pedido del usuario) y al correrlo destapó que **`/etc/os-release` no existía**: la distro era anónima para cualquier programa que no fuera suyo. Ahora `OS: takana x86_64`. Va como receta propia (`source.dir`) y no como constante de `takana-bootstrap`: meterlo ahí re-sellaría el product-rootfs —el baseline del selfhost— por cinco líneas. Sin VERSION_ID ni fecha: un sello con la fecha del build cambiaría el hash cada día sin motivo, y con él la clausura de toda imagen que lo lleve. 🧨 Y el hallazgo estructural: `upgrade apply` falló dos veces con **No space left on device teniendo 3 G libres en la raíz**. No era `/` ni los inodos (9%): el estado de generaciones vive en **`/var/lib/hammer`, que es sda3 y mide 487 MB** — la partición «estado» del layout. Cada generación guarda una copia del árbol aplicado (272 MB el nuestro), así que dos no caben. Movido a `/work` por symlink, igual que qorpa, y la generación 6 entró. El layout de la imagen necesita revisión: 512 MB no alcanzan para un mecanismo que guarda un árbol por generación, y el síntoma apunta al sitio equivocado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
64d48e743e |
minga abierto a P2P (la ventana oficial), fastfetch al corpus, y el rehash por su ruta
**minga con su daemon escuchando.** Decisión del usuario: minga es la ventana oficial al mundo y git queda como espejo de compatibilidad, así que el daemon no es opcional — es el punto de entrada. El binario ya está sellado (`b3:121cf4a8…`, 22 M, static-pie) y ahora trae su `[[user]]` (uid 970, home `/work/minga`: en la raíz no cabe y `/work` sobrevive a un re-`dd`) y su `[[service]]`: `listen /ip4/0.0.0.0/tcp/4001` — la convención libp2p, libre en la caja (ocupados: 22022 admin, 2345 git-SSH, 3002 gitea en loopback, 80/443 caddy). Declarar ambos **no movió el ArtifactHash**. ⚠⚠ **La identidad no se sella ni se inventa.** El keypair lo genera el usuario con `minga init`; tawasuyu avisó de que el suyo era provisorio. Si la card hiciera un `init` automático, la caja se inventaría su identidad soberana en el primer arranque y todo lo que se firme después colgaría de ella. La guarda comprueba que el repo exista y sale 78: un peer sin identidad decidida es peor que un peer ausente. **fastfetch** (2.68.1) entra al corpus y a `perfil.cli` — lo heredan servidor y los escritorios. Receta CMake con `-DCMAKE_LINK_DEPENDS_USE_LINKER=OFF` (sin eso el lld de zig segfaultea y el error no nombra a zig) y con la detección opcional APAGADA explícitamente: con `link = "static"` no hay `dlopen`, y una detección que entra «porque la librería estaba en el lab ese día» produce artefactos distintos bajo el mismo nombre. Y `ca-certificates`: el rehash se llamaba por nombre y **`c_rehash` lo instala esta misma receta en `/out/usr/bin`**, no está en el PATH del sandbox. Se invoca por su ruta. La guarda que añadí ayer hizo su trabajo: el build falló ruidosamente en vez de sellar otra vez un capath sin índice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
69526768b7 |
CUTOVER: el gitea sirve desde la caja nueva — TLS válido, HTTPS y SSH, supervisado por arje
`git.gioser.net` y `git.tawasuyu.net` responden **200 con TLS válido desde 2.29.29.217**, `git clone` funciona por HTTPS **y** por SSH:2345, y `gitea` + `caddy` corren supervisados por `arje-zero`. El gitea de gioser está parado. Es el primer servicio real que deja la máquina que se va a borrar. Mudanza INCREMENTAL: los DNS de gioser son casi todos `CNAME → www`, así que mover `www` habría mudado quince dominios cuyos backends siguen allá. Se convirtió sólo `git` (las dos zonas) de CNAME a A propio con TTL 60 — reversible en un minuto. Cinco cosas que sólo se aprenden haciéndolo: · **Parar el origen no es `rc-service gitea stop`**: dice «already stopped» con el proceso vivo, y matarlo no alcanza — lo revive arje-zero, que lo tiene como card `openrc-gitea` con Restart y **9001 reinicios** en el contador. Se paró con `arjectl stop openrc-gitea` (el arjectl que construimos hoy, hablando con el arje de gioser), y para eso hubo que extraer su card del genesis y escribirla en `cards.d` — que es justo el hueco del §6.12. · **El token de `hcloud` también gestiona el DNS** (Cloud API unificada, `/v1/zones`); la API vieja `dns.hetzner.com/api/v1` redirige a la consola web. Y un `PUT` sobre el rrset no puede cambiar el TIPO: hay que DELETE del CNAME y POST del A. · **ACME falló primero contra gioser** (502) porque el challenge salió antes de que propagara el DNS. Con el DNS al día, `arjectl restart caddy` → certificate obtained successfully. · **La identidad SSH se muda con el servicio**: `REMOTE HOST IDENTIFICATION HAS CHANGED` hasta que se copiaron las claves de host de gioser a la caja. Así los clones existentes no notan nada; el precio es limpiar el known_hosts propio del `:22` de administración. · El `sshd` del producto escucha sólo en `:22` ⇒ el git por SSH necesitó `Port 2345` y que el usuario `gitea` tenga shell real (su authorized_keys fuerza `command="gitea serv …"`). `caddy` gana su `[[service]]` (con guarda del Caddyfile, HOME propio para los certificados de ACME —o cada reinicio pediría certificados nuevos y se comería el límite de emisión— y la nota de por qué corre como root) y el perfil lo arranca. Consecuencia para la granja: las 23 recetas que clonan por `https://git.tawasuyu.net/…` **ya apuntan a la caja**. Comprobado: el worker resuelve 2.29.29.217 y `git ls-remote` responde. Revertir, si hiciera falta: `arjectl start openrc-gitea` en gioser y los dos `git` de vuelta a CNAME. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
fd09ca77b0 |
arjectl en la imagen: relanzar en caliente — y el genesis NO alcanzaba para eso
El servicio que falla y agota su backoff sólo se podía recuperar reiniciando la máquina entera.
Ya no. Y no hubo que escribir nada: **el cliente existía en tawasuyu y el crate se llama `arje-ctl`**
(el binario, `arjectl`). Buscarlo por `arjectl` no lo encontraba, y de ahí salió mi conclusión falsa
de que había que implementarlo — el protocolo ya traía ListEntes, SpawnCardFromDisk,
StopCardFromDisk, KillEnte y EnteStatus.
`recipes/arjectl.toml` lo construye del MISMO commit que `arje-zero` (98db584f), y no por comodidad:
el bus es un protocolo entre dos binarios y un cliente de otro árbol puede conectar sin entenderse
con el init. Publica sólo `arjectl`; el crate también produce un `systemctl` de camuflaje que acá no
se instala — en una distro sin systemd, ese nombre en el PATH invita a escribir runbooks con el
verbo ajeno.
⚠ EL HUECO QUE SÓLO SE VE USÁNDOLO: el genesis de la seed dice qué arranca AL BOOT, pero
`start`/`restart` usan `SpawnCardFromDisk`, que lee `/etc/arje/cards.d/<label>.json` — y el armado
no lo escribía:
$ arjectl start gitea
Error: arje-zero rechazó: card gitea: No such file or directory
(buscada en /etc/arje/cards.d/gitea.json)
Son dos preguntas distintas —qué arranca solo, y qué se puede encarnar a pedido— y arje las responde
desde sitios distintos. `inyectar-cards.py` escribe ahora los dos árboles, y en `cards.d` escribe
TODAS las cards, no sólo las nuevas: `sshd` viene del product-rootfs y tampoco era relanzable.
Medido con la VM arrancada UNA sola vez: la imagen trae `cards.d/{gitea,sshd}.json` · `list-units`
da PID/CPU/MEM/HILOS/reinicios · poner el `app.ini` + `arjectl start gitea` ⇒ **GET / 200 sin
reiniciar** (uptime 6 min) · `arjectl restart gitea` cambia el PID (118 → 192) y sigue en 200.
⚠ Y un aviso que costó un HTTP intermitente: **`arjectl start` sobre un Ente YA VIVO lo DUPLICA** —
`SpawnCardFromDisk` no deduplica por label y arje le da un ULID nuevo. En gitea el síntoma fue
`unable to lock level db … resource temporarily unavailable` y un `[F]`: dos servidores peleando por
el mismo estado. Para relanzar se usa `restart`, o se mira `list-units` antes. Un `start` idempotente
es trabajo de arje, no de esta imagen.
⚠ Deuda anotada, no barrida: `arjectl` va en `perfil.servidor` porque este frente es el que lo pagó.
TODA imagen de takana corre arje-zero como PID 1 y ninguna se puede operar sin él ⇒ el argumento
para subirlo a `base` es fuerte, y es una línea. Se deja como decisión.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
de7c3faa9c |
gitea arranca: su [[service]], probado con el entorno de verdad — y dos fallos que sólo salen ahí
La receta ya declara su servicio (SDD 30) y `perfil.servidor` lo habilita. El ArtifactHash NO se mueve (`b3:391a613e…` antes y después): `[[service]]` está fuera de `hash_inputs`. ⚠ Primero, una corrección del commit anterior: declaraba `gitea` en `perfil.servidor` con SEIS LÍNEAS DE COMENTARIO y sin la línea `"gitea",`. El perfil cargó igual, el grafo no dijo nada y el paquete simplemente no estaba. Lo cazó `targets.py --services` al resolver el label: «lo declara gitea, que NO pertenece al perfil». Un comentario que explica una entrada que no existe se lee como la entrada. La card no encarna el binario directo: **gitea se niega a correr como root** (`[F] Gitea is not supposed to be run as root`), así que va por `setuidgid`. Y comprueba dos cosas del SITIO antes de arrancar, saliendo 78 con un mensaje que las nombra: `/etc/gitea/app.ini` y el usuario `gitea`. Un gitea sin config no falla — arranca y ofrece el asistente de «crear administrador» a quien pase. Probado en el worker con el argv exacto y `env -i`, que es lo que arje hace de verdad. Tres fallos que con una shell normal no se ven NUNCA: · `exec setuidgid …` a secas ⇒ `sh: exec: line 0: setuidgid: not found`. El `sh` de busybox de la imagen no trae `FEATURE_SH_STANDALONE`: no despacha sus applets, los busca en `PATH`, y PID 1 no garantiza ninguno. Todo con ruta absoluta (`/bin/grep`, `/usr/bin/setuidgid`). · sin `PATH` en el `envp` ⇒ `git not found: executable file not found in $PATH`. **gitea lanza `git` como subproceso**, y el mensaje se lee como «falta git» con git instalado y raíz de `base`. Un envp vacío no es «limpio»: es sin PATH. · sin `cd` ⇒ `fatal: error reading '/root/.git'`. El cwd se hereda y gitea corre `git config` en él. `Service` no tiene campo `cwd`, así que va en el argv, a la vista. Con eso: escucha, `GET /` responde **200** y el proceso corre como `gitea`. Las dos guardas verificadas por separado (78 y su mensaje cada una). El worker quedó limpio y censado: sin usuario, sin /etc/gitea, sin /var/lib/gitea, sin symlinks y sin procesos. Y `/etc/gitea` entra en las rutas de la mudanza: el `app.ini` guarda los SECRETOS generados (gitea los escribe en el propio fichero, que por eso tiene que ser suyo, `gitea:gitea 0660`). ⚠ Lo que queda abierto y está anotado en la receta: **el usuario `gitea` no existe en el producto**. `/etc/passwd` de la imagen es la constante `takana_bootstrap::PRODUCT_PASSWD` y sólo trae `root` y `sshd`. El SDD 30 sacó las Cards de una constante de Rust y las puso en la receta; los USUARIOS siguen donde estaban las Cards. Hasta que se declaren, el usuario llega con los datos del sitio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn |
||
|
|
74151a2b0c |
gitea 1.27.0 con sqlite y bindata: el binario sellado no podía abrir la base de datos
La mudanza de gioser pasa por levantar su gitea del otro lado — 28 repos, 26 sin copia fuera. El
corpus ya tenía `gitea` sellado (1.26.4, `b3:35bb4f04…`) y NO sirve, medido contra el artefacto:
$ gitea --config app.ini migrate
Error: sqlite3 requires: -tags sqlite,sqlite_unlock_notify
this Gitea binary was not built with SQLite3 support
Construía, reproducía y no podía abrir la sqlite en la que gioser guarda TODO (`DB_TYPE = sqlite3`).
Y `gitea --version` decía `version development`. Es subcomando-sin-driver un piso más abajo.
Tres cambios, y el tercero es el que no se ve venir:
· Pin 1.27.0 (gioser corre 1.27.0). No es cosmético: gitea migra el esquema hacia adelante y se
planta si la DB trae migraciones más nuevas que el binario, así que el pin va por delante del
origen, nunca por detrás.
· Tags `sqlite,sqlite_unlock_notify` (⇒ `cgo = true`, el driver de mattn es C) y `bindata`, que
embebe `public/` y `templates/`: sin él la receta publica sólo el ejecutable y el servidor
arrancaría sin UI.
· La fuente pasa de `repo`+`commit` al TARBALL DE RELEASE. El clon no alcanza: la UI se compila
con Node y las deps Go no están vendoreadas en git. El `gitea-src-1.27.0.tar.gz` trae las dos
cosas hechas — medido: `public/assets/` 764, `vendor/` 11246, `templates/` 663 — así que
construye con el lab tal cual, sin Node y sin red. La URL es locator (ADR 0013); ancla el sha256.
Nuevo hash: `b3:391a613e…`. Declarado en `perfil.servidor` como paquete y NO en `servicios`:
arrancarlo necesita el [[service]] del SDD 30 con su app.ini, y declarar un arranque que no existe
sería la mentira que ese campo existe para no tener.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
|
||
|
|
0e35652fb1 |
un solo NetworkManager (y KDE recupera WiFi) + upower al corpus sin arrastrar polkit
── 1. Se RETIRA incoming-kde/networkmanager.toml ───────────────────────────────────────────────
networkmanager-qt b3:78bbdcda00d758fb3cc3a0be7da6a67395dfbe0e4d55c5d8e5cf8afaf728ff72
plasma-nm b3:496a9ff7794ae5d89ac2842bdfbca38c25c91df37465a049db7cf75ee0041b82
Ayer se promovió NetworkManager al corpus CON WiFi porque la variante de KDE iba con -Dwifi=false,
y quedaron dos. Esto cierra el arco: retirada la de la cola, `networkmanager-qt` resuelve al PADRE
y la distro tiene UNO solo. Consecuencia concreta: **KDE pasa a tener WiFi**, porque su applet
hablaba con un demonio construido sin soporte inalámbrico.
Medido con `yupana radio` antes de borrar: 2 rebuilds (networkmanager-qt, plasma-nm), los dos en
la cola KDE. Y comprobado sobre el ARTEFACTO del corpus que publica lo que nm-qt pide —`libnm.pc`
y `usr/include/libnm`— antes de quitarle el suelo. Los dos sellan.
── 2. upower al corpus: COSMIC dibujaba una batería sin nadie que se la contara ────────────────
libgudev b3:87ca5e73… · udev-pc b3:fa880b5c… (hash IDÉNTICO desde las dos partes ⇒ cache-hit)
upower b3:c82e655ff7011150f21f9413433c518f12c827c52ecf6f9fc3484f75d85c155c
Ayer escribí que esta cadena eran «3 promociones, una de ellas polkit, decisión de arquitectura».
Fui a mirar y polkit SALE de la ecuación con una perilla: la variante de KDE va `-Dpolkit=enabled`
«porque polkit YA está sellada» —cierto EN ESA COLA y falso desde el corpus—, y apagarlo cuesta
poco medido en FUNCIÓN: polkit en upower sólo gobierna las acciones privilegiadas (suspender e
hibernar por org.freedesktop.UPower, además deprecadas: hoy eso lo hace logind). Reportar batería,
carga, tiempo restante y línea de corriente NO pasa por polkit, que es justo lo que COSMIC quiere.
Se corrigió además la frase del comentario heredado que decía `-Dpolkit=enabled` al lado de un flag
que ahora dice `disabled`: una nota que desmiente al código de al lado es peor que no tener nota.
Y se le añadió su [[service]] (el corpus no lo traía; el label/id son los mismos que en GNOME a
propósito: el ULID identifica al SERVICIO, no al artefacto). No mueve el hash.
Comprobadas las NEEDED de upowerd con provee.py: libupower-glib, libffi.so.8, libz.so.1,
libudev.so.1 — las cuatro las publica el corpus y ya estaban en [deps]. Sin NEEDED colgante.
── 3. targets.toml ─────────────────────────────────────────────────────────────────────────────
escritorio-kde servicios += NetworkManager (llegaba por clausura de plasma-nm y NADIE lo
arrancaba: el applet sobre un demonio apagado)
escritorio-cosmic paquetes += upower · servicios += upowerd
⚠ Y se CORRIGE la nota de ayer que decía «NO declarar networkmanager en escritorio-kde porque
plasma-nm ya arrastra la variante de la cola». Ya no hay variante de cola.
|
||
|
|
5e5369f775 |
libffi-shared en base: python3 volvía a ser INERTE en servidor, cli y base — y el vigía no miraba ahí
`vigia-sonames.py` corría por defecto SÓLO sobre los cinco perfiles de escritorio
(`p.startswith("escritorio")`). O sea que `base`, `cli` y `servidor` —el objetivo de la mudanza—
**nunca se miraron**. Apuntándolo a ellos a mano:
== servidor 147 nodos · 183 sonames provistos · 1 sin proveedor
FALTA libffi.so.8 ← lo piden: python3
`python3` no arrancaba en las tres imágenes, por el mecanismo EXACTO que `musl-shared` y
`zlib-shared` arreglaron dos días antes: la receta canónica de libffi es `--disable-shared`, ningún
artefacto del cierre publica ese soname, y dentro del lab el agujero lo tapa el rootfs de Alpine —
así que el build pasa y la imagen sale rota. `libffi-shared` ya existía en el corpus y ya estaba
declarada en otros dos perfiles; sólo faltaba en `base`.
servidor 148 nodos · 186 sonames · **0 sin proveedor** (base y cli, igual)
## Y el vigía pasa a mirar TODOS los perfiles
⚠ **Es la tercera vez en dos días que un guardián de este repo mide menos de lo que su resumen
afirma**: `static-audit.sh` sólo globeaba `recipes/` y no las 5 colas (3 mentiras ocultas); el bloque
de raíces sucias sólo corría al hidratar un perfil y no veía las 649 recetas fuera de toda imagen
(qdrant con 69 M de basura); y éste dejaba fuera medio sistema. **Un «0 huecos» sobre la mitad del
sistema se imprime igual que uno sobre todo él.**
⇒ Al leer un guardián, leer su SCOPE antes que su veredicto: el glob, el bucle, de dónde saca la
lista. Los ocho perfiles dan 0 sin proveedor, así que ampliar el alcance no mete ruido — sólo deja de
esconder.
|
||
|
|
4b38af8f3f |
networkmanager al corpus CON WiFi y con nmcli — y los perfiles que no tenían gestor de red
networkmanager b3:c36414ece23e322ef007d0acc7ce6f65b8b822ae55c38b38c9afa4b3eb340adb (52 MB)
Arreglo del segundo hueco del barrido: `networkmanager` figuraba SÓLO en escritorio-kde, y desde
GNOME, COSMIC o sway no se alcanza (cola hermana). Pero promover la receta de KDE tal cual habría
sido peor que no hacerlo, y sólo se vio mirando el ARTEFACTO:
· `-Dwifi=false` ⇒ un gestor de red que no maneja interfaces inalámbricas. Allá es correcto
(esa receta existe para dar libnm.so a plasma-nm y nada más); acá habría puesto en tres
imágenes un demonio que arranca, dibuja el applet y no lista una sola red.
· sin `nmcli` ⇒ ni una herramienta con la que manejarlo. En sway eso es la diferencia entre
tener red y no tenerla.
La variante del corpus cambia tres cosas, y la tercera hizo falta MEDIRLA:
1. `-Dwifi=true`. Habla nl80211 por libnl (ya era dep) y delega el handshake WPA2 en
`wpa_supplicant`, que desde hoy está en perfil.base ⇒ va también en [deps] runtime.
2. `readline` en [deps] build.
3. `-Dnmcli=true -Dreadline=libreadline`. Con sólo la dep, nmcli NO se construyó: `-Dnmcli` vale
true de fábrica pero `-Dreadline` es un COMBO que por defecto va en `auto`, y sin decirle
dónde mirar resolvió a «none» ⇒ meson apagó el CLI EN SILENCIO. Comprobado sobre el store.
Y `readline-shared` en runtime, cazado con provee.py sobre las NEEDED del binario: nmcli sale con
`NEEDED libreadline.so.8` y la readline canónica del corpus es sólo .a. La dep de BUILD y la de
RUNTIME son artefactos distintos — tercer caso del día tras cmus y bluez. No mueve el hash.
⚠ Se queda `-Dcrypto=null`, y es una limitación real escrita en la receta: sin cripto no valida
802.1X / WPA-Enterprise (el WiFi de una oficina). WPA2-PSK funciona porque ése lo hace el
supplicant. Encender nss o gnutls arrastra dos cadenas enormes: unidad de trabajo aparte.
── targets.toml ────────────────────────────────────────────────────────────────────────────────
escritorio-gnome, escritorio-cosmic, escritorio-sway += networkmanager (+ servicio)
escritorio-kde, escritorio-sway += wireplumber (+ servicio)
⚠ networkmanager NO se declara en escritorio-kde: allá plasma-nm ya arrastra la variante de la
cola, y dos NetworkManager distintos en la misma ruta es la enfermedad de las dos glib. Que la de
KDE vaya sin WiFi es deuda del frente KDE, y queda escrita donde toca.
|
||
|
|
9ac4e1727f |
targets: barrido de perfiles y servicios — la WiFi faltaba, y tres escritorios no arrancaban nada
── BARRIDO 1: las hojas selladas sin perfil ────────────────────────────────────────────────────
Método, para que se pueda repetir: cruzar el campo `perfiles` de los cinco build-state*.json con
`dependientes_total`. De 905 nodos, 631 sin perfil; de ésos, **620 son HOJAS** (nadie depende de
ellas, o sea que ninguna clausura las puede alcanzar) y 11 llegan por la clausura de otra.
Las 620 se parten en dos por una marca objetiva, la cabecera del importador:
· **529** dicen «PUNTO DE PARTIDA, no final» — volcado crudo de import-nix/import-alpine
(328 Go + 195 Rust + 6 C). Nadie las curó nunca para una imagen.
· **91** están trabajadas a mano. Ésas son las que se triaron una por una.
Lo que salió de ese triaje, y lo que se declara:
**`base` += `wpa_supplicant` — la imagen NO PODÍA ASOCIARSE A UN WiFi.** dhcpcd 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 existía desde el frente de metal —su cabecera dice «para asociar el WiFi del medio live al
AP»— y estaba en CERO perfiles. Es la figura de `foot` y esta vez el precio era quedarse sin red.
**`cli` += nano, tig, patch, socat, pigz, miller, dwarves.** El criterio va escrito en el fichero
porque meter las 620 habría sido peor que no mirar: entra lo que un usuario de esta capa ESPERA
encontrar y hoy no está. El resto queda en el catálogo, que NO es lo mismo que la imagen.
**`escritorio-sway` += wlr-randr**: no había con qué cambiar la resolución ni rotar una pantalla.
── BARRIDO 2: el «enable» ──────────────────────────────────────────────────────────────────────
De los cuatro escritorios **sólo GNOME tenía lista `servicios`**. Los otros tres salían con sus
demonios instalados y NINGUNO arrancado, y la métrica de clausura daba N/N igual — `paquetes` dice
qué se instala, `servicios` dice qué se levanta, y sin la segunda la imagen está a medias sin que
nada falle.
Escritas las tres que faltaban, computando la lista (no a ojo): se cruzaron los `[[service]]` de
las 11 recetas que los declaran contra el campo `perfiles` de los cinco grafos — la CLAUSURA, no
las raíces, porque la mitad de los demonios llegan como dep. Y GNOME sumó `cupsd`+`bluetoothd`,
que nacieron ayer.
⚠ El cruce destapó tres ausencias que NO se arreglan acá y quedan escritas en el fichero:
· KDE y sway tienen `pipewire` y **no tienen `wireplumber`**: sin gestor de sesión los nodos
existen y nadie los conecta.
· COSMIC **no tiene `upowerd`** y dibuja un indicador de batería.
· KDE y sway no tienen `logind-compat`.
Comprobado: tomllib parsea, y los ocho perfiles resuelven con scripts/targets.py.
|
||
|
|
4e807444d1 |
targets: mold, sccache y cliphist — las tres que habrían repetido el error del commit anterior
El grafo regenerado por el cron delató lo que faltaba: `mold` y `cliphist` salían con `perfiles: []`. O sea que en el mismo día en que se midió que 649 recetas selladas no están en ninguna imagen, dos de las recetas nuevas se iban a sumar a la lista. La métrica lo vio porque existe; la intención no alcanza. - `cli` += `mold` y `sccache`. Las dos son herramientas de quien CONSTRUYE con la distro, no de quien la usa. `sccache` estaba sellada hace meses y sin perfil; `mold` es de hoy. - `escritorio-sway` y `escritorio-cosmic` += `cliphist`. SÓLO ahí: habla `wlr-data-control`, que KWin y mutter no implementan — mismo muro que `wf-recorder`. Declararlo en KDE o GNOME daría un binario que arranca y no hace nada, que es peor que no tenerlo. Comprobado con scripts/targets.py: cli resuelve mold+sccache, y sway/cosmic los tres. |
||
|
|
66db4e4054 |
targets: declarar lo que ya estaba sellado y en ninguna imagen — y las dos capacidades del punto 8
MEDIDO al ir a declarar las recetas nuevas, y es más grande que ellas: de las 892 recetas selladas del corpus, **649 no están en NINGÚN perfil**. El 73% del catálogo está compilado y no viaja en ninguna imagen. No es deuda de build (drenaje.json dice 0) y la métrica de perfil NO lo puede ver: mide la clausura de lo DECLARADO. Es la lección de foot a escala de catálogo. Concreto: yazi, atuin, zellij, jujutsu, just, direnv, watchexec, mise, xh, age, rclone, restic, lazygit, difftastic, bottom y duf estaban selladas hace meses y en cero imágenes; y `helix` estaba declarada sólo en escritorio-gnome, que es donde menos sentido tiene. perfil.cli += esas 16 + las nuevas de hoy (btop, ncdu, nushell, aerc, cmus, syncthing). Cero recetas nuevas y cero builds: sólo dejan de ser invisibles. Como los CUATRO escritorios y `servidor` heredan `cli`, una sola edición los alcanza a todos. perfil.servidor += wireguard-tools: el kernel ya trae WireGuard (SDD 22) y no había con qué configurarlo — misma figura que el cortafuegos y el reloj que este perfil ya declaraba. Los cuatro escritorios += cups, bluez (punto 8 de PUBLICABLE, docs/20). Viven en el CORPUS y no en las colas por lo mismo que mpv y atuq: se alcanzan sibling-first→padre, una copia para los cuatro. ⚠ DEUDA DECLARADA, NO OLVIDO: las dos recetas traen su [[service]] y los perfiles NO los arrancan — de los cuatro escritorios sólo escritorio-gnome tiene lista `servicios`. Los otros tres no arrancan NADA, y eso es más grande que cups y bluez. Va escrito en el fichero para que se vea. El barrido de las 649 queda pendiente y es su propia unidad de trabajo: 363 son recetas Go importadas en tanda (herramientas de desarrollo) y NO todas deben entrar en una imagen. Comprobado con scripts/targets.py: los ocho perfiles resuelven, sin nombres desconocidos. |
||
|
|
1b56b16402 |
SDD 30 §4a+§4c: los 9 demonios de GNOME declarados — y aparecieron dos que no estaban en NINGÚN perfil
Lo que el script de sesión lanza con `&` ahora está declarado en las recetas y habilitado en el perfil. Ninguna receta movió su hash: 9/9 idénticos a los que los grafos ya registraban. EL HALLAZGO, y no lo buscaba: la comprobación inversa del resolutor rechazó `arje-logind-compat` y `arje-polkit-compat` porque están en CERO perfiles — y sin embargo qemu-desktop-image.sh los copia al rootfs a mano y el de COSMIC hace `exit 1` si falta logind-compat. Dos binarios imprescindibles, presentes en la imagen y ausentes del destino declarado: la misma forma del agujero de `foot`, encontrada por una comprobación en vez de por una imagen inusable. Son raíces de escritorio-gnome (los dos) y de escritorio-cosmic (sólo logind, verificado que sus scripts no nombran polkit). DOS COSAS QUE NO SON TRANSCRIPCIÓN: - `dbus-daemon --fork` no se traduce tal cual: arje supervisa al HIJO DIRECTO y Type=forking no existe, así que un daemon que forkea y sale deja a arje viendo morir al padre con éxito y reencarnándolo para siempre. La card usa --nofork. - `scope = system|session` decide DÓNDE va la card. Las de sesión necesitan XDG_RUNTIME_DIR y usuario logueado; en el genesis arrancarían antes de que exista ninguno. Y fuera de mirada NADIE entrega cards de sesión todavía, así que salen con AVISO: el hueco queda contado, no omitido. Correcciones propias: la unicidad del label es DENTRO del perfil, no del corpus (upower vive legítimamente en dos colas); la membresía se lee de los CINCO grafos, no sólo el del corpus; una RAÍZ manda sobre el grafo, que es derivado y lo regenera el cron; y la flag nace en inglés (`--services`) como manda la regla 4, aunque `--lista` sea deuda vieja del mismo fichero. `--selftest`: 7 casos, el primero es el CONTROL que tiene que pasar en verde. |
||
|
|
556fcad5fa |
SDD 30 §4a: el perfil ya sabe HABILITAR — y avisa del demonio que nadie arranca
`paquetes` decía qué se INSTALA; no había dónde decir qué se LEVANTA. El sshd del producto arrancaba porque su Card estaba escrita a mano en una constante de Rust, no porque nadie lo hubiera declarado. `servicios = [...]` por perfil (se hereda como `paquetes`, mismo orden y dedup) + `targets.py --servicios <perfil>`, que cruza label → receta → exec contra los `[[service]]` del corpus y la membresía de perfil de build-state.json. Lo que vale más es la comprobación INVERSA: avisa de los paquetes que están en la imagen, TRAEN un demonio y el perfil no arranca. Es la versión servicios de la lección de `foot` —la métrica mide la clausura de lo DECLARADO y no ve lo que falta en la declaración— y no es hipotética: antes de escribir `servicios = ["sshd"]` el resolutor ya avisaba «openssh está en la imagen y TRAE este servicio, pero el perfil no lo arranca». Probado con roturas A PROPÓSITO, 5/5, y con un control que TIENE que pasar: habilitado sin declarar (ERROR) · dos recetas con el mismo label (ERROR) · lo declara una receta que no está en el perfil (ERROR: el card apuntaría a un binario ausente y arje lo encarnaría con ENOENT en cada backoff) · la inversa (AVISO, no rompe el cron) · el caso bueno (rc=0). Los 8 perfiles siguen expandiendo igual y los llamadores de shell no cambian. |
||
|
|
13ce9b16f1 |
targets: la IA local entra en las CUATRO imágenes de escritorio
Decisión del usuario (2026-09-12): `llama-cpp` + `ia-modelo-chat` de raíz en escritorio-gnome, -kde, -cosmic y -sway. El motivo es que el panel de la barra lateral está en las cuatro —viene con `atuq`— y una función del navegador que sólo existe en una imagen es una trampa: en las otras el usuario ve el panel y lee «esta imagen no trae modelo». Cuesta ~1,25 GiB por imagen (199 M el motor + 1,04 GiB el modelo) y está escrito en el comentario, al lado de las líneas, para que bajarlo sea una decisión y no un descuido. Verificado como manda la lección de `foot` que este fichero ya aprendió: las dos raíces expanden en los cuatro perfiles (`scripts/targets.py`), las dos están SELLADAS al hash vigente, y el grafo las ve con sus cuatro perfiles encima — una raíz que no resuelve quedaría `wanted` y ninguna métrica lo diría. El grafo CIERRA y el topo-sort sigue OK. |
||
|
|
d3b42f892d |
zstd-cli: la distro comprime todo con zstd y no traía con qué descomprimirlo
`recipes/zstd.toml` construye **sólo `lib/`** —lo dice su propia cabecera, «build de lib/ (no CLI)»— porque lo que necesitan sus 13 consumidores (mesa, libadwaita, appstream, rsync, los `zstd-sys`) es `libzstd.a`. El binario nunca se construyó, y el agujero sólo se ve al instalar la distro en una máquina de verdad: **un takana recién instalado no puede desempacar su propio laboratorio**, porque el `tar` del rootfs es el de busybox (`tar: unrecognized option: zstd`) y `zstd` no existe. Hubo que extraer por tubería desde otro hub — y un hub que se instala solo no puede depender de eso. ⚠ **Y mi arreglo anterior estaba mal**: declaré `zstd` en `perfil.base` dando por hecho que traía el binario. Se destapó aplicándolo con `takana upgrade` en la caja: «✓ generación 1 aplicada, + 8 añadidos» y `zstd` seguía ausente, porque los 8 ficheros eran headers y `libzstd.a`. **El upgrade hizo exactamente lo que debía; lo que estaba mal era lo que le pedí que aplicara.** Corregido a `zstd-cli`. Variante y no ampliación de la canónica: `zstd` es dep de 13 recetas y `mesa` está entre ellas; re-hashearla arrastraría esa torre por un binario de 1 M que ninguna usa. Mismo criterio que las 23 variantes `*-shared`, con el eje en librería/herramienta en vez de estático/dinámico. Radio cero. `HAVE_ZLIB/LZMA/LZ4=0` a propósito: sin eso el Makefile las detecta del sysroot del LAB y el binario sale pidiendo sonames que el artefacto no publica. Verificado: estático, 0 intérpretes requeridos, `Zstandard CLI v1.5.7`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
9a31857eaa |
targets: zstd en base — un takana recién instalado no podía desempacar su propio laboratorio
La distro comprime TODO con zstd: la imagen del lab (`lab-image.tar.zst`), el respaldo al Storage Box, el `dd` remoto de una instalación. Y la imagen no traía el binario. Medido sembrando el lab en la caja de producción: el `tar` del rootfs es el de busybox y contesta `tar: unrecognized option: zstd`, así que hubo que extraer por tubería desde el hub (`zstd -dc … | ssh caja 'tar -xf -'`). Un hub que se instala solo no puede depender de que otro hub le descomprima las cosas. La receta ya existía y está sellada (`b3:1ceb6215…`): sólo faltaba declararla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
7e41ffd0e6 |
SDD 28 §6.5: el muro derribado — de 23 binarios inertes a 1, y sin mover un ArtifactHash
Elegidas las salidas 1 y 2 del §6.4; hecha la 1, que resultó más barata de lo que parecía y desbloquea la 2 en vez de competir con ella. `musl-shared` publica `/usr/lib/libc.so` + `/lib/ld-musl-x86_64.so.1`. Como variante y no tocando la canónica: `musl` es Stage 1 y su `of_tree` es el baseline del selfhost. Control: `takana hash recipes/musl.toml` sigue en `b3:ce952f72…`. Y `musl` no es dep de nadie ⇒ cero re-hasheo del corpus. En la caja: `sh: python3: not found` → `Python 3.12.10`. De 23 inertes quedó **1**, `sqlite3`, que pedía `libz.so.1`; `zlib-shared` ya estaba sellado y sólo faltaba declararla en `base`. La salida 2 (python/perl estáticos) sigue viva pero deja de ser urgente: con el cargador publicado el sistema ya no cuelga del rootfs del lab. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
7e86459738 |
musl-shared: el corpus publica su propio CARGADOR — 23 binarios dejan de ser inertes
La imagen del perfil servidor traía 23 binarios de 726 que no podían ejecutarse:
$ python3 -c "print(1)"
sh: python3: not found <- y `command -v python3` decía /usr/bin/python3
No faltaban: eran ELF dinámicos pidiendo `/lib/ld-musl-x86_64.so.1`, y **ningún artefacto del corpus
publicaba ese fichero**. Funcionaban en el hub sólo porque el rootfs Alpine del LAB lo presta — la
misma fuga que documentan `zlib-shared` y `expat-shared`, un piso más abajo, y peor: el lab no entra
en `hash_inputs`, así que la dependencia era invisible para el store.
Los 23 son la suite binutils entera + perl, python3, sqlite3, flex y nft. Entre ellos, TODO el
instrumental del proyecto, que es Python.
**Variante y no `--enable-shared` en la canónica**: `musl` es componente de Stage 1 y su `of_tree` es
el baseline de `selfhost-verify`. Re-hashearlo obliga a rehacer ese baseline a propósito, y eso es su
propia unidad de trabajo. Con el patrón `*-shared` (ya hay 23 en el corpus) la canónica no se mueve —
control: `takana hash recipes/musl.toml` sigue dando `b3:ce952f72…`, el mismo sellado de Stage 1. Y
además `musl` no es dep de NADIE (medido: sólo de sí misma; el enlace estático lo resuelve el musl de
zig), así que esto suma sin mover un solo ArtifactHash del corpus.
**⚠ `compiler = "gcc"` y no `zig-cc`, medido.** Con zig cc el `libc.so` sale con 1586 símbolos
dinámicos contra los 1654 de Alpine, y los 68 que faltan son EXACTAMENTE los que musl implementa en
ensamblador x86_64 (`memset memcpy memmove memcmp strlen` y toda la familia matemática) más los
`__stack_chk_*`. No es que no se compilen —en el `libc.a` canónico están—: zig los resuelve con su
propio musl y los deja `FUNC LOCAL HIDDEN` de tamaño 0, fuera de la tabla dinámica. El síntoma es un
cargador que arranca, reloca y muere con `memset: symbol not found`, que se lee como "el binario está
roto" y no como "a la libc le faltan símbolos". Con gcc: **1651 símbolos**, `memset` y `ceil`
presentes, y `python3 3.12.10` CORRE con el cargador del corpus.
De paso: el comentario de `musl.toml` decía que el artefacto aportaba «el loader, para uso dinámico
futuro». Era falso —`--disable-shared` no construye ninguno— y es justo la etiqueta que hizo que
nadie buscara el agujero. Corregido (los comentarios no entran en `hash_inputs`; verificado).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
|
||
|
|
2613fe3951 |
servidor-image.sh: la imagen del perfil servidor, armada por script — y netup pasa a ser raíz
El ensamblado dejaba de ser reproducible en cuanto se cerraba la terminal: cuatro pasos, tres de ellos con una trampa que no se ve. Ahora es un script, con las trampas escritas en su cabecera: 1. **El kernel.** `linux.toml` NO sirve para hcloud (apaga SCSI y el disco de Hetzner es virtio-SCSI). Va `linux-generic`, que sí trae `CONFIG_SCSI_VIRTIO=y` — dato que no está en la receta, lo pone `make defconfig`, y sólo se ve en el `.config` que el artefacto publica. 2. **El init se pisa.** La clausura del perfil trae busybox y su `/sbin/init` gana por hidratarse después. El script lo restaura e IMPRIME la reparación (`../bin/busybox → /usr/bin/arje-zero`), que es la diferencia entre arreglarlo y creer que estaba bien. 3. **EXDEV.** Comprueba con `findmnt` que STORE y WORKDIR estén en el MISMO montaje y aborta con un mensaje que dice qué hacer, en vez de fallar fichero por fichero a mitad de un `cp -al`. 4. **La clave es obligatoria.** Sin `AUTHKEYS` no arma nada: una instalación remota sin `authorized_keys` deja una máquina viva e INALCANZABLE, que es peor que una que no arrancó. Y el cmdline va SIN `init=`, a propósito: así corre el wrapper que escribe `install-image.sh`, que es quien monta `/store` y `/var/lib/hammer`. **`netup` entra como raíz de `perfil.servidor`** aunque su binario ya venga dentro del `product-rootfs`. La razón es concreta y se pagó hoy: el del producto está congelado en el artefacto sellado del bootstrap, así que el arreglo de la ruta on-link NO llegaba a la imagen. Declarado como raíz, la hidratación lo proyecta encima y la imagen lleva el vigente — verificado por sha256: la imagen trae `a23df20e…` y el product-rootfs `a84b0a0d…`. `recipes/netup.toml` re-pineado a `be383ea4` (el commit del arreglo). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
7b38e2ef16 |
kde: los portales — segundo hueco cerrado, y OBS ya tiene con quién hablar
Medido recorriendo los `.service` de D-Bus de TODO el store: los únicos backends de portal sellados eran los de COSMIC y GNOME. Ninguno de KDE, y la cola `incoming-kde` no tenía ni el frontend. Consecuencia concreta: la imagen KDE trae `obs-studio` con su plugin linux-pipewire, que captura pantalla POR EL PORTAL — o sea que en Plasma no tenía con quién hablar mientras en los otros dos escritorios ese frente estaba cerrado. Sin portal tampoco hay diálogo de fichero ni compartir pantalla para nada que los use. Van los DOS paquetes porque la cadena es de tres: cliente → org.freedesktop.portal.Desktop [frontend] → org.freedesktop.impl.portal.* [backend] El frontend se copia de la cola de COSMIC y **comparte artefacto**: hash idéntico (ecbe13f68eca6) desde las dos colas ⇒ un solo directorio en el store, cero builds. El backend es receta NUEVA, 6.7.2 — la MISMA versión que plasma-workspace, kwin y kscreenlocker de esta cola, porque habla interfaces privadas de kwin para el ScreenCast y desalinear ahí es pedir que dos mitades del mismo release se entiendan por casualidad. **Cero frontera nueva**: sus 22 dependencias (Qt6, KF6, KWayland, protocolos de Wayland, xkbcommon) ya estaban todas, verificadas una por una antes de escribir la receta. ⚠ SIN EL PORTAL DE IMPRESIÓN, y la causa es más honda que el error. El build moría en `src/print.cpp:27` con `'QtPrintSupport/private/qcups_p.h' file not found`, y la cadena está medida entera: no hay NINGUNA receta de cups en el catálogo ⇒ qtbase 6.11.1 se construyó sin CUPS ⇒ su `QtPrintSupport/private/` trae qpaintengine_alpha_p.h y qprintengine_pdf_p.h pero NO qcups_p.h. No falta una cabecera: falta el subsistema. Un portal de impresión sin con qué imprimir no es funcionalidad que se pierde, es código muerto que no enlaza. Upstream no expone `option()` para portales —se compilan todos—, así que el parche va en TRES sitios y no en uno: la fuente, la instanciación (desktopportal.cpp/.h) y **la lista de interfaces que el backend ANUNCIA**. El tercero es el que se olvidaría y el que importa: anunciar `impl.portal.Print` sin implementarlo hace que el frontend enrute a un portal que no contesta, que es peor que no tenerlo. Cada `sed` lleva su `grep` de verificación al lado, para que el día que upstream mueva esas líneas el parche falle RUIDOSO en vez de volverse inerte. VERIFICADO sobre el artefacto sellado: publica `org.freedesktop.impl.portal.desktop.kde.service`, y su `kde.portal` anuncia 17 interfaces con **0 apariciones de Print** e incluyendo ScreenCast, Screenshot y RemoteDesktop, que es exactamente lo que OBS necesita. Y sobre el rootfs hidratado: están el frontend (`org.freedesktop.portal.Desktop.service`), el backend y el `kde.portal`. ⚠ Y la licencia casi la firmo mal: leí la cabecera de UN fichero y generalicé. Contadas las etiquetas SPDX del árbol entero: 88 `LGPL-2.0-or-later`, 23 de la fórmula de KDE e.V. y 1 `GPL-2.0-or-later`. El binario las enlaza a todas ⇒ la expresión es la CONJUNCIÓN. El perfil pasa a 98 raíces, 361/361 listo, deuda 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV |
||
|
|
c599daa346 |
SDD 28: el servidor de producción — y perfil.servidor declarando la deuda que no se ve
El frente arranca con el inventario MEDIDO de gioser, no con un plan: PID 1 es `arje-zero` (nuestro init ya gobierna un servidor real, aunque sobre userland Artix), Hetzner Cloud arranca por **BIOS** —que es justo el layout que `takana-install.sh` ya escribe— y el kernel propio trae VIRTIO/EXT4 `=y` con `netup` haciendo DHCPv4, que es exactamente el caso de una caja hcloud. Dos hallazgos que cambian el trabajo: - **La secuencia de arranque de gioser no está escrita en ningún lado.** `rc-status` dice `stopped` para caddy, gitea, sshd, cronie y act-runner y los cinco están VIVOS colgando de PID 1. Un `arje-absorb --from openrc` produciría una Semilla perfecta que no levanta el servidor. - **La mitad del Caddyfile describe un servidor que ya no existe**: 4 de 19 dominios sin DNS y sin ficheros en `/var/www`, uno en 502. Una mudanza fiel copia también la basura ⇒ la regla queda escrita: no se muda lo que no responde. `[perfil.servidor]` hereda `cli` (SDD 27 §7.1) y declara 5 raíces que existen (openssh, caddy, curl, wget, tmux) y **5 que NO** (takana, chrony, nftables, cronie, logrotate). Quedan `wanted`, y `wanted` no es `debt` ⇒ `drenaje.json` seguirá diciendo `deuda=0`. Se declaran igual: sin ellas el perfil saldría N/N —completo y verde— describiendo un servidor sin hora, sin cortafuegos y sin latido. Es la lección de `foot` en escritorio-sway, pagada por adelantado. Cuenta esperada: 5, verificada resolviendo cada nombre contra el campo `name` de las 869 recetas. La peor de esas cinco: **takana no tiene receta**. El corpus construye 869 y no la suya, así que el host no puede instalar takana desde el repo de takana — el bloqueante de que el servidor se sirva a sí mismo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x |
||
|
|
c20372597a |
targets: kde/gnome/cosmic heredan cli — las imágenes ya no salen sin shell ni red
Paso 1 del SDD 27. Resultó ser UNA LÍNEA POR PERFIL: el mecanismo `hereda` ya existía en `scripts/targets.py` —transitivo, con detección de ciclos y orden preservado— y `escritorio-sway` ya lo usaba desde el 2026-08-07. Los otros tres nunca lo declararon, y eso no era una decisión: era composición no declarada. LO QUE ARREGLA, medido antes: de los 27 paquetes del perfil `base`, la clausura de escritorio-kde alcanzaba DOS. Contra el rootfs real: sin `bash`, sin `sudo`/`doas`, sin `git`, sin `useradd`, sin `gpg` y **sin `dhcpcd`** —o sea sin con qué pedir una IP—; y lo que parecía estar (`ip`, `mount`, `fsck`) eran applets de busybox (`/sbin/ip → ../bin/busybox`). DESPUÉS, verificado hidratando de verdad y no leyendo el grafo: en el rootfs aparecen `bash`, `sudo`, `doas`, `git`, `dhcpcd`, `useradd`, `gpg`, `rg`, `fd`, `bat`, y `/sbin/ip` pasa a ser el BINARIO de iproute2 en vez del applet. raíces kde 49→96 · gnome 39→86 · cosmic 43→88 nodos kde 301→356 · gnome 204→256 · cosmic 162→216 deuda 0 en los tres — no hubo que construir NADA, ya estaba todo sellado colisiones nuevas al hidratar: CERO (los 28 .hammer-tmp son los de gmp/mpfr de antes) ⚠ `escritorio-mirada` NO hereda a propósito: es el rootfs *slim* del USB y sumarle 47 raíces contradice su razón de ser. Si algún día se quiere, es la misma línea. `kde-rootfs` en el volumen se rehidrató con la herencia (11 G) para que el nombre canónico no quede viejo — que es el error que este mismo día costó encontrar en GNOME y COSMIC. El rootfs FUNDIDO y la imagen siguen siendo los de antes: rehacerlos es un paso aparte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV |
||
|
|
542d9875fb |
kde: upower — la imagen declaraba powerdevil y no tenía batería
Primero de los dos huecos que dejaban a KDE POR DEBAJO de una instalación estándar. El perfil declaraba `powerdevil` —el gestor de energía de Plasma— pero no a `upower`, que es con quien powerdevil habla para leer la batería y aplicar perfiles. Medido: upower sólo existía en la cola de GNOME, y las colas no se ven entre sí (HERMANO→corpus, nunca de reojo). En un portátil eso es un escritorio sin indicador de batería, y el arranque no falla ⇒ no se ve hasta usarlo. Cuesta TRES recetas y no siete, y la diferencia es una perilla: `-Dintrospection=disabled`. GNOME la necesita ENCENDIDA porque su shell hace `imports.gi.UPowerGlib` desde JavaScript, o sea que carga el typelib. powerdevil NO: habla D-Bus con `org.freedesktop.UPower`. Apagarla saca de la frontera gobject-introspection, gi-foreign-girs, glib-introspected y py3-setuptools, que sólo viven en la cola de GNOME. ⚠ Y NO SE PROMUEVEN AL CORPUS, aunque era lo primero que intenté. Al mover `libgudev` allá su ArtifactHash CAMBIÓ (7404bc8d → 87ca5e73) y eso no es una mudanza: `glib` existe en las DOS colas y son recetas distintas, así que desde `incoming-gnome/` se construye contra la glib de GNOME y desde el corpus contra la del corpus. Promoverla habría arrastrado a GNOME a la glib del corpus — el cuadro de las «dos glib» que este repo ya pagó caro. Revertido y verificado que vuelve al hash de antes (7404bc8d), o sea GNOME intacto. Se COPIAN a la cola de KDE. `udev-pc` sí comparte artefacto (sus deps son sólo pkgconf ⇒ mismo hash desde las dos colas, un solo directorio en el store). `libgudev` es otro a propósito, por la glib. VERIFICADO sobre el artefacto sellado, no supuesto: trae `usr/libexec/upowerd`, el `org.freedesktop.UPower.service` de system-services y su política en `system.d` — que es exactamente lo que powerdevil necesita para que el demonio arranque por activación D-Bus. El perfil pasa a 49 raíces, 301/301 listo, deuda 0. Queda el segundo hueco: KDE no tiene backend de portal (ni `xdg-desktop-portal` en su cola), o sea que no hay compartir pantalla — y en particular la captura de obs-studio, que la imagen ya trae, no tiene con quién hablar en Plasma. Eso es más caro: pide traer el portal a la cola y ESCRIBIR `xdg-desktop-portal-kde`, que no existe como receta en ninguna cola. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV |
||
|
|
e1d58cc583 |
cosmic: hidratar desde el perfil — su lista a mano se saltaba 17 declarados
Misma divergencia que se encontró ayer en GNOME, medida igual: `hydrate-cosmic.sh` nombra 25 raíces escritas a mano dentro del script, el perfil `escritorio-cosmic` declara 43, y **17 paquetes declarados no llegaban al rootfs**: adwaita-cursors · xkeyboard-config · bash · atuq (el navegador) · mpv · swayimg zathura + zathura-pdf-poppler · dejavu-fonts y dejavu-fonts-nerd · libnotify desktop-file-utils · hicolor-icon-theme · y las cinco *-shared de runtime ⚠ EL PRIMERO NO ES UNA COINCIDENCIA: `adwaita-cursors` es EXACTAMENTE el ejemplo con el que la cabecera de `scripts/hydrate-profile.py` advirtió de esto el 2026-09-04 — «se añadió a las raíces de cosmic y sway, y los scripts de hidratación, que no la conocen, habrían seguido armando un rootfs sin cursores». No era hipotético: seguía pasando hoy, cinco días después. Y `xkeyboard-config` es la cicatriz de sway repitiéndose: sin él el compositor arranca sin mapa de teclado. Y AL REVÉS NO SE PIERDE NADA, que es lo que hace el cambio barato y lo separa del caso GNOME (donde primero hubo que mover 11 raíces de runtime al perfil): de las 25 del script, la única que el perfil no declara es `zlib-shared`, y la clausura del perfil la alcanza igual. O sea que el motivo que justificaba la lista a mano —los componentes que `cosmic-session` lanza por PATH, que ningún grafo de build ve— YA ESTÁ RESUELTO en el perfil, que los declara todos como raíces. La lista quedó redundante sin que nadie lo notara. VERIFICADO HIDRATANDO, no leyendo: `hydrate-profile.py escritorio-cosmic` proyecta 162/162 nodos (24166 ficheros, 4,8 G) y ahí están los 17. (Ojo con la sonda: `bash` instala en `/bin/bash`, no en `/usr/bin` — mi primer chequeo lo dio por ausente y era el chequeo el que miraba mal.) ⚠ Y UN DATO QUE MATIZA EL DIAGNÓSTICO Y LO EMPEORA. El rootfs que hay en disco (`escritorios/cosmic-rootfs`, 3,9 G) SÍ tiene cursores, xkb y mpv — o sea que no se armó con la lista por defecto, alguien le pasó raíces a mano. Y aun así le falta `atuq`. El problema real no es que la lista esté mal: es que **lo que llega a la imagen depende de lo que alguien se acuerde de teclear**. Derivarlo del perfil quita esa variable. El script se deja porque su cabecera documenta por qué COSMIC necesita raíces de runtime explícitas —sigue siendo cierto sobre la naturaleza de cosmic-session—; lo que ya no es cierto es que su lista sea la verdad. Los dos armadores de imagen (qemu y metal) ahora apuntan a `hydrate-profile.py`. ── Y de paso, una nota vieja de targets.toml corregida ──────────────────────────────────────────── Decía que el perfil de KDE «son 13 raíces de ARRANQUE EN METAL, no la imagen completa», con konsole/kate/dolphin y otras 19 apps selladas y sin declarar. Ya no: hoy son 48 raíces, con esas apps, las fuentes y las *-shared declaradas — el frente KDE lo reordenó y la nota quedó atrás. De su cola quedan 8 recetas fuera del perfil y son las que corresponde: libICE, libSM, libXtst y los cinco eslabones huérfanos de xwayland desde que se retiró su receta. Lo que a KDE le queda es OTRO problema, también medido hoy: su imagen no se hidrata del perfil sino de un rootfs YA ARMADO (8,4 G) que nadie re-deriva. De las 18 hojas del perfil están 15 — faltan `obs-studio` y `atuq`, justo las dos añadidas después de esa hidratación. Re-hidratarlo es decisión del frente KDE. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV |
||
|
|
eb8f217245 |
gnome: el perfil y el hidratador decían cosas distintas — 21 paquetes no llegaban a la imagen
Verificando que las extensiones de la tanda anterior llegaran al rootfs apareció algo bastante peor que lo que fui a buscar: **`scripts/gnome/hydrate-gnome.sh` no lee `targets.toml`**. Arma el rootfs desde una lista de 14 raíces escrita a mano dentro del propio script, y de las 28 raíces que el perfil declaraba sólo DOS coincidían. MEDIDO recorriendo la clausura de esa lista (123 recetas) contra la membresía del perfil: **21 paquetes declarados en `escritorio-gnome` no llegaban al rootfs hidratado**. No son accesorios — son las hojas, que es justo lo que un perfil declara porque no es dep de build de nadie: atuq (el navegador) · foot (la terminal) · helix (el editor) · mpv · swayimg (el visor) zathura + zathura-pdf-poppler (el lector de PDF) · dejavu-fonts y dejavu-fonts-nerd (las FUENTES) xdg-desktop-portal-gnome · libnotify · desktop-file-utils bzip2-shared, expat-shared, libffi-shared, ncurses-shared, xz-shared (las .so que se cazaron el 2026-09-03 recorriendo NEEDED, justamente para que el rootfs no resolviera contra el lab) y las cuatro de ayer: las tres extensiones y gnome-tweaks O sea que el trabajo de descubrir que la imagen no tenía terminal, ni editor, ni una sola fuente —con su análisis largo escrito en este mismo fichero— estaba DECLARADO y no llegaba. Y la divergencia iba en los dos sentidos: 11 raíces de RUNTIME vivían sólo dentro del script y el perfil no las conocía (accountsservice, geoclue, libgdm, libgweather, upower, librsvg, ibus, los dos gi-*-typelibs, gsd-schemas y wireplumber). Sin ellas el shell no arranca: las carga por `imports.gi.*`, que es invisible para la clausura de deps. ARREGLO: las 11 se mueven a `targets.toml` con la justificación que el script ya traía bien escrita, y el script queda marcado como SUPERADO por `scripts/hydrate-profile.py`, que hidrata desde el perfil — la única declaración que existe. Su propia cabecera ya advertía de este modo de fallo («DOS fuentes de verdad para lo mismo, y divergen sin que nada lo diga»); esto es esa advertencia cumpliéndose. El perfil pasa de 28 a 39 raíces y de 190 a 204 nodos, deuda 0. VERIFICADO HIDRATANDO DE VERDAD, no leyendo: `hydrate-profile.py escritorio-gnome` proyecta 210/210 nodos (27993 ficheros, 3,5 G) y ahí están las tres extensiones, `gnome-tweaks` con su módulo `gtweak`, y los tres gschemas en el directorio GLOBAL. Después se corrió el bloque nuevo del lanzador contra ESE árbol: enumeró las tres, escribió el override y `glib-compile-schemas` salió 0 con stderr vacío. ⚠ DOS COSAS QUE APRENDÍ MIDIENDO Y CONVIENE QUE ESTÉN DICHAS: 1. **El hub NO puede hidratar GNOME hoy.** `gjs`, `gnome-shell` y `spidermonkey` salen «no sellado» localmente. No es deriva ni un grafo mintiendo: sus hashes vigentes SÍ están en `work/farm-sellados.txt` — o sea sellados EN EL WORKER — y la cosecha baja el manifiesto y no el store, a propósito (bajarlo deshacía la poda). `build-state` los cuenta bien porque mira también el manifiesto; `hydrate-profile.py` mira sólo el disco local. Los dos tienen razón. 2. **EXDEV entre bind-mounts, otra vez.** Hidratar a `work/` o a `work/out` falla con «Invalid cross-device link» AUNQUE `work/out` esté en el mismo /dev/sdb que el store: son bind-mounts distintos y `linkat()` no cruza montajes. Hay que hidratar accediendo al store por su montaje raíz (`--store /mnt/cosecha/store --into /mnt/cosecha/escritorios/…`). El mensaje de error del script lo dice y aun así me costó dos intentos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV |
||
|
|
4e4a2c8d2a |
gnome: dash-to-panel, appindicator y gnome-tweaks — y la lista de activas sale de las recetas
GNOME deja de ser la cáscara: tres extensiones y la app que el proyecto no instala y todo el mundo
termina instalando. El perfil queda 190/190 con 28 raíces, deuda 0.
dash-to-panel v73 junta dash, ventanas y bandeja en una sola barra (lo que más cambia el uso)
appindicator v64 devuelve los ICONOS DE BANDEJA que GNOME quitó; sin ella las apps que usan
StatusNotifierItem corren y no tienen dónde mostrarse
blur-my-shell v72 (ya estaba; se le saca el override, ver abajo)
gnome-tweaks 46.1 tema, iconos, cursor, tipografías, los botones de minimizar/maximizar que
GNOME esconde, y el interruptor de las extensiones
⚠ LA LISTA DE ACTIVAS SE MUEVE A UN SOLO SITIO, y esto era una bomba de relojería. `enabled-extensions`
es un ARRAY: un override de GSettings lo escribe ENTERO. Con la primera extensión el override vivía
en su receta y funcionaba; con la segunda NO se habrían sumado — ganaría la que compile última
(orden alfabético) y las otras quedarían instaladas y MUERTAS, sin un solo error. Ahora la arma
`scripts/gnome/gnome-start-qemu.sh`, que ya compilaba los esquemas al arrancar, enumerando los uuid
presentes en /usr/share/gnome-shell/extensions. La política es «lo que la imagen instala, se
activa»; el dconf del usuario gana sobre el default.
Una receta NO puede ser ese sitio: hammer exige `[source]` en toda receta («missing field `source`»),
así que no hay forma de escribir una receta de pura política. Se probó el bloque contra un árbol de
juguete: lista las dos extensiones, salta un directorio sin metadata.json, el override compila con
stderr vacío y `gsettings get` devuelve las dos. Y si la lista cambia borra `gschemas.compiled` para
forzar recompilación — sin eso, una imagen ya arrancada ignoraría la extensión nueva en silencio.
⚠ EL HALLAZGO CARO: `msgfmt` de gettext-tiny REVIENTA con las formas plurales del árabe. SIGILL,
exit 132, «index 6 out of bounds for type 'size_t[6]'», y deja un .mo de 0 bytes. Reproducido FUERA
de la receta con el binario sellado. No es el idioma sino el mensaje: blur-my-shell compila su
ar.po sin problema porque no tiene NINGÚN plural (msgstr[5] = 0); el de tweaks tiene exactamente
uno. Acá se saca `ar` del LINGUAS y se pierde la traducción de UNA app, con el diagnóstico escrito
en la receta. ARREGLARLO ES OTRA UNIDAD DE TRABAJO: gettext-tiny la usan 108 recetas.
Cada receta usa el mecanismo de SU upstream, que no es el mismo en las tres:
blur-my-shell `make build` llama a `gnome-extensions pack` —el CLI que nuestro shell apaga— así
que se copia el árbol a mano, con el layout LEÍDO del zip de EGO y comparado
contra él (idéntico, sin sobras ni faltantes)
dash-to-panel su Makefile tiene un `install` que honra DESTDIR y hace lo de un paquete de
distro: gschema y locale a los directorios GLOBALES. ⚠ `VERSION=73` se pasa a
mano porque sin ella el Makefile hace `git describe` y el árbol viene por
`git archive`, sin `.git` ⇒ metadata quedaría con el marcador "version": 9999
appindicator meson. ⚠ `-Dlocal_install=disabled` explícito: en `auto` decide mirando si el uid
es 0, o sea que hoy acierta POR ACCIDENTE — y si el lab dejara de correr como
root instalaría en $HOME/.local y el artefacto sellaría vacío. Y `jq` va en deps
aunque sea JavaScript puro: su meson saca el uuid del metadata.json con jq
⚠ gnome-tweaks NO costó ninguna receta nueva, y eso hubo que medirlo: su meson declara siete deps
mínimas de runtime y las siete ya estaban selladas. La que parecía traer una cadena de Python
entera —`pygobject-3.0`— la provee `py3-gobject`, que YA ESTABA en el corpus con otro nombre y
publica `pygobject-3.0.pc`. El nombre no es el hecho, otra vez.
Sí costó dos deps que el configure reclamó y que son el patrón `.pc Requires` → `[deps].build`:
`appstream` (la pide `libadwaita-1.pc` y el error culpa a libadwaita, que está bien instalada) y
`desktop-file-utils` (meson la busca como PROGRAMA para `update-desktop-database`).
Sus deps de RUNTIME —python3 y py3-gobject— van declaradas: es una app de Python, si no están en la
imagen se instala y no arranca, y ningún build lo delata.
Las cuatro licencias, verificadas en la fuente pineada y NINGUNA leída del COPYING —cuyo apéndice
«How to Apply» trae siempre la frase «or any later version»—: blur-my-shell del README del commit,
dash-to-panel y appindicator de la cabecera de sus fuentes, gnome-tweaks de su etiqueta SPDX.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
|
||
|
|
8c2cb10414 |
gnome: la primera extensión del catálogo — blur-my-shell v72, instalada Y activa
La imagen de GNOME traía la CÁSCARA: el shell sabe cargar extensiones —`ui/extensionSystem.js` va
en el gresource sin opción que lo apague y su gschema sellado trae `enabled-extensions`— pero no
había ninguna que cargar. Esta es la primera, y fija el patrón para las otras ~1400.
⚠ NO HIZO FALTA TLS, que era la conclusión fácil del análisis de ayer. glib-networking sólo lo pide
BAJARLAS desde extensions.gnome.org con el descargador del shell. Empaquetada, una extensión se
instala como cualquier receta y no toca la red.
LA FUENTE ES EL REPO DEL AUTOR, NO EL ZIP DE EGO, por dos razones y las dos medidas:
1. hammer no sabe abrir un zip: el fetch extrae con `tar -x` y GNU tar contesta «This does not
look like a tar archive». Ninguna receta del corpus usa .zip.
2. Y aunque lo abriera, el zip de EGO es un ARTEFACTO que construye EGO desde este mismo repo.
Pinear el zip es pinear el binario de un tercero; pinear el commit es pinear el código.
El tag v72 es el que EGO sirve para shell 48 —preguntado a su API, no supuesto— y el metadata.json
declara ['46','47','48','49','50']. ⚠ Eso hay que preguntarlo por extensión: nixpkgs va por GNOME
50 y hay extensiones que ya sólo declaran el major nuevo.
SIN `gnome-extensions pack`, que es lo que usa el Makefile de upstream y es el CLI que nuestra
receta del shell apaga (-Dextensions_tool=false; encenderlo costaría gnome-autoar y re-hashear
gnome-shell entero). No hace falta: `pack` sólo arma un zip que después alguien descomprime, y lo
que el shell lee es un DIRECTORIO. El layout no se inventó — se leyó del zip de EGO v72, que es la
salida de ese mismo `pack`, y el artefacto sellado se comparó contra él: **entradas idénticas, ni
sobras ni faltantes**, 45 .mo compilados.
⚠ INSTALADA ≠ ACTIVA, y esto es lo que hace que la receta sirva. El shell sólo carga lo que esté en
`org.gnome.shell enabled-extensions`: sin eso el artefacto sella, el fichero está en la imagen y NO
PASA NADA al arrancar — el mismo cuadro de «sellado ≠ instalado» que este repo ya se comió con
foot, mpv y libnotify, un escalón más abajo. Se resuelve con un override de GSettings, que cambia
el DEFAULT y deja ganar al dconf del usuario que no la quiera.
PROBADO DE VERDAD, no deducido: se compiló el directorio de esquemas del shell con el override
puesto (`glib-compile-schemas`, exit 0 y stderr vacío — que hay que mirar, porque sale 0 aunque
RECHACE un esquema) y después `gsettings get org.gnome.shell enabled-extensions` devolvió
['blur-my-shell@aunetx'].
⚠ TRAMPA ANOTADA PARA LA SEGUNDA: `enabled-extensions` es un ARRAY y el override lo escribe ENTERO.
Dos recetas con su propio override no se suman — gana la que compile última y la otra queda
instalada y muerta, sin error. Cuando llegue la segunda, la lista pasa a un solo sitio.
Y una corrección sobre la licencia, que casi firmo mal: el LICENSE es el texto del GPLv3, y su
apéndice «How to Apply» CONTIENE la frase «either version 3 … or any later version» siempre, así
que grepearla ahí no distingue -only de -or-later (la trampa que la memoria del repo ya tenía
escrita). La evidencia buena es el README del commit pineado: «This program is distributed under
the terms of the GNU General Public License, version 3 or later». El árbol JS no lleva cabecera de
licencia en ningún fichero.
Va de RAÍZ en escritorio-gnome por la lección de `foot` que targets.toml ya aprendió tres veces.
El perfil queda 183/183 listo con 25 raíces, deuda 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
|
||
|
|
248fa8e991 |
desktop-file-utils: cerrar el «abrir con»
update-desktop-database construye la caché MIME→APLICACIÓN (mimeinfo.cache). Comprobado antes de escribir la receta: NINGÚN artefacto del store lo traía, así que esa caché no se construía nunca. Con shared-mime-info (cerrado ayer) el escritorio sabe QUÉ TIPO es un fichero; sin ésta seguía sin saber CON QUÉ abrirlo — el menú «abrir con» vacío y sin aplicación por defecto. Misma familia que aquel hueco: falla en RUNTIME, no en build. gnome-shell, libadwaita y mutter lo piden en nixpkgs y acá construyen tan contentos sin él, así que ninguna métrica de deuda puede verlo. Prueba del consumidor con 14 .desktop REALES de okular/dolphin/kate/konsole/spectacle: mimeinfo.cache de 2.368 bytes · 47 asociaciones application/pdf=okularApplication_pdf.desktop text/plain=okularApplication_txt.desktop;org.kde.kate.desktop;org.kde.kwrite.desktop Los 4 binarios estáticos con 0 NEEDED (--prefer-static desde el principio, no tras el error). REPRODUCE bit a bit · tarball en el mirror. Va como RAÍZ en los CUATRO escritorios y no en , y la razón importa: escritorio-kde y escritorio-gnome comparten CERO raíces con base y bash NO está en su clausura. escritorio-mirada documenta que no hereda «a propósito, es slim»; esos dos no dicen nada, y sway sí hereda cli. Declararla en los cuatro funciona con cualquiera de las dos lecturas. La asimetría en sí queda para decidir aparte: hoy «KDE 302/302» significa «la capa de escritorio está completa», no «la imagen está completa». Triaje: huecos 2 → 1 (queda ). Clausuras 303/182/160/205, todas selladas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2 |
||
|
|
1da6851775 |
bash-completion: cerrar el hueco del tabulador
bash es RAÍZ del perfil base —el shell de la distro— y cinco recetas repartidas en cuatro colas (appstream, colord, gnome-shell, modemmanager, swayimg) instalan completados en el directorio que este paquete define. Sin él NO los instalan y siguen adelante: nada falla, nadie se entera, y el usuario se queda sin tabulador en las herramientas del sistema. Hueco de RUNTIME puro, de los que ninguna métrica de deuda puede ver — por eso hizo falta la frontera para encontrarlo. Va al CORPUS y como raíz de , no a una cola: lo alcanzan las cuatro imágenes de escritorio y la CLI, y una receta resuelve sibling-first y después el catálogo padre, nunca una cola hermana. Y entra como RAÍZ porque nadie lo alcanza por deps de build: los consumidores sólo preguntan por su .pc. Verificado como consumidor, las dos mitades: pkgconf --variable=completionsdir bash-completion → /usr/share/bash-completion/completions el marco carga 131 completados y complete devuelve regla · 522 ficheros instalados REPRODUCE bit a bit · tarball en el mirror Triaje: los huecos vuelven a CERO. base pasa de 26 a 27 raíces, clausura 58/58 sellada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2 |
||
|
|
4f9ee5430f |
dunst: sway ya atiende las notificaciones, y hay un píxel que lo prueba
Era la única mitad que quedaba abierta del mapa del §6.10: KDE atiende
`org.freedesktop.Notifications` con plasma-workspace, GNOME con gnome-shell y
COSMIC con cosmic-notifications; sway no tenía a NADIE, así que una página que
pedía notificar mandaba el mensaje al bus y se lo comía el silencio.
`dunst` 1.12.2 y no `mako` —que es el de la casa wlroots— por dos razones
medidas: el nombre `mako` YA ESTÁ OCUPADO en el corpus por el motor de plantillas
de mesa, y el cierre del perfil incluye las herramientas de build, así que la
imagen terminaría con dos artefactos homónimos peleando por las mismas rutas; y
mako habla D-Bus por sd-bus, que en musl es una receta nueva (basu), mientras
dunst habla por GDBus, que ya está sellado. Cero dependencias nuevas.
DOS GUARDIANES EN LA PROPIA RECETA, porque las dos cosas que importan no se ven
en que compile:
- `X11=0` es una intención; el hecho se lee en el ELF. dunst compila los dos
backends por defecto y su config.mk avisa: sin wayland «forzará xwayland». En
esta distro eso sella un binario que arranca, toma el bus y NO PINTA NUNCA. La
fase exige `libwayland-client` entre los NEEDED y prohíbe `libX11`.
- El `.service` de D-Bus es la mitad que importa: sin systemd, al daemon lo
levanta EL BUS. Se comprueba que reclame `org.freedesktop.Notifications` y se
le saca la línea `SystemdService=`, que acá no puede significar nada.
Y `DUNSTIFY=0`: entró primero en 1 y el binario **segfaultea hasta en
`--help`** (rc=139 en las tres pruebas). La causa es el cuadro que este repo ya
tiene escrito: nuestro `libnotify.so.4` es compartido y arrastra la cadena glib
`.so`, mientras esta cola enlaza la GLib estática ⇒ dos copias de GObject en un
proceso. No se pierde nada: el emisor de la casa es `notify-send`, que viene
DENTRO del artefacto de libnotify y ya está en las cuatro imágenes. Hay un
guardián que frena el día que alguien vuelva a poner DUNSTIFY=1 sin leer.
LA PRUEBA, en `scripts/wlr/dunst-headless.sh`: sway headless + bus de sesión +
`notify-send`, y NADIE lanza dunst a mano — lo activa el bus por el .service,
que es el mismo camino de una página web en atuq. La evidencia es un diff de
píxeles antes/después acotado al cuadrante donde dunst dibuja, porque un log en
verde es compatible con una pantalla vacía y el color no distingue (swaynag pinta
su barra de error en el mismo rojo).
positivo 14832 píxeles cambiados · servidor: dunst knopwob 1.12.2 1.2
control 0 píxeles · ServiceUnknown · notify-send rc=1
El control negativo borra el .service dentro del overlay temporal y EXIGE que no
se dibuje nada: probado en los dos sentidos.
Captura en docs/evidencia/dunst-sway-notificacion-2026-09-07.png.
Vigía de sonames: escritorio-sway 203 → 204 nodos, 512 sonames, 0 sin proveedor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNFzrqFBEt6W7maGJKf2Xs
|
||
|
|
07b9b31454 |
targets: libnotify en los cuatro escritorios que llevan atuq
Sin esta línea la receta queda sellada y en NINGUNA imagen — la lección de `foot`
que este mismo fichero tiene escrita cuatro renglones más arriba de `atuq`, y que
la clausura no puede ver porque mide lo declarado.
Va en los cuatro perfiles que ya llevan el navegador (kde, gnome, cosmic, sway) y
NO en base/cli/mirada, que no lo llevan. Verificado parseando el TOML y no
leyéndolo: libnotify=1 en esos cuatro, 0 en los otros tres.
Medición al lado, con el vigía de sonames antes y después:
kde 306 -> 307 nodos 2083 -> 2086 sonames 0 sin proveedor
gnome 190 -> 191 657 -> 660 0
cosmic 161 -> 162 498 -> 501 0
sway 201 -> 202 509 -> 512 0
mirada 41 -> 41 231 -> 231 0 (no lleva atuq)
+1 nodo exacto por perfil, que es lo que se agregó, y el que no lo lleva no se
movió. Si hubiera arrastrado algo sin querer, el delta no sería 1.
Y una comprobación que el mapa de §6.10 no da y que es la que decide si el
`dlopen` va a funcionar: que resuelvan las deps de la PROPIA librería. Un dlopen
falla en silencio igual si libnotify está pero su gdk-pixbuf no. Se lo pregunté
al loader musl, no a mí:
ld-musl --list /usr/lib/libnotify.so.4
libgdk_pixbuf-2.0.so.0 => /usr/lib/... libgobject-2.0.so.0 => /usr/lib/...
libglib-2.0.so.0 => /usr/lib/... libgio-2.0.so.0 => /usr/lib/...
libc.so => /lib/ld-musl-x86_64.so.1
Toda la cadena cae dentro del rootfs salvo libc.so, que es la excepción del lab
ya documentada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgNtJEFetMYbXax5dVUjZZ
|
||
|
|
3a83d2eda4 |
gnome: xz-shared — sin ella NINGUNA app de la imagen carga, no una
`scripts/vigia-sonames.py` sobre los cinco perfiles marcaba `liblzma.so.5` en GNOME pedido por `libadwaita` y por `python3`. La columna de QUIÉN lo pide es la que decide, y acá decidió fuerte: mirando el artefacto sellado, el `NEEDED` no lo lleva un binario suelto sino **`usr/lib/libadwaita-1.so.0`**, que es la librería contra la que enlaza toda app GTK4/GNOME de la imagen. Sin proveedor del SONAME el loader falla en TODAS, no en una. Misma fuga de siempre —el `xz` canónico del corpus es `.a`, así que el rootfs lo resolvía contra el sysroot Alpine DEL LAB— y por eso invisible al store: el lab no entra en `hash_inputs`. La receta `-shared` existía sólo en `incoming-kde`. Se promueve al corpus con el mismo procedimiento que `bzip2-shared` el 2026-09-03, y comprobando lo mismo antes de creerlo: el hash es idéntico desde las dos rutas (b3:174289d5) ⇒ un solo artefacto y cero rebuilds. Si hubiera diferido, sería otra receta y no valdría la promoción. Va sólo en GNOME: en cosmic y sway ese soname lo pide únicamente `python3`, que es herramienta de build y no viaja en la imagen. GNOME pasa de 3 sonames sin proveedor a 2. De los dos que quedan, `libperl.so` es de `perl` (build), y `libreadline.so.8` lo pide `usr/bin/sqlite3` —el shell de la CLI, NO `libsqlite3.so.0`—, así que las apps están bien y lo roto es el comando. Queda anotado como verruga, no como rotura. |
||
|
|
92d47cfe1d |
mirada: las tres -shared que faltaban — el USB resolvía expat/zlib/libffi contra el lab
`scripts/vigia-sonames.py` sobre los cinco perfiles: `escritorio-mirada` era el único que aún tenía la fuga al sysroot del lab en componentes de RUNTIME. `mesa-swrast` y `wayland` salen con `NEEDED libexpat.so.1`, `libz.so.1` y `libffi.so.8`; las recetas canónicas de expat, zlib y libffi son `--disable-shared`; ningún artefacto del cierre publicaba esos SONAME. O sea que el rootfs los resolvía contra el Alpine DEL LAB y el USB sólo arrancaba donde hubiera Alpine debajo — que es peor que una dep faltante, porque el lab no entra en `hash_inputs` y el store no puede ni notarlo. Es la misma fuga que los otros cuatro perfiles cerraron el 2026-09-03. mirada quedó fuera porque su lista nació como copia literal del PKGS de `mirada-usb.sh` y nadie la revisó desde entonces. La dirección hoy está invertida —el script deriva su PKGS de `targets.py escritorio-mirada`— así que arreglarlo acá arregla el USB, y no hay dos listas que puedan divergir. Actualicé el comentario, que seguía diciendo «lift verbatim». Las tres viven en `corpus`, la misma cola del perfil, y ya están selladas ⇒ CERO rebuilds: sólo entran a la clausura. NO sumo `bzip2-shared` ni `ncurses-shared`, que sí llevan los otros perfiles: acá esos sonames los pide únicamente `python3`, que es herramienta de build y no viaja en la imagen. El vigía imprime siempre QUIÉN pide cada soname justamente para poder separar eso; sin esa columna su informe no se puede triar y uno acaba tapando ruido. Verificado: mirada pasa de 10 sonames sin proveedor a 7, y los 7 que quedan son todos de `python3` y `perl`. |
||
|
|
b606bf988b |
targets.toml: atuq entra en los cuatro escritorios
Hasta ahora atuq estaba sellado y NO estaba en ninguna imagen. Es exactamente la lección que este fichero ya aprendió con `foot` en el perfil de sway: una receta sellada que ningún perfil declara no la lleva nadie, y la métrica de clausura no lo puede ver porque mide lo DECLARADO. Va en los cuatro por el mismo motivo que `mpv` y desde el mismo sitio: vive en el CORPUS, y una receta resuelve sibling-first y después el catálogo padre, así que las cuatro colas lo alcanzan. Arrastra la cadena GTK3 en sus variantes `-shared`, y el comentario lo dice: con las estáticas, libgtk-3 y libgdk-3 se llevaban cada una su copia de pango/cairo y el navegador no llegaba a pintar. NOTA sobre la métrica: el grafo del hub sólo reporta base/cli/escritorio-mirada — los cuatro escritorios no aparecen en `by_profile` porque sus raíces viven en las colas. Es previo a este cambio y no lo introduce; queda anotado porque significa que esta declaración NO se ve todavía en `build-state.json`. |