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.
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.
Decisión del usuario. El motor (199 M) y el modelo de chat (1,04 GiB) van en las cuatro imágenes
porque la barra lateral está en las cuatro; el de embeddings (610 MiB) no, porque es para una función
—preguntarle al archivo por significado— que no todo el mundo usa.
Y «opcional» no significa «lo armás vos»: el modelo ya es receta del corpus, así que la maquinaria de
la Etapa F lo vuelve paquete sin trabajo extra. Publicado en `dist/repo` con su `expected_hash`
anclado, junto con las deps que `install` necesita para reproducirlo — `llama-cpp` faltaba en el
catálogo, y sin ella el paquete no se puede instalar aunque exista. De paso entraron nftables, libmnl
y libnftnl, que tampoco estaban.
⚠ Tres de esos paquetes se habían publicado con el ancla de sanidad por default (`/usr/bin/<nombre>`),
que en una librería o en un modelo NO EXISTE: `install` habría fallado al verificar. Re-empaquetadas
con su ancla de verdad (`/usr/bin/llama-server`, `/usr/sbin/nft`, `/usr/lib/libmnl.so.0`…).
Lo que hace que esto sea opcional de verdad es una línea: la receta NO está declarada en ningún
perfil. Está escrito en el SDD para que nadie lo «arregle» agregándola.
Y la mitad que separa «opcional» de «invisible»: con el modelo ausente el host ya no dice sólo «no
está» sino cómo conseguirlo — «es una descarga opcional — instalalo con `takana install
ia-modelo-embeddings`». Medido en el control negativo del guardián.
El barrido con `verificar-repro.sh` sobre las 15 recetas CMake baratas del corpus dio
**5 que no construyen hoy**. Éstas son las dos cuyo arreglo no cuesta nada (radio 0 y 3 rebuilds).
Diagnosticada `json-c` en vez de suponerla: apartando su artefacto y reconstruyendo con la salida
capturada, muere con el mismo `Error running link command: Segmentation fault` que `dwarves` y que
los `protoc-gen-upb*` de protobuf. Un `-DCMAKE_LINK_DEPENDS_USE_LINKER=OFF` en cada una y listo — el
`lld` de zig 0.16.0 segfaultea con el `--dependency-file` que CMake ≥3.27 emite.
⚠ **Lo que importa de este commit no son las dos recetas: es que el fallo no era un caso aislado.**
Cuando apareció en protobuf lo esquivé con `compiler = "gcc"` creyendo que era cosa de esos plugins.
Ya van CINCO proyectos sin relación entre sí con el mismo crash, y otros tres medidos y pendientes.
`crun` cayó a deuda (json-c es dep suya) y se reconstruyó: sigue siendo estático con 0 NEEDED y
**vuelve a arrancar un contenedor de verdad** con el busybox del corpus como rootfs. Las tres
REPRODUCEN bit a bit.
Queda planteado con el número delante, sin arrancarlo: los otros tres del censo son
`libtiff-shared` (12 rebuilds), `libjpeg-turbo` (17) y `libjpeg-turbo-shared` (23) — ~52 en total y
tocan los stacks gráficos de los escritorios. Y el censo cubrió 15 de las 23 CMake del corpus y
NINGUNA de las ~130 de las colas: el número real de rotas es mayor que 5.
Verificado en OVMF con el kernel linux-metal-kexec recién construido
(b3:965acc37…, CONFIG_KEXEC_FILE=y confirmado en el .config sellado):
BdsDxe: starting Boot0002 "UEFI Misc Device" ← el firmware, UNA vez
KEXEC-PRUEBA: kernel = 6.16.12
✓ kernel cargado en memoria
⚡ saltando — el userspace muere ACÁ. Esto no vuelve.
KEXEC-PRUEBA: kernel = 7.1.2
KEXEC-PRUEBA-LLEGADA: este kernel NO lo arrancó el firmware
La prueba no es que aparezca el 7.1.2: es que "BdsDxe: starting" aparece
UNA SOLA VEZ. Dos kernels distintos corrieron y el firmware sólo
intervino en el primero. Y el 7.1.2 vive dentro del initramfs, sin
entrada de arranque, así que el firmware no podría haberlo lanzado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
Contesta la pregunta que ni el grafo ni `vigia-sonames` contestan: no «¿está
sellado?» ni «¿arranca el binario?» sino **«¿hay alguien que lo LEVANTE?»**. Un
demonio puede estar sellado, con contenido, con todos sus sonames resueltos, y
que ninguna imagen lo arranque nunca.
Va al latido por la lección que `vigia-sonames` ya dejó escrita doce líneas más
arriba —un vigía que hay que acordarse de invocar no se distingue de no tenerlo—
y con más motivo: su entrada son DOS ficheros que cambian por separado (las
recetas y `targets.toml`), así que la divergencia entre «declarado» y
«habilitado» aparece sola, sin que nadie toque el vigía.
Corre PRIMERO su propio `--selftest`: si el guardián está roto, su silencio no
es una respuesta.
Primera lectura, ya en el repo: 0 errores y 18 avisos. El más ruidoso es
`dbus-system`, que viaja en SEIS perfiles y sólo GNOME lo arranca.
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.
Buscando cómo escribir la receta de `tawasuyu` apareció que su ÚNICO remoto es el gitea de gioser
(`ssh://gitea@git.tawasuyu.net:2345/…`, y ese nombre resuelve a 204.168.193.248 = gioser). Al mirar
el resto: **28 repositorios con remoto en esta máquina, 26 SIN NINGUNA copia fuera**. Sólo `takana` y
`llimphi-standalone` tienen espejo externo.
Apagar el origen no borra unos servicios: borra EL CÓDIGO CON EL QUE SE VOLVERÍAN A CONSTRUIR, y los
clones de trabajo están en el mismo disco que también muere. Entre los 26 está `tawasuyu`, que
produce 10 de los 13 binarios que nadie más provee — la dependencia circular completa.
No se ve desde ninguna otra parte del censo: un repo no es un proceso, ni un puerto, ni un dominio.
Se descubre cuando ya no hay de dónde sacarlo.
· `censar.py` lo mira ahora (`[[repo]]`), y reporta NO «tiene remoto» sino si alguno de sus remotos
NO es esta máquina: un remoto que apunta afuera es la prueba de que el código sobrevive. La lista
de «esta máquina» sale del censo mismo (sus IPs + los dominios que sirve), no de nombres cableados.
· `planear.py` lo emite como PASO 1, por delante del rescate de binarios: aquello pierde un servicio,
esto pierde la posibilidad de reconstruirlo. El arreglo ya está escrito en el repo —takana usa
`pushurl` doble por `scripts/espejo-setup.sh`.
Y una corrección de algo que dije antes: la perilla por receta que vi en `recipe.rs` es `strip_debug`,
no una versión de rust. NO existe `rust_version`; sólo `zig_version`. Pinear rustc por receta para
honrar el `rust-toolchain.toml` de tawasuyu (1.96.0, contra 1.97.0 del lab) sería código nuevo en
takana-core, no una opción ya disponible.
De paso, medido el subárbol de los diez binarios por separado: cuatro (`willay-daemon`,
`sandokan-seguridad-core`, `pacha-secretos`, `tupu-cli`, entre 84 y 153 deps) NO arrastran
criptografía en C; cuatro traen `ring` y dos `aws-lc-sys`. O sea que hay un escalón por donde
empezar sin pelear con cmake.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
`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.
De los 13 servicios cuyo binario NADIE provee —los que se pierden al apagar gioser— DIEZ salen del
mismo repositorio (`tawasuyu`, hoy `bad13117c`): matilda, pacha (paquete `pacha-cli`),
pacha-secretos, sandokan-watch (`sandokan-seguridad-core`), shuma-daemon, shuma-gateway, tejido, tupu
(`tupu-cli`), willay-crosscheck (`willay-cruce`) y willay-daemon. Los otros tres son ajenos
(act_runner, adb, y el `puerta-…` de target/debug). Una receta cubre diez servicios.
**El tamaño real es una quinta parte del aparente.** El Cargo.lock del workspace tiene 2823 crates,
pero eso incluye su stack gráfico, audio y Android; el subárbol que esos diez binarios necesitan son
524. Y sólo CINCO traen C: `aws-lc-sys` y `ring` (criptografía, compilan C/asm y piden cmake) más
`dirs-sys`, `inotify-sys` y `netlink-sys`, que son bindings puros sin librería externa.
⚠ **Lo que hay que decidir antes de escribirla.** `tawasuyu/rust-toolchain.toml` fija 1.96.0 y lo
argumenta en el propio fichero: una distro que promete builds deterministas y atestación firmada no
puede tener el compilador flotando. El lab de takana trae 1.97.0 y es RODANTE por diseño —
`lab-toolchain.lock` detecta la deriva, no la evita. Una receta takana compilaría con un rustc
distinto del que tawasuyu exige: el binario NO sería el mismo que corre, y rompería el contrato de
atestación del otro frente. Tres salidas, y es decisión, no trámite: pinear 1.96.0 para esta receta
(como las 84 que pinean zig 0.13.0), mover el pin de tawasuyu, o llevar los binarios tal cual
sabiendo que no reproducen.
Y no se construye en el origen: 4 núcleos y ~2 G disponibles. Eso va al worker dev.gioser.net.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
Ayer estas dos huyeron a `compiler = "gcc"` para esquivar un crash del linker en los tres
`protoc-gen-upb*`. **Esquivé el fallo sin conocer su causa, y salió caro:** al mover sólo protobuf, el
link murió con `undefined reference to std::__1::basic_string<…>` —el `__1` de libc++— porque abseil
seguía en zig, así que hubo que arrastrar las dos fuera del toolchain por defecto.
La causa apareció al día siguiente en `dwarves`, bisecando la línea de enlace real:
tal cual → exit 139 (SIGSEGV)
quitando `-static` → exit 139 ⇒ no es el enlace estático
quitando `--dependency-file` → **exit 0** ⇒ ES ESO
El `lld` de zig 0.16.0 segfaultea con el `--dependency-file` que CMake ≥3.27 mete en la línea de
enlace. Con `-DCMAKE_LINK_DEPENDS_USE_LINKER=OFF` **los cuatro binarios de protobuf enlazan con zig**,
incluidos los tres plugins que se caían.
Esto es, además, la validación de la causa raíz en un proyecto INDEPENDIENTE de dwarves: dos
proyectos sin relación, mismo síntoma, misma perilla, los dos arreglados.
Con el escape se va también el `-static-libstdc++`, que sólo hacía falta porque g++ enlaza contra la
libstdc++ de GNU. Comprobado sobre el artefacto: el único NEEDED de `protoc` es `libc.so`, que provee
`musl-shared`.
Verificado de punta a punta, no por el código de salida: `protoc --version` → `libprotoc 36.1`, y un
`.proto` compila a `m.pb.h`/`m.pb.cc` **y** a `p/m.pb.go` pasando por `protoc-gen-go` — o sea que la
cadena plugin↔driver que abrí anteayer sigue entera. Las dos REPRODUCEN bit a bit.
Se deja escrito el camino completo en las recetas, diagnóstico corto incluido, porque la lección no
es la perilla: es que **un escape que funciona sin explicar el fallo se paga después**, y acá se pagó
con dos recetas fuera del toolchain por defecto y un choque de runtimes de C++ que sólo apareció
porque el escape era parcial.
El diseño completo del hueco: qué declara el PAQUETE (`[[service]]`, hecho) y
qué decide el PERFIL (si arranca, pendiente), que es la misma partición que
systemd hace entre [Service] e [Install] y que `arje-absorb` respeta al absorber
sólo lo habilitado.
Deja escritas las dos cosas que cuestan caro si se descubren después:
1. El SDD 06 decía «el init (arje) lee ese árbol» de /etc/hammer/init.d/*.rule.
Es FALSO: el único uso de INIT_RULES_DIR en el repo es escribirlo. Había TRES
convenciones de dónde vive un servicio y ninguna se tocaba con las otras
(más una cuarta en `query service:`). Canónicos son los de arje —genesis de
la seed y cards.d/—; la mutación del .swm queda derogada o reapuntada, y la
afirmación falsa queda marcada en su propio doc.
2. La trampa para el emisor: el sidecar .hammer/recipe.toml NO entra al
ArtifactHash, así que el openssh ya sellado no lleva el bloque y, con el hash
sin mover, NUNCA se reconstruye solo. Derivar la seed del sidecar hoy daría
un producto SIN sshd, en silencio. Por eso product_seed_card() sigue usando
la constante a propósito, y re-sellar es una unidad aparte CON control de
reproducibilidad — openssh no está certificado como reproducible.
Medido hoy: `ia-modelo-embeddings` dio REPRODUCE y `ia-modelo-chat` «no construyó», y la ÚNICA
diferencia entre las dos era que el tar de la primera seguía en `work/tarballs` y el de la segunda lo
había borrado yo liberando disco. Sin caché, el build sale a buscar la fuente a una URL `.invalid`
—que es lo que el ADR 0013 pone a propósito cuando el objeto vive sólo en nuestro mirror— y muere con
`Could not resolve host`.
O sea que el gate informaba «no construyó» para `firefox-pgo-profile` y los dos modelos de IA según
qué hubiera en el caché local. Un guardián cuyo veredicto depende de eso no es un guardián.
Ahora hace `source` de `scripts/fuentes/mirror-env.sh` si existe. Aditivo, como manda el ADR 0013: si
el fichero no está, todo sigue igual que antes.
Con eso, ia-modelo-chat: REPRODUCE.