**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
**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
Antes: dos reconstrucciones daban `libKF6SyntaxHighlighting.so` distintos —
8.800.310 bytes diferentes desde el offset 41, y hasta el tamaño cambiaba
(10.345.888 contra 10.345.840). Ahora: REPRODUCE.
Hash: 4c1638a0… → 3db73c19… (arrastra 4 dependientes directos, 5 transitivos).
── Dónde NO estaba, que también cuenta ────────────────────────────────────────────────
No era el indexer: `katehighlightingindexer` arma el índice con `QVariantMap`, que es
QMap y va ordenado. No eran los generadores de Perl: ninguno de los cuatro itera un
hash. Mirarlo antes ahorró parchear lo que no era.
── Dónde estaba: `data/generators/generate_jinja.py`, con tres dependencias del azar ──
1. `to_do.pop()` sobre un `set` saca un elemento ARBITRARIO. En `--dry-run` el orden de
los `print(out_file)` es lo que CMake recoge en `out_xmls`, y eso acaba siendo **el
orden de las entradas del `.qrc`** ⇒ el recurso compilado cambiaba de disposición
entera. Se toma el menor: mismo conjunto, orden fijo.
2. `version = str(round(time.time()))` hornea la HORA DEL BUILD en el XML generado. Se
honra `SOURCE_DATE_EPOCH`, que es la convención de reproducible-builds y que el
sandbox de takana ya fija (=1).
3. `os.listdir()` sin ordenar. Sólo importa si dos ficheros declaran el mismo lenguaje,
pero quitar la dependencia del readdir no cuesta nada.
── Medido en los dos sentidos ANTES de tocar la receta ────────────────────────────────
Corriendo el generador a mano, sin builds de por medio:
· antes — tres PYTHONHASHSEED distintos ⇒ TRES md5 distintos, y el orden salta a la
vista: `jinja-json, jinja-yaml, jinja-toml…` contra
`jinja-qml, jinja-dockerfile, jinja-typescript…`
· después — las mismas tres semillas ⇒ el MISMO md5
· generando de verdad con semillas Y momentos distintos ⇒ los 35 XML IDÉNTICOS,
con `version="1"`
· control negativo — sin `SOURCE_DATE_EPOCH` sigue poniendo la hora actual
(comprobado: coincidía con `date +%s` al segundo) ⇒ fuera del sandbox no cambia nada
Primer aviso de esta medición: mi primera comparación dio «idéntico con las tres
semillas» y era MENTIRA — el script salía con «Destination folder does not exist» y yo
comparaba md5 de un mensaje de error. Comparar salidas sin mirar que la herramienta
hiciera algo es inventarse un control.
Con -vga none la captura ya no tiene punto ciego, asi que un ✗ significa
«la ventana no esta». Clasificado el ultimo cuadro de las 10: 2 con la
pagina pintada, 1 con la ventana en blanco y 7 con SOLO el escritorio.
Las dos salidas explicaban los ✗ inflados de las series VGA=1, no el
fenomeno.
El MOZ_LOG separa los dos regimenes: las que tienen ventana escriben
952-1505 lineas hasta el apagado; las 7 sin ventana se cortan a los ~40s
y quedan 6 minutos de silencio, sin crash (WebRender arranca). Quedan
como hipotesis NO medida los frame callbacks que el compositor no manda.
Ademas: tasa-primera-pintura.sh nombra el log por VGA (la serie nueva
habia sobrescrito los crudos de la anterior).
ksvg, qqc2-desktop-style, kirigami-addons y libplasma, ya reconstruidos contra el
kirigami arreglado. REPRODUCEN 4 · DERIVA 0 · NO-DETERMINISMO 0.
No es adorno: los cuatro llevan QML, que es donde vivía la carrera del AOT. Que el
árbol reconstruido reproduzca es lo que cierra el arreglo — sellar de nuevo no
demuestra nada por sí solo.
El arreglo del no-determinismo de kirigami movió su ArtifactHash (41eea38e… →
a72b1626…) y con él el de todo lo que cuelga: 37 dependientes directos, 39
transitivos (entran `frameworkintegration` y `plasma5support` por vía indirecta).
Reconstruidas las 39 en orden topológico y con UN solo `flock -o` para toda la
tanda. Comprobado aparte del log: para las 39 existe `store/<hash-de-hoy>-<nombre>`.
Las gordas: kwin 3027s, plasma-workspace 1958s, okular 810s, kirigami-addons 816s.
⚠ A mitad de tanda, `kirigami-addons` y `kdf` murieron con «No space left on
device» — y es otra vez la trampa de siempre: NO eran recetas rotas. El disco de
`/mnt/vvv` estaba al 100%. Las dos reconstruyen bien con sitio libre.
Dos cosas se arreglaron a raíz de eso:
· `poda-fuentes.sh` con su suelo de 24 h liberaba CERO, porque los 30 árboles
eran todos de hoy — la lección de `cache-ci-no-envejece` otra vez: un guardián
calibrado a una escala que el dato nunca alcanza no protege. Con `--horas 1`
y luego `--horas 0` se recuperaron 14 G.
· la tanda ahora poda el árbol de CADA receta al terminarla (sólo el suyo: las
deps son compartidas, borrarlas en vuelo es el ADR 0012). Con eso
`work/sources` se mantuvo en 54-71 M durante el resto de la cascada en vez de
crecer sin freno.
El `never: 1` que aparece en el grafo KDE es `minga`, una receta nueva de otro
agente, sin perfil y ajena a esta cascada.
`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
Dos cosas antes de la medición: el MOZ_LOG de las que pintan y las que no termina IDÉNTICO (las dos
commitean WaylandBufferSHM de 1280x696), y el cruce de las 15 corridas está confundido — las 13 de
«perfil del usuario» salieron todas con VGA=1.
`screendump` de QMP fotografía UN dispositivo. Con VGA=1 hay dos, y hasta ahora se miraba uno. Con
`id=` en cada uno y capturando los dos en cada toma, en la misma corrida y el mismo instante:
+312s ██ PRIMERA PINTURA — 704456 px EN vga0
gpu0 = 962675 px de fondo + panel y dock (el escritorio, SIN navegador)
vga0 = 703766 px magenta (la ventana, con la página)
⇒ cosmic-comp maneja las DOS salidas y la ventana cae en una u otra. «Pinta 3 de 13» era cuántas
veces cayó en la pantalla que yo fotografiaba: una tasa de mi cámara, no del producto. El §6.10.ter
tiene la misma explicación (aquel arranque también traía `drm: card0 card1`).
Queda en pie, ya sin confundido: el navegador arranca, mapea y commitea SIEMPRE; cuando la ventana
está en la pantalla que se mira, se ve entre +184 y +312 s; el perfil no era la causa y cosmic-comp
nesteado tampoco.
⚠ Tercera vez en el día con la misma forma: un ✗ de una captura de UNA pantalla no dice «la ventana
no está», dice «no está en ESA» — igual que `mapped 1` no probaba que se viera. El estado anota ahora
en qué pantalla apareció y cuántas se fotografiaron, y el vigía NO cuenta las ciegas: las nombra.
Diez corridas idénticas con `scripts/cosmic/tasa-primera-pintura.sh` (--as-user, VGA=1, ventana 480 s,
captura cada 30 s), más las cinco anteriores, todas anotadas en docs/state/primera-pintura.json:
perfil del usuario 3 de 13 pintaron 184, 199, 204 s
perfil nuevo en tmpfs 2 de 2 197, 197 s
Dos cosas que la tasa dice y una anécdota no podía: cuando pinta, pinta SIEMPRE en la misma ventana
(184…204 s) y nunca a los 300, 400 ni 900 ⇒ no es una cola larga, son DOS REGÍMENES —o sale a los
~200 s o no sale—; y con el perfil del usuario falla ~3 de cada 4, mientras que con perfil nuevo en
tmpfs no falló (2 de 2, pocas corridas para afirmar que nunca, suficientes para saber dónde mirar:
qué hace el primer arranque del perfil que el perfil ya hecho no hace).
⚠ Condición de la medida, parte del número: las diez salieron con el anfitrión a load ~10 (tanda de
KDE de otro agente + una VM de otra sesión, en 4 cores). El número es un PISO, no una constante.
⚠ Y una trampa del arnés medida en las corridas 8–10: con esa carga el guest tarda más de 420 s en
llegar al shell y los `esperar` vencen. No las invalida —se verificó una por una que el navegador se
lanzó y dejó su MOZ_LOG de 1200–5000 líneas— pero una corrida abortada de verdad se ve casi igual:
por eso el guion cuenta las abortadas APARTE en vez de sumarlas a los fallos.
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
Antes: dos reconstrucciones con las mismas entradas y el mismo lab daban artefactos
distintos. Ahora: `why-differs` da 464 entradas idénticas · 0 divergen.
Hash: 41eea38e… → a72b1626… (arrastra 37 dependientes directos, 39 transitivos).
── CAUSA 1: el AOT de QML compilaba un conjunto DISTINTO de funciones cada vez ─────────
`libKirigamiTemplates.so` cambiaba de tamaño (5.737.344 vs 5.795.072) y difería en 177
símbolos locales, todos `QmlCacheGeneratedCode…__invoke` y concentrados en CUATRO ficheros:
InlineMessage, LinkButton, NavigationTabBar y Badge.
Y esos cuatro son justo los que importan módulos QML HERMANOS del propio proyecto
(`org.kde.kirigami.platform`, `.primitives`, `.controls`). `qmlcachegen` sólo compila a AOT
lo que puede resolver de tipos, y los tipos del hermano salen de su `.qmltypes`, que produce
otro objetivo del mismo build. `src/templates/CMakeLists.txt` no declara `DEPENDENCIES`
ninguna ⇒ con ninja en paralelo es una CARRERA.
Control que sostiene el diagnóstico: `Chip` y `Heading`, del mismo directorio, no divergen —
y `kirigami-addons` y `kquickcharts`, que también llevan QML, reproducen. No es «QML es
no-determinista».
Arreglo: `-j1` en el compile. Orden topológico fijo ⇒ el cachegen ve siempre lo mismo.
NO es throttling (eso va por nice/taskset justamente para no re-hashear): es corrección, y
el re-hash es el precio que se paga a propósito.
Descartadas, con motivo: `-DQT_QML_NO_CACHEGEN=ON` apaga también el bytecode precompilado
(coste de arranque en el escritorio); `--only-bytecode` sería lo quirúrgico pero
`QT_QMLCACHEGEN_ARGUMENTS` es propiedad de objetivo y NO es INHERITED, así que no hay forma
de ponerla desde la línea de órdenes.
── CAUSA 2: el .tar.bz2 de plantillas se armaba sin orden ──────────────────────────────
`kde_package_app_templates` de ECM tiene camino reproducible —`--sort=name --mtime=@
SOURCE_DATE_EPOCH --numeric-owner --owner=0 --group=0`— pero detrás de `if(GNU_TAR_FOUND)`,
que exige que `tar --version` diga «GNU tar». El rootfs del lab es Alpine: `tar` es BUSYBOX
⇒ caía al respaldo `cmake -E tar cvfj`, que ni ordena ni normaliza. El orden lo ponía readdir.
Arreglo: `tar` (GNU tar 1.35, ya en el corpus) entra en `[deps] build`.
Comprobado mirando el artefacto, no deducido:
drwxr-xr-x 0/0 1970-01-01 00:00 ./
-rw-r--r-- 0/0 1970-01-01 00:00 ./CMakeLists.txt
-rw-r--r-- 0/0 1970-01-01 00:00 ./LICENSES/BSD-3-Clause.txt
owner 0/0, mtime al epoch (SOURCE_DATE_EPOCH=1 lo fija el sandbox) y la lista ORDENADA.
`scripts/cosmic/atuq-en-imagen.py` ya no contesta «pinta / no pinta» sino CUÁNTO TARDA: mide cada
captura, imprime `+Ns ██ PRIMERA PINTURA`, acepta `--budget` (def. 420 s) y `--until-paint`, y sale
≠0 si no pintó o si se pasó del presupuesto. El magenta se cuenta CON TOLERANCIA porque `cosmic-idle`
atenúa la pantalla al 46 % a los ~+590 s y un contador por color exacto lo lee como «desapareció».
El número se publica en `docs/state/primera-pintura.json` y `scripts/vigia-imagen.py` lo informa como
sexto dato, marcado como «no lo mide este vigía» (arrancar la imagen son ~15 min sin KVM). Se ACUMULA
una entrada por corrida, no se pisa, y el vigía informa la tasa:
⚠ pintaron 3 de 5 corridas · primera pintura +197…+204s
⚠ Y eso corrige lo que publiqué hace una hora. Con cinco corridas sobre la MISMA imagen: pintó en
tres (+197, +197, +204 s) y NO pintó en dos —una con 900 s de observación—, siendo la 4 y la 5 el
mismo comando. El fallo es INTERMITENTE: ni «nunca pinta» ni «sólo tardaba».
El argumento que me llevó a «tarda» era el MOZ_LOG (`mapped 1` + «has buffer» + WaylandBufferSHM
commiteado). La corrida 5 lo refutó: commiteaba cuadros desde ≤+377 s con la pantalla vacía a los
900 s. Que el cliente se crea visible NO es evidencia de que se vea; la evidencia es el píxel. Queda
escrito en el vigía, al lado de la tasa, para que no se vuelva a usar como prueba.
De paso, las flags nuevas nacen en inglés (CLAUDE.md §4): --as-user, --wait, --budget, --until-paint,
--only-cosmic; los mensajes siguen en castellano.
kio kcolorscheme kdeclarative krunner kwayland layer-shell-qt kquickimageeditor
kcharselect kruler kdf kfind.
REPRODUCEN: 11 · DERIVA: 0 · NO-DETERMINISMO: 0 · no construyeron: 0
`kio` entra acá y es de las gordas del árbol KDE: reproduce bit a bit.
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
Hub, KF6 tanda 4: REPRODUCEN 9 · DERIVA 1 · NO-DETERMINISMO 2 · no construyeron 0
Worker, Rust: REPRODUCEN 13 · DERIVA 2 · NO-DETERMINISMO 0
El libro pasa de 104 a 207 entradas en la jornada.
── Los dos que divergen, y por qué importan ──────────────────────────────────────────
`kirigami` — está en `escritorio-kde` y tiene **37 dependientes**. Dos sitios:
· libKirigamiTemplates.so: los símbolos del AOT de QML llevan un contador por fichero
que cambia de corrida a corrida (`…LinkButton_qml3$_28/_38/_58` en A contra
`…Badge_qml3$_88/_98/_108` en B) ⇒ el orden en que se compilan los .qml no está fijado.
· kdevappwizard/templates/kirigami6.tar.bz2: difiere desde el byte 0xa, o sea desde el
primer bloque comprimido — un tar armado sin orden estable.
`syntax-highlighting` — también en `escritorio-kde`. libKF6SyntaxHighlighting.so difiere
en 8.800.310 bytes desde el offset 41 y hasta **el tamaño cambia** (10.345.888 contra
10.345.840). Las cadenas que difieren son datos comprimidos ⇒ es el recurso generado con
las definiciones de sintaxis, otra vez orden de entrada.
Los tres de hoy (con `gocryptfs`) son la MISMA clase: **recurso generado cuyo orden de
entrada nadie fija**. No es el compilador ni el lab: es que la receta deja que el orden lo
decida un `readdir` o el planificador.
── Lo que NO se hace acá, y por qué ──────────────────────────────────────────────────
Arreglarlo toca la receta ⇒ re-hash ⇒ reconstruir kirigami y sus 37 dependientes. Eso es
una decisión de campaña con su coste, no algo que se mete de paso en una verificación.
Los cuatro ejemplares quedan en `store/.divergen/` para poder mirarlos.
── Control que sostiene el hallazgo ──────────────────────────────────────────────────
`kirigami-addons` y `kquickcharts` TAMBIÉN llevan QML y reproducen. O sea que no es «QML
es no-determinista»: es algo propio de esas dos recetas.
Hub, KDE Frameworks tanda 3: kglobalaccel kpackage kconfigwidgets kiconthemes
ktextwidgets kxmlgui kparts kcmutils knotifications kdeclarative ksvg kwallet.
REPRODUCEN: 12 · DERIVA: 0 · NO-DETERMINISMO: 0 · no construyeron: 0
Worker (ya con `go` arreglado), familia Go:
REPRODUCEN: 15 · DERIVA: 1 · NO-DETERMINISMO: 1 · no construyeron: 0
· `checkmake` — la que antes «no construía» — REPRODUCE. Cierra el arreglo del enlace
de `go`: no estaba rota, faltaba el binario en el PATH del host.
· `cilium-cli` DERIVA: el artefacto guardado era viejo; las dos reconstrucciones
coinciden entre sí ⇒ se movió el mundo, no la receta. No es un fallo.
· `gocryptfs` NO-DETERMINISMO, y es el primero de verdad del frente Go.
La causa de gocryptfs, con los dos ejemplares guardados en `store/.divergen/`:
usr/bin/gocryptfs ELF, difieren [.text .rodata .data .debug_*]
· .debug_str sólo en A: …/.gotmp/go-build3432240771/b141
· .debug_str sólo en B: …/.gotmp/go-build2186295412/b141
El directorio temporal `go-build<aleatorio>` queda horneado en el DWARF. Hipótesis a
comprobar, no conclusión: sólo pasa con `cgo = true`, porque cgo genera fuentes C en ese
temporal y su ruta real entra en la info de depuración; con CGO off no hay fuentes
generadas y las 15 restantes reproducen. En el corpus hay exactamente cuatro recetas Go
con cgo: gitea, gocryptfs, sq y usql — ninguna verificada todavía. Se miden antes de
afirmar nada.
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
Tanda 2 de la familia KDE Frameworks (hub): kbookmarks kcolorscheme kcompletion
kdnssd kholidays kunitconversion kauth kpty solid sonnet prison kservice.
REPRODUCEN: 12 · DERIVA: 0 · NO-DETERMINISMO: 0 · no construyeron: 0
Y aparte: el worker tenía su propio libro (`work/repro-worker.tsv`, fuera de la siembra)
con 13 verificaciones del 2026-09-05 que nunca volvieron al hub. Cobertura medida y
perdida. Importadas 10.
Las otras 3 NO se importan, y el motivo es el que justifica que la clave del libro sea
(receta, ArtifactHash) y no la receta sola: su hash cambió desde entonces, así que la
medición ya no habla del artefacto de hoy.
· atuq, bzip2 — reproducían, pero a un hash superado
· aichat — decía NO-DETERMINISMO a `c40a15bd…`; el hash vigente es `7a515b0a…`
y a ÉSE el propio worker le midió «reproduce». Importar por nombre
habría arrastrado un no-determinismo que ya no existe.
Barrido por FAMILIA (KDE Frameworks tier 1, todos CMake), que es lo que hace
legible el censo: si el fallo fuera del toolchain se concentraría acá y saltaría
a la vista. karchive kcodecs kconfig kdbusaddons kguiaddons ki18n kidletime
kitemmodels kplotting kwidgetsaddons kwindowsystem kcrash.
REPRODUCEN: 12 · DERIVA: 0 · NO-DETERMINISMO: 0 · no construyeron: 0
Los 12 además CONSTRUYEN hoy, que es el otro hallazgo del verificador: `sealed`
sólo dice que alguien los construyó alguna vez con algún lab, y el lab rueda.
Estaba sólo en `recipes/incoming-wlr/`, o sea inalcanzable desde las otras
cuatro colas. `cliphist` vive en el CORPUS y la pide en `[deps] run`, así que
corpus, gnome, kde y cosmic la reportaban como `orphan_deps: ["wl-clipboard"]`.
No era cosmético: `escritorio-cosmic` declara `cliphist` de raíz, y la imagen
se habría armado con el demonio de historial y SIN `wl-copy`/`wl-paste` — el
modo de fallo silencioso que la propia receta de cliphist advierte (el binario
corre y no hace nada). La clausura no lo podía ver: decía `falta=0` en los
cinco perfiles, porque una dep que no resuelve no se cuenta, desaparece.
Solución = la que el catálogo ya usó con `mpv` y `atuq`: subirla al corpus, de
donde las colas la alcanzan (sibling-first y después el catálogo padre).
Medido antes de mover: ninguna de sus 9 deps tenía variante hermana en
`incoming-wlr`, así que el ArtifactHash no podía cambiar — y no cambió
(`b3:fc2ab7b7…` a los dos lados, comprobado con `takana hash`). Cero re-sellos.
Tras regenerar los cinco grafos: `orphan_deps: []` en los cinco, y la clausura
de `escritorio-cosmic` pasa de 273 a 274, sellada.