Commit Graph
100 Commits
Author SHA1 Message Date
SergioandClaude Opus 5 c7c2337559 el informe del agente de adentro: los cinco defectos y las tres trampas
Primer informe hecho DESDE la jaula, y vale más que cualquier inspección desde el anfitrión porque
son las cosas con las que se choca al usarla. Queda registrado qué se arregló y —lo importante—
DÓNDE vive cada arreglo: 1 y 2 en el upper, que es caché descartable, y por eso hay un guion que los
repone tras cada recreate; 3 y 5 en el manifiesto; 4 en el worker.

Del 4: se autorizó la clave de la caja, no la de root — credencial distinta y revocable. Del 5:
takana NO se compila adentro; el binario es el musl del store, y lo que fallaba es que el
target/release del anfitrión enlaza a /usr/bin/takana, que adentro es el de Arch.

Y las tres trampas de orientación que el agente reportó: el store real es /store y los runbooks dicen
./store porque están escritos para el hub; hay dos checkouts y no son intercambiables; y el .fleet
ausente no es defecto, lo repone la cosecha.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:06:58 +00:00
SergioandClaude Opus 5 e5e723b670 lo que el agente arregló DENTRO de la jaula, escrito afuera para que sobreviva al recreate
El agente que trabaja enjaulado encontró y arregló tres defectos a mano, pero viven en el `upper`,
que por el D3 del ADR 0015 es caché descartable: al primer `recreate` se pierden. Esto es esa mano,
escrita.

1. No hay entrada en /etc/passwd para el uid de adentro: ssh y git mueren con «No user exists for uid
   1001» y ni intentan conectar. Pasa porque la instancia de persona va con root = false —si no, el
   CLI se ve uid 0 y rechaza --dangerously-skip-permissions— y ese uid no existe en la imagen Arch.
2. /etc/ssh/ssh_config.d es de la capa inferior y su dueño cae fuera del mapa (nobody), así que ssh
   ABORTA con «Bad owner or permissions»; no degrada, aborta. El directorio no se puede mover (EXDEV
   en overlay), así que se redirige el Include a una copia propia.
3. known_hosts se siembra desde el anfitrión, que es lo que evita el StrictHostKeyChecking contra el
   worker la primera vez —y el ~/.ssh de adentro ya pasó a rw en el manifiesto, para que ssh pueda
   escribirlo cuando aparezca un host nuevo—.

Escribe en el upper como la PERSONA, no como root: si no, le dejaría ficheros que ella no puede tocar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:06:21 +00:00
Sergio c4d8b68692 estado: cosecha granja 2026-09-18T16:01:37Z — avance del árbol KDE 2026-09-18 16:01:37 +00:00
SergioandClaude Opus 5 4be4680310 atuq: la bóveda ENTREGA una contraseña, y sólo cuando alguien dice que sí — las seis etapas en verde
A ✓ sin la app dueña, el host contesta locked:true — y con ok:true, que es la trampa
    B ✓ con la app, vault.status dice ABIERTA: el socket sube en 1-2 s
    C ✓ el diálogo ABRE en el compositor y se lo CONTESTA: «1» ⇒ yes, «2» ⇒ no
    D ✓ vault.save con consentimiento real guarda, y vault.match la encuentra SIN contraseña
    E ✓ el navegador real, sobre una página servida por HTTP: la contraseña llega al CAMPO
    F ✓ contestando que NO, al campo no llega NADA

Dos corridas seguidas en verde, 6 min cada una. La unidad 12 del SDD queda CERRADA.

La E es la promesa entera del §6 y sola no probaría nada —una bóveda que entrega siempre se ve
idéntica a una que entrega con permiso—; por eso la F corre lo mismo diciendo que no. Y lo que se
mide no es lo que el host contesta sino lo que hay EN EL CAMPO: la página delata al servidor lo que
le pongan. Es la lección del §7.quater aplicada al otro extremo.

Tres fallos del ARNÉS, los tres disfrazados de fallo del producto:
· `wait` a secas esperaba también a la app de la bóveda ⇒ la jaula se comía su timeout y a la app la
  mataba un KILL, y con ella lo recién guardado (sled no alcanzaba a volcar). La etapa siguiente no
  encontraba la credencial: «la bóveda no guarda», el diagnóstico contrario al verdadero;
· el diálogo NO responde al teclado mientras la app de la bóveda inicializa su GPU — dos llimphi
  arrancando a la vez sobre render por software se pisan: doce teclas sin efecto y `denied` por
  timeout con la ventana abierta. Ahora se espera a que la app PINTE (12-13 s), no a su socket;
· una sola tecla no alcanza y el borde se mueve: el contestador insiste hasta que la ventana se va,
  que es la única señal que no depende de adivinar cuánto tarda.

El guardián entra ENTERO a la suite (era `--hasta A`): 20 en verde, ~38 min.

Lo que costó llegar: el guardián se escribió para medir una función y destapó tres piezas rotas,
NINGUNA en atuq — el dueño y el diálogo no estaban en el corpus, ninguna ventana llimphi podía pintar
sin Vulkan, y el dueño atendía de a un cliente. Las tres se veían igual desde el navegador: una
bóveda que no ofrece nada, sin un solo error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:57:20 +00:00
SergioandClaude Opus 5 057332363a /usr/bin/claude era una COPIA del guion, por eso el arreglo no llegaba
El usuario reportó dos veces que claude seguía abriendo en /opt/takana con el arreglo ya pusheado y
ya tirado en la caja. La causa: `claude` en el PATH no era este fichero sino una COPIA suya en
/usr/bin, hecha en la instalación y congelada con el destino viejo cableado. Se vio en su propio
error anterior —«/usr/bin/claude: line 23»—, que apuntaba a un fichero distinto del que yo editaba.

Ahora /usr/bin/claude es un ENLACE a este guion, así que no puede volver a derivar, y queda dicho en
la cabecera. Comprobado por traza: desde /work/sergio/takana pasa /work/sergio/takana y desde
/home/sergio pasa /home/sergio. Barrido: no hay otras copias instaladas de scripts/servidor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:41:37 +00:00
SergioandClaude Opus 5 5999aeda45 borré /usr/bin/id probando el setuid — qué pasó y por qué no se prueba así
Para saber si el bit setuid elevaba copié busybox sobre /usr/bin/id (busybox elige el applet por
argv[0] y necesitaba un nombre válido) y después borré «mis» copias de prueba. Una era el `id` del
sistema, y no quedaba otro: /bin/id tampoco existía. El usuario se lo encontró de frente: «claude:
line 23: id: not found» y «-bash: /usr/bin/id: No such file or directory».

Restaurado como sus hermanos de identidad —whoami y groups apuntan a ../../bin/busybox— y comprobado
con id de root, de sergio y con el `id -un` que usa el lanzador. Barrido: 0 enlaces colgados en
/bin, /usr/bin, /sbin y /usr/sbin.

La lección es de método: para probar una propiedad del sistema no se usa un nombre que el sistema ya
ocupa. La sonda correcta fue la que terminé escribiendo —cinco líneas de C a un nombre inventado—.
Y es la misma familia que el /etc/shadow del §6.51: la caja no avisa cuando le falta algo esencial,
se entera el que lo usa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:39:14 +00:00
SergioandClaude Opus 5 9db1845f22 claude abre DONDE ESTÁS PARADO, no en /opt/takana
Reportado tal cual: «entro al repo y ejecuto claude, pero claude sigue estando en /opt/takana y no me
agarra el repo». Era literal: el lanzador tenía el destino cableado a /opt/takana, así que desde
/work/sergio/takana abría el árbol del root — y encima con la memoria de ESE proyecto, porque la
memoria del agente se indexa por ruta de trabajo.

Ahora el defecto es $PWD; PROY sigue ganando cuando se lo pasa a propósito, y si la ruta no existe
cae a /opt/takana. Probado con un doble del binario en los tres casos.

Y el `cd` de adentro ahora falla ruidosamente: la jaula sólo monta lo concedido, así que desde una
ruta no concedida no hay nada que abrir. Antes eso se lo tragaba un `&&` y el agente arrancaba en `/`
sin decir por qué.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:33:54 +00:00
Sergio e04089b222 estado: cosecha granja 2026-09-18T15:31:57Z — avance del árbol KDE 2026-09-18 15:31:58 +00:00
SergioandClaude Opus 5 f32f2f3543 el mapeo por rango: se intentó cerrar y NO alcanzó — queda dicho dónde para
Se hicieron las dos cosas obvias: rango para sergio en /etc/subuid y /etc/subgid, y los ayudantes
newuidmap/newgidmap en setuid root y además con capacidades (cap_setuid=ep). El error SE MOVIÓ —ya no
dice «no hay rango para sergio»— pero sigue sin mapear: «newuidmap: open of uid_map failed:
Permission denied».

Lo descartado, medido: el setuid SÍ eleva en esta caja (sonda en C compilada con el cc nuevo:
uid=1001 euid=0), / no está nosuid, y el uid_map del proceso es suyo (-rw-r--r-- sergio:sergio). O
sea que no es ni el rango que faltaba ni el bit que no tomaba. La causa de fondo queda SIN
DETERMINAR, y se escribe así en vez de inventarla.

De paso, un detalle que cuesta un rato si no se sabe: `chown` BORRA el bit setuid, así que hay que
chownear primero y ponerlo después. La primera sonda salió sin bit por ese orden y parecía que el
setuid no funcionaba en la caja.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:25:54 +00:00
SergioandClaude Opus 5 ff871f4a07 dar de alta una persona: casi es adduser, y lo que falta es lo que tumbó la caja
Respuesta medida a «¿agregar un usuario es más complicado que un simple adduser?». `adduser` está —es
el de busybox— y crea la cuenta. Lo que no alcanza es DÓNDE queda la contraseña: esta caja NO USA
/etc/shadow, el hash vive en el campo 2 de /etc/passwd, y el shadow que hubo está aparcado como
/etc/shadow.el-que-bloqueo-root. El nombre no es broma: crear un /etc/shadow con las cuentas
bloqueadas dejó a sshd sin aceptar ni la clave de root, y la caja se quedó sin administración con los
ocho dominios caídos ~20 minutos. Un adduser a secas puede crear ese fichero sin avisar.

Así que el guion crea la cuenta, deja el hash donde el login lo lee, y APARTA cualquier /etc/shadow
que el alta haya creado donde no había. El control es el que importa: fotografía quién podía entrar
ANTES y falla si alguien perdió su contraseña.

Y hace lo que hace falta para tener poder de verdad: sudoers.d (el sudo de acá sí es setuid; doas no
funciona), rango en /etc/sub{u,g}id con los ayudantes newuidmap/newgidmap en setuid —sin eso un
userns anidado no mapea uid 0 y takana build no corre dentro de la jaula—, ssh, y la instancia
claude-<u> con `root = false`, porque si no el CLI se ve uid 0 y rechaza --dangerously-skip-permissions.

Probado de punta a punta con un usuario desechable y borrado después sin dejar residuo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:20:55 +00:00
SergioandClaude Opus 5 3dc4424597 la jaula que te mapea a root rompe al propio agente
`claude --dangerously-skip-permissions` moría en la caja con «cannot be used with root/sudo
privileges»: la instancia de sergio traía `root = true`, que mapea el uid del invocante a 0 ADENTRO
(qorpa.rs:1533), así que el CLI se veía a sí mismo como root. Con `root = false` queda uid 1001 y la
bandera se acepta — comprobado: `claude --dangerously-skip-permissions --version` contesta.

De paso, a esa instancia le faltaba `/store` en `rw` —la de root ya lo tenía—: sin eso el agente lee
el store y no puede sellar nada. Ahora escribe (probado y limpiado).

Y se corrige el aviso sobre los builds, que ahora falla por otro motivo: con root=false el bwrap
anidado SÍ se levanta y /proc monta, pero no puede mapear el uid 0 que takana build pide, porque
sergio no tiene rango en /etc/subuid y newuidmap/newgidmap no son setuid en esta caja. Cerrarlo es
una decisión con precio (un setuid más, y el /usr/bin de acá está hidratado del store), no un
trámite; mientras tanto el build se le pide al anfitrión por ssh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:03:17 +00:00
Sergio f485f0a187 estado: cosecha granja 2026-09-18T15:02:07Z — avance del árbol KDE 2026-09-18 15:02:07 +00:00
SergioandClaude Opus 5 984ebdf58f la subpágina de humanoid se publica con rsync, y queda dicho por qué
El resto de los sitios se publican con `git pull` porque Caddy sirve de los clones. Con humanoid no
se puede: su repo es el proyecto Android y lo que se sirve son APK ya construidos más su índice —
salida de release que no vive en ningún repo, y que para regenerarse pide Android SDK + Java, que la
caja no tiene. Hasta que la caja construya Android, esto es un puente declarado, no el modelo.

`--checksum` a propósito: los APK se reconstruyen con el mismo tamaño y otra fecha, y un rsync por
fecha subiría 91 M cada vez; medido hoy, lo que de verdad cambió fueron tres ficheros (index.html,
en/index.html y rizoma-launcher.apk) y los otros nueve sólo tenían la marca de tiempo distinta. Y no
lleva --delete: borrar a ciegas en lo que se está sirviendo no es publicar, es perder.

El control pregunta al SITIO, no al rsync —que "salió bien" y una página vieja conviven sin ruido— y
comprueba además un APK, porque el índice puede estar bien y la descarga rota: es lo que la gente se
lleva.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:57:48 +00:00
SergioandClaude Opus 5 bd8f9fbbb2 atuq: la bóveda ANDA hasta la etapa D — y con el navegador abierto quedaba muda por un tercer fallo
Con el arreglo de llimghi sellado, el guardián de metal pasó de la A a la D:

    A ✓ sin la app dueña, el host contesta locked:true (y con ok:true, que es la trampa)
    B ✓ con la app, vault.status dice ABIERTA — el socket sube en 1 s
    C ✓ el diálogo ABRE en el compositor y se lo CONTESTA: «1» ⇒ yes, «2» ⇒ no (los dos sentidos)
    D ✓ vault.save con consentimiento real guarda, y vault.match la encuentra SIN contraseña
    E ✗ el navegador pide la contraseña y no llega nunca

La E destapó un fallo que no es de atuq ni de llimphi: el dueño de la bóveda atendía de a UN cliente
—`atender_cliente` no vuelve hasta que el cliente se va, y se lo llamaba en el hilo del accept—, así
que la primera conexión se quedaba con él mientras viviera. Y el caso normal es ése: Gecko lanza un
`puriy-costura` por PUERTO, ocho en atuq, y todos conectan al arrancar. La extensión de la bóveda
mandaba `vault.match` y no volvía nunca: sin insignia, sin log y sin error, indistinguible de «este
sitio no tiene contraseñas».

Control sin navegador, en los dos sentidos: un solo cliente contesta en milisegundos; con otro host
conectado y quieto, la misma pregunta queda colgada y la mata el timeout a los 30 s.

Arreglado en tawasuyu (`cf3540460`, un hilo por conexión; los diálogos los serializa ahora el Mutex
de la bóveda) con su test de regresión, que además cazó que el arreglo obvio —clonar el Dueno— borra
el socket en el Drop del primer hilo que termina. Los siete tests que ya había no podían ver el fallo:
abrían un cliente por vez, que es justo lo que producción nunca hace.

⚠ Y subir el pin volvió a chocar con el `Cargo.lock` abierto de tawasuyu. Lo que importa para la
próxima es CUÁL operación lo cierra: `cargo metadata` sin `--locked` da **1 línea** de diff y cero
checksums movidos; `cargo generate-lockfile` da 6.305 líneas y 750 checksums, e invalidaría el
vendoreo de todos. Publicado en `b80f7567c` con índice temporal (estaba MM).

Del lado del guardián, tres cosas que salieron de fallar:
· el contestador INSISTE hasta que la ventana se va — con el mismo `wtype rc=0`, una corrida
  contestaba y otra volvía `denied`: la ventana entra al árbol del compositor antes de que llimphi
  tenga el teclado enganchado, y un sleep más largo sólo mueve el borde;
· el contestador busca la ventana POR PATRÓN: con el navegador abierto, teclear «la enfocada» le
  contestaría a la página, y eso es un sí que nadie dio;
· la sonda del chrome mira `isShownForTab` y la insignia ANTES de apretar — `triggerClickOrPopup`
  sale por la puerta de atrás si la acción no está mostrada, y un click que no llega se ve igual que
  una bóveda que no contesta. (Y `WebExtensionPolicy` no es global en el scope de una ventana: sale
  del módulo. Eso costó una corrida.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:52:50 +00:00
SergioandClaude Opus 5 4b52acae06 la batuta: los tres repos al lado y los sitios publicándose solos
terapeuta y los .ec quedan decididos —respaldar, no mudar, y los dominios vencieron hace tiempo—, lo
que explica el NXDOMAIN del §10 y lo saca de los bloqueos.

El gitea de la caja YA tenía sergio/takana.git al lado de sergio/tawasuyu.git; lo que faltaba era el
árbol de trabajo. Los tres clones quedan en /work/sergio de sergio:sergio y los tres EMPUJAN: se le
generó clave en la caja y se registró en su cuenta de gitea por la API con un token de `gitea admin`.

Y los sitios pasan a servirse de los clones, así que publicar es `git pull`. El contenido estaba al
día —se comparó lo servido con lo que generan hoy los repos y coincidía byte a byte—; lo roto era el
modelo: cuando el repo cambiara, la web no se iba a enterar, y eso no se nota porque un sitio viejo
responde 200 igual que uno nuevo.

Lo de humanoid es distinto: su repo es el proyecto Android y lo que se sirve son APK ya construidos,
salida de release que no vive en ningún repo. Refrescar esa página no es publicar, es compilar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:52:12 +00:00
SergioandClaude Opus 5 69a93a9d0d publicar los sitios pasa a ser git pull: Caddy sirve de los clones, no de copias
La mudanza dejó los sitios como copias a mano en /work/www —4,2 G del monorepo sólo para
tawasuyu.net— sin ninguna forma de refrescarlas. El contenido estaba bien y el modelo era frágil: el
día que cambia el repo la web no se entera, y eso no se nota, porque un sitio viejo responde 200
igual que uno nuevo.

Ahora Caddy sirve directo de los clones: tawasuyu.net del monorepo y gioser.net/takana/hifas del
clon de gioser-web. Los medios que NO están en git —los APK de humanoid, los 265 M de muestras,
vivo, ende— siguen en /work/www con su propio `root`, que es la separación correcta. Liberados 4,2 G.

El guion hace `pull --ff-only` (no pisa cambios locales de un árbol que está sirviendo), regenera las
dos páginas hermanas —hermanas.py trae el destino cableado a una ruta de gioser, así que se le pasa
el del clon sin tocar el fichero de ese repo— y después COMPRUEBA: que lo servido sea byte a byte lo
que hay en disco, que los medios sigan en 200, y que /.git/config dé 404, porque servir desde un clon
no puede exponer el repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 14:49:04 +00:00
Sergio 4be104c8fa estado: cosecha granja 2026-09-18T14:32:20Z — avance del árbol KDE 2026-09-18 14:32:20 +00:00
Sergio eb98bc2543 estado: cosecha granja 2026-09-18T14:01:41Z — avance del árbol KDE 2026-09-18 14:01:41 +00:00
SergioandClaude Opus 5 ae352998e8 ¿se puede borrar gioser ya? — el barrido, y un hallazgo que cambia el orden
La condición del usuario está cumplida y medida: la caja sella recetas de takana y de tawasuyu desde
fuente (puriy-costura al mismo hash que predice el hub), tiene compilador propio, driver de C y el
latido empujando. Y los diez dominios de gioser.net/tawasuyu.net resuelven a la caja.

Dos hallazgos del barrido. terapeuta.ec, andino.ec y mercedes.andino.ec NO RESUELVEN en ningún lado
—NXDOMAIN desde dos resolvedores— aunque gioser las siga sirviendo por IP: para el público ya están
caídas. Y el que cambia el orden de las cosas: A GIOSER YA NO SE ENTRA. El 22 está cerrado desde el
laptop, desde la caja y desde el worker; 443, 80 y 2345 abiertos; hcloud dice `running`.

Eso importa porque quedan 127 de 150 entradas de datos sin decidir, y hoy no se pueden bajar sin modo
rescate. O sea: lo que bloquea el borrado no es capacidad —la caja ya reemplaza a gioser— sino datos
sin clasificar en una máquina en la que ya no se puede entrar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:45:26 +00:00
Sergio 35b90b9abf estado: cosecha granja 2026-09-18T13:31:41Z — avance del árbol KDE 2026-09-18 13:31:41 +00:00
SergioandClaude Opus 5 063200214c atuq: el muro de llimphi, arreglado en llimphi — y las dos recetas re-pineadas a ese commit
El §7.septies dejó el diagnóstico: sin display handle, el camino de escritorio de llimphi abre el
display EGL surfaceless, la surface no tiene un solo formato y la app panica. El arreglo está empujado
allá (`eed3120b6`) y son dos ficheros: `Hal::new_con_display` —que usa el `instancia_con` que YA
existía para el camino layer-shell— y el llamador de escritorio pasándole la `window`, que estaba ahí,
creada una línea antes para hacerle la surface. La pieza no faltaba: faltaba enhebrarla.

Control antes de empujar, porque hasta reconstruir no se puede medir: `cargo check -p shuma-pregunta`
pasa con el parche y FALLA con una rotura a propósito en la línea tocada — un check que no compila el
fichero se ve igual que uno que sí.

Commiteado allá con índice temporal (GIT_INDEX_FILE + commit-tree + push <sha>:main), que es lo único
que publica dos rutas sin llevarse por delante los 181 ficheros que otra sesión tiene en vuelo; el
control (`git diff-tree -r --name-only`) nombra dos. El primer push fue rechazado porque el remoto se
movió: se rehízo el commit-tree sobre el FETCH_HEAD nuevo, comprobando antes que esos dos ficheros no
habían cambiado allá.

⚠ Con esto el pin de las dos recetas DIVERGE del `23a292863` de sus once hermanas del monorepo, y eso
cuesta un árbol de fuentes propio — otro vendoreo de 2,4 G, porque el árbol se comparte por
`<repo>-<sha>`. Queda dicho en las dos recetas.

⚠ Y el sello todavía no está: las dos están encoladas en el worker detrás del build de rust. Hasta que
sellen y la etapa B del guardián pase, esto es un arreglo compilado, no un arreglo medido.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:19:29 +00:00
SergioandClaude Opus 5 f126be8691 §6.52: el driver de C queda entregado, y su control destapó un profile.d mudo
El envoltorio existe y está puesto: /usr/bin/cc y c++ sobre el seed-zig que ya estaba en el store,
más las tres banderas en profile.d. No mete nada nuevo en el corpus; volver a zig el compilador
oficial de la distro sigue siendo decisión de ADR.

Y el cuarto control —preguntar por la VARIABLE en una shell de login, no por el fichero— encontró que
en esta caja /etc/profile.d no lo leía nadie porque /etc/profile no existía: las banderas habrían
quedado presentes, con contenido y sin efecto, y el bash_completion.sh que ya estaba ahí llevaba
quién sabe cuánto igual de mudo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:14:16 +00:00
SergioandClaude Opus 5 41b21755b4 en la caja /etc/profile.d no lo leía nadie: /etc/profile no existía
El guion dejaba las RUSTFLAGS en /etc/profile.d/rust-enlazador.sh y ninguna shell de login las
recogía, porque en esta caja /etc/profile NO EXISTE. O sea que el fichero habría sido exactamente la
avería que este repo persigue: presente, con contenido, y sin ningún efecto. Lo descubrió el control
nuevo, que pregunta por la VARIABLE en una shell de login en vez de por el fichero.

Se crea /etc/profile con el bucle estándar sobre profile.d —de paso revive el bash_completion.sh que
ya estaba ahí, igual de mudo— y no se toca el PATH, que lo fija el init.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:13:46 +00:00
Sergio b87fb62b35 estado: cosecha granja 2026-09-18T13:01:37Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 00030fe3f0 estado: cosecha granja 2026-09-18T12:31:34Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio c1e1aff628 estado: cosecha granja 2026-09-18T12:01:26Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 2ad477c799 estado: cosecha granja 2026-09-18T11:31:27Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio c038ad130e estado: cosecha granja 2026-09-18T11:01:22Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 47d3d63a88 estado: cosecha granja 2026-09-18T10:31:36Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 62111694a2 estado: cosecha granja 2026-09-18T10:01:44Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 971acfbbd6 estado: cosecha granja 2026-09-18T09:31:30Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio d6658ebd5f estado: cosecha granja 2026-09-18T09:01:27Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 54821745f2 estado: cosecha granja 2026-09-18T08:31:35Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio ead8042f5a estado: cosecha granja 2026-09-18T08:01:27Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 85c9a23eb6 estado: cosecha granja 2026-09-18T07:31:26Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio 5a175b8cc9 estado: cosecha granja 2026-09-18T07:01:26Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio f25153867d estado: cosecha granja 2026-09-18T06:31:27Z — avance del árbol KDE 2026-09-18 13:12:22 +00:00
Sergio e831a0fabc estado: cosecha granja 2026-09-18T06:01:29Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 36b295e0af estado: cosecha granja 2026-09-18T05:31:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio a2e10814c3 estado: cosecha granja 2026-09-18T05:01:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio fc429dcce2 estado: cosecha granja 2026-09-18T04:31:20Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio c1fa58362d estado: cosecha granja 2026-09-18T04:01:24Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 41519cd2f9 estado: cosecha granja 2026-09-18T03:31:21Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 1955b1ddac estado: cosecha granja 2026-09-18T03:01:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio ff999ed3c1 estado: cosecha granja 2026-09-18T02:31:22Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio a128d987c9 estado: cosecha granja 2026-09-18T02:01:22Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 4c0f32ed9d estado: cosecha granja 2026-09-18T01:31:22Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 51b61141c6 estado: cosecha granja 2026-09-18T01:01:22Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 4de64c7c77 estado: cosecha granja 2026-09-18T00:31:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio a8a148912a estado: cosecha granja 2026-09-18T00:01:22Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 1f6ed096ae estado: cosecha granja 2026-09-17T23:31:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 87446e5e57 estado: cosecha granja 2026-09-17T23:01:23Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio d7b7e288ed estado: cosecha granja 2026-09-17T22:31:26Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
Sergio 65b2469808 estado: cosecha granja 2026-09-17T22:01:24Z — avance del árbol KDE 2026-09-18 13:12:21 +00:00
SergioandClaude Opus 5 69ed6e53c0 la caja no tenía NINGÚN driver de C: se lo damos con el zig que ya está en su store
`command -v cc gcc clang zig` en la caja no devolvía nada. Eso no es sólo incómodo: es lo que hacía
que rustc, con el compilador propio ya sellado, no pudiera enlazar una sola dylib —y ahí entran todas
las proc-macros—. El `-L /usr/lib` obvio empeora las cosas: rompe los estáticos, porque rustc enlaza
musl static-PIE con su copia self-contained y ese camino le hace preferir la libc.a del sistema, que
no es PIC. El camino de librerías no puede ser global; quien sabe cuál es cuál es un driver de C.

NO introduce nada nuevo en el corpus: envuelve el `seed-zig` que ya está en el store —producto del
bootstrap, como stage1-rootfs—. Volver a zig el compilador de C OFICIAL de la distro sería otra cosa,
y es decisión de ADR. La caché de zig va a /work y no a la raíz de 5,9 G, que ya nos costó una caída.

Controles que corre el propio guion, sobre el HECHO y no sobre la intención: compila y ejecuta un
hola mundo en C; enlaza una proc-macro, compila el programa que la usa y lo ejecuta; y —el que evita
el remedio peor que la enfermedad— comprueba que el estático SIN banderas siga enlazando.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:11:57 +00:00
SergioandClaude Opus 5 2fafb3a589 atuq: el vigésimo guardián entra a la suite, y entra TRUNCADO — con el motivo escrito al lado
`test-atuq-boveda-metal --hasta A` pasa a correr con los otros diecinueve. Corre truncado a propósito:
las etapas B..F chocan contra el muro del §7.septies —ninguna app llimphi de escritorio abre ventana
sin Vulkan— que no es de la bóveda, y un rojo permanente por una causa ajena se aprende a ignorar en
tres días. Lo que la A afirma es la medición que costó tres semanas de no ver: **sin la app dueña, el
host contesta `locked:true` con `ok:true`**, o sea que «cerrada» es una respuesta exitosa.

La cabecera del runner dice ahora 20 y avisa que un verde de éste NO dice que la bóveda ande. Es la
misma advertencia que ese fichero ya se aplicó dos veces: un número escrito como predicción envejece
callado, y un cuadro entero en verde puede ser un producto sano o un runner que no mide nada.

Probado en los dos sentidos, que es lo único que distingue un guardián de una decoración:
· en verde, 3 s, dentro del runner y sobre el sello vigente (`b3:e556024b`);
· ROJO con una rotura a propósito —invertir la aserción de la etapa A— ⇒ salida 1.

Y el tamaño que faltaba para la decisión del §7.sexies: `boveda` 22 M y `shuma-pregunta` 21 M, ~43 M
por perfil. Hoy la respuesta a «en qué imágenes se declaran» es NO, y no por el tamaño: declararlas
antes de que llimphi se destrabe sería instalar una bóveda que niega todo sin un error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:50:18 +00:00
SergioandClaude Opus 5 f611751f86 atuq: el diálogo de la bóveda no abre, y el muro no es de la bóveda — ninguna ventana llimphi pinta
`boveda` (b3:59ffd74b) y `shuma-pregunta` (b3:99763eca) sellaron en el worker. Ninguno de los dos abre
ventana, y el error no habla de contraseñas:

    panicked at llimphi-hal/src/lib.rs:1032: index out of bounds: the len is 0 but the index is 0

Esa línea es `caps.formats[0]`: la surface no tiene NI UN formato. La cadena, medida eslabón por
eslabón con RUST_LOG=wgpu_hal=debug:

    No (or unknown) windowing system ((None, Some(..))) present. Using surfaceless platform
    Trying native-render → No config found! · Trying presentation → No config found!

El `None` es `raw_display_handle`, y no es del arnés: es lo que llimphi hace A PROPÓSITO en su camino
de escritorio, con el comentario al lado («Sin display: este camino no tiene ventana todavía»). Sin
display handle wgpu abre el display EGL surfaceless, que no publica configs con WINDOW_BIT ⇒ la
surface no es presentable ⇒ cero formatos ⇒ panic.

No se nota en una máquina con Vulkan, porque ese camino elige Backends::PRIMARY y a Vulkan el display
no le hace falta. Y **ninguna imagen de takana tiene Vulkan**: las tres recetas de mesa se compilan
con `-Dvulkan-drivers=` vacío, y `vulkan-loader` sólo existe en incoming-kde sin que ningún perfil lo
declare (y un loader sin driver no es un driver). ⇒ toda app llimphi de escritorio cae al camino GL y
no puede abrir ventana. Reproducido en TRES binarios y dos versiones de wgpu: shuma-pregunta y boveda
(29) y llimphi-counter (27).

Los controles, porque la conclusión es fuerte: pedir Vulkan explícito da NoAdapter y no hay ICD ni
libvulkan en ninguna capa (la premisa está medida); con softpipe el fallo es OTRO y anterior —wgpu
pide compute shaders y softpipe se queda en GL 3.3—, así que llegar hasta acá ya exige llvmpipe; y el
compositor está levantado con su socket y su WAYLAND_DISPLAY.

Lo que esto dice del producto: el consentimiento de la bóveda no puede pedir permiso en ninguna imagen
de hoy, y como un lanzamiento fallido se traduce a «no» (Command::new falla ⇒ return false), el usuario
vería una bóveda que niega todo sin un solo error.

Se arregla en llimphi (que pase el display handle; la función `instancia_con` ya existe ahí al lado) o
con un driver Vulkan por software en el corpus. No se arregla en takana ni en el arnés.

Entra igual lo que sí se puede afirmar:
· `scripts/test-atuq-boveda-metal.py`, seis etapas declaradas, la A en VERDE — sin la app dueña el
  host contesta locked:true, que es la medición del §7.sexies ahora vigilada. Cada etapa dice qué
  afirma y la B nombra el muro en vez de dejar que se rediagnostique;
· `recipes/wtype.toml` — el corpus no tenía NINGUNA forma de meter una tecla en un compositor: todos
  los arneses de scripts/wlr miran, ninguno toca. Sellado y en ningún perfil: es instrumento.

⚠ Y una trampa del arnés, mía, que costó tres corridas: exportaba GALLIUM_DRIVER=softpipe, o sea que
yo mismo lo clavaba al driver débil. mesa-swrast y mesa-llvmpipe se ven como dos nombres de lo mismo
—«mesa por software»— y la diferencia decide si el arnés existe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:48:06 +00:00
SergioandClaude Opus 5 589c656262 atuq: la bóveda del navegador contesta CERRADA — faltaban el dueño y el diálogo, no el guardián
El §7.quinquies.bis cerró la unidad 12 diciendo que el guardián de METAL «ahora sí se puede escribir
porque hay con qué correrlo». No se podía, y la primera pregunta que ese guardián iba a hacer lo dice
en veinte segundos, contra el artefacto sellado y por marcos de 4 bytes:

    ← {"id":1,"ok":true,"verb":"ping","version":"0.1.0"}          ← el control: el host ESTÁ
    ← {"id":2,"ok":true,"verb":"vault.status","locked":true,...}
    ← {"id":3,"ok":true,"verb":"vault.match","locked":true,"items":[]}
    stderr: puriy-costura: sin bóveda (No such file or directory): los verbos vault.* dirán «cerrada»

Lo que engaña es el `ok:true`: **«cerrada» es una respuesta exitosa**, así que ningún log, ningún
reintento y ninguna insignia la distinguen de un usuario que todavía no desbloqueó la suya. Hoy, en
las cuatro imágenes de escritorio, atuq instala la décima extensión y la función está apagada.

Las dos mediciones del §7.quinquies eran correctas y lo siguen siendo —`strings` da `vault` 10 veces,
`vigia-atuq-verbos` da cero verbos sin dueño—, pero las dos preguntan si el host CONOCE el verbo.
Ninguna pregunta quién contesta del otro lado del socket: el host no abre la bóveda nunca, a propósito
(sled toma un lock exclusivo y el proceso que lanza el navegador muere con cada pestaña), así que le
habla a un DUEÑO — y el dueño no estaba en el corpus. Saber el verbo no es poder contestarlo.

Entran las dos piezas que faltaban, las dos con el mismo efecto de «se ve apagado y nada falla»:
`boveda` (pacha-boveda-llimphi, el dueño único de la base, que levanta el socket del navegador) y
`shuma-pregunta` (el diálogo que lanza `PorDialogo`; sin él `Command::new` falla y **todo vault.fill
se deniega**, indistinguible de que el usuario haya dicho que no).

Las dos pineadas al MISMO 23a292863 que ya comparten puriy-costura, shuma-* y pacha-* —un pin
distinto es otro vendoreo de 2,4 G— y con la forma de mirada-greeter, que es el patrón de una llimphi
GUI: link dinámico (winit/wgpu dlopean EGL/Vulkan/wayland) y la inyección de LOCKSTEP_XML_PATH que
evita el panic de zbus-lockstep-macros en el subdir xml/schemas/ de atspi.

⚠ El sello TODAVÍA NO ESTÁ: las dos están encoladas en el worker detrás del build de rust, bajo
`flock -o`. Hasta que sellen, estas recetas son una hipótesis escrita, no un artefacto medido.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:17:38 +00:00
SergioandClaude Opus 5 a170885357 las proc-macros SÍ se pueden compilar en la caja: falta un cc, no un parche
Con rust sellado, la caja compila estáticos sin tocar nada, pero ninguna dylib —y eso incluye todas
las proc-macros—. El `-L /usr/lib` obvio EMPEORA las cosas: rompe los estáticos, porque rustc enlaza
musl static-PIE con su copia self-contained y ese camino le hace preferir la libc.a del sistema, que
no es PIC. Cuatro controles lo aíslan y el error nombra al culpable (`__malloc_size_classes`). O sea
que ningún RUSTFLAGS global sirve: apaga un caso y enciende el otro.

Quien distingue los dos casos es un driver de C. La caja no tiene gcc ni clang —clang18 en el store
son sólo cabeceras—, pero sí tiene zig: el artefacto seed-zig del bootstrap trae zig 0.16.0 y corre.
Con `cc → zig cc` y `-C linker=cc -C linker-flavor=gcc -C link-self-contained=no` la cadena completa
funciona: la proc-macro enlaza, el programa que la usa compila e imprime, y el estático sigue bien.
Las tres banderas son necesarias; la variante fina `=-linker` es inestable en este triple.

Lo que queda es una decisión, no trabajo: seed-zig no tiene receta —es producto del bootstrap—, y
volverlo el `cc` del sistema es elegir el compilador de C de la distro.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 10:08:11 +00:00
SergioandClaude Opus 5 67e61583fe estado: gcc-libs re-sellada con libgcc.a y el corpus sigue en CERO deuda
La cascada que temíamos no existía: los cinco consumidores declaran gcc-libs en `runtime`, que está
fuera de hash_inputs, así que ninguno se re-hasheó. 938 selladas, 3 ajenas, 0 en deuda, y las cinco
imágenes al 100 %.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:49:19 +00:00
SergioandClaude Opus 5 fca1de2e55 yupana: radio mezclaba las aristas de build con las de runtime y sobreestimaba el coste
`radio gcc-libs` contestaba «9 dependientes, 9 sellados caen a deuda», y con ese número avisé de una
cascada de horas —firefox con PGO dos veces, waterfox, atuq—. La respuesta correcta es CERO: los
cinco declaran gcc-libs en `deps.runtime`, y **`runtime` no entra en `hash_inputs`**, así que
re-sellarla no re-hashea a nadie. Se vio al mirar el store después del cambio: los cinco seguían al
día.

El grafo de `_deps` une build+runtime a propósito —eso es el CIERRE de una imagen, y esa unión
arregló en su día que firefox arrancara sin libstdc++—, pero `radio` responde otra pregunta: «¿a
quién obligo a reconstruir?». Ahora usa dos grafos: la cascada sale del de BUILD, y las imágenes del
de la unión, que es lo correcto para cada uno. Los que dependen sólo por runtime se siguen
informando, nombrados, diciendo que NO se reconstruyen.

Controles: gcc-libs ⇒ 0 de cascada y 9 sólo-runtime; zlib ⇒ 126 directos y 391 transitivos (sigue
viendo las de verdad); zsh ⇒ 0. Y `test-yupana-radio.py` pasa: libdrm sigue cruzando las 5 colas.

Un número que sobreestima frena arreglos correctos, que es justo lo que casi pasa acá.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 01:48:35 +00:00
SergioandClaude Opus 5 e49545e0a7 gcc-libs enviaba un ld script que pedía una librería que nadie tenía
`/usr/lib/libgcc_s.so` no es una librería: es un ld script —`GROUP ( libgcc_s.so.1 -lgcc )`— y ese
`-lgcc` es libgcc.a, que ninguna receta del corpus proveía. Esta misma la borraba: el
`find -name '*.a' -delete` de la poda, y de paso el `rm -rf /usr/lib/gcc` se llevaba el original.

Medido en la caja con el rust recién sellado: los binarios estáticos compilan y corren, pero
cualquier dylib —y eso incluye TODAS las proc-macros— muere con `unable to find library -lgcc`. O
sea `cargo build` con `derive` imposible sobre takana, con el compilador perfecto. Y no alcanza con
dar `-L /usr/lib`: con eso se resuelven `-lc` y `-lgcc_s`, y entonces aparece justo este `-lgcc`.

Es la misma familia que el hueco que originó esta receta —todo presente, todo reproducible, y el
camino que hace falta no existe—, sólo que del lado del ENLACE en vez del arranque.

libgcc.a se copia a /usr/lib (donde el -L del enlazador ya mira; el gcc completo no está en esta
distro) y se salva de la poda. Y entra un GUARDIÁN 3 que compara lo que el ld script PIDE contra lo
que el artefacto TRAE: si vuelve a quedarse sin libgcc.a, corta.

Radio medido con yupana antes de tocar: 7 dependientes directos, 9 transitivos, 7 imágenes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:38:18 +00:00
SergioandClaude Opus 5 096c426b13 estado: CERO deuda — las cinco imágenes cierran al 100 %
rust selló en el worker (b3:6441302d…, 352 M, 17m47s de build), y con eso no queda ninguna receta en
deuda ni sin construir: base 74/74, cli 142/142, escritorio-mirada 41/41, metal-tigerlake 7/7 y
servidor 175/175.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:34:23 +00:00
SergioandClaude Opus 5 2720253a41 rust: el enlazador pasa a ser gcc — dejo de tapar síntomas de la misma causa
Tres intentos persiguieron el mismo problema por partes: `-lgcc` no se encuentra, después `-lgcc_s` y
`-lc` en los build scripts del stage2. La causa es una sola: un enlazador invocado SIN driver de C no
conoce los caminos del sistema. Ese `-L /usr/lib`, el directorio privado de gcc y las libs implícitas
las agrega `gcc`, no `ld`. Dárselos a mano es una lista sin fondo, porque cada etapa del bootstrap
enlaza cosas distintas.

Y el intento de dárselos por RUSTFLAGS (_BOOTSTRAP y _NOT_BOOTSTRAP) NO llegó a los build scripts:
comprobado mirando el renglón de enlace, donde `/usr/lib` seguía sin aparecer. Así que la cura va
donde corresponde: `linker = "gcc"` en el `[target.x86_64-alpine-linux-musl]` de bootstrap.toml. De
paso, con gcc de driver los `-Wl,-rpath,…` del bootstrap vuelven a ser válidos —eran lo que había
obligado a `rpath = false`, que se deja igual porque sigue siendo lo correcto con prefijo /usr—.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:31:50 +00:00
SergioandClaude Opus 5 7deb512425 rust: los build scripts del stage2 tampoco tienen /usr/lib en su línea de enlace
Con `-lgcc` resuelto, el build avanza y muere más adentro: los build scripts de libc, proc-macro2 y
quote no encuentran `-lgcc_s` ni `-lc`. Mirando el renglón de enlace entero, sus únicos `-L` son sus
propios directorios de build: NO lleva /usr/lib. El enlace de `std` sí lo llevaba —de ahí
`musl-root = "/usr"`—, pero eso aplica al TARGET, no a los binarios de HOST que el bootstrap compila
por el camino.

Es la misma causa de las tres veces: sin driver de C, nadie agrega los caminos del sistema. Se los
damos por RUSTFLAGS en las dos variantes que mira el bootstrap (_BOOTSTRAP para lo que compila el
stage0, _NOT_BOOTSTRAP para stage1+). libc.so, libc.a y libgcc_s.so están los tres en /usr/lib del
lab: comprobado, no supuesto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:23:21 +00:00
SergioandClaude Opus 5 58930769d9 rust: el arreglo de libgcc iba en la fase que enlaza, no en configure
El primer intento dejó el enlace en `configure` y lo comprobó ahí mismo: la comprobación dio ✓ y el
build murió igual, con el log mostrándolo con claridad cruel —«rust: libgcc.a enlazada desde
/usr/lib/gcc/…» y, tres pantallas después, «ld.lld: error: unable to find library -lgcc»—.

La causa es que LAS FASES NO COMPARTEN SISTEMA DE FICHEROS: cada una es un bwrap nuevo con
`--tmp-overlay /`, que es una capa tmpfs DESCARTABLE (lo dice el propio comentario de sandbox.rs).
El enlace se evaporó entre configure y compile. Un guardián que comprueba en la misma fase en la que
escribe confirma algo que ya no será cierto cuando importe: lo que cambia el entorno de una fase se
hace EN esa fase. Ahora va al principio de `compile` y de `install`, que son las que enlazan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:14:08 +00:00
SergioandClaude Opus 5 5541900132 rust: -lgcc no se encuentra cuando el enlazador es ld.lld sin driver de C
El build muere a los 6 minutos enlazando libstd.so: `ld.lld: error: unable to find library -lgcc`.
Es la otra cara de lo que la receta ya documentaba para `rpath = false`: al poner ld.lld como
enlazador del triple, se invoca DIRECTO, y el `-L` del directorio privado de gcc lo agrega gcc, no
ld. libgcc_s.so sí está en /usr/lib —por eso `-lgcc_s` pasa y sólo falla el estático—, pero libgcc.a
vive en /usr/lib/gcc/<triple>/<version>/.

Se enlaza a /usr/lib, que el propio renglón de enlace que falla ya busca. La ruta sale de
`gcc -print-libgcc-file-name` y no escrita a mano a propósito: la versión es del LAB, que no entra en
hash_inputs, así que una ruta literal se rompería en silencio el día que el lab mueva de gcc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:03:47 +00:00
SergioandClaude Opus 5 54283b8a94 la cosecha decía «reintenta próximo ciclo» y no reintentaba nada
Si el push del estado se rechaza porque otro agente empujó primero, el ciclo siguiente se rechaza
IGUAL —nadie rebasa nunca— y el commit del cron se queda local para siempre. Se encontró en la caja:
rama divergente con un `estado: cosecha granja` atascado y la cosecha diciendo «reintenta» cada media
hora sin que nada cambiase. El mensaje tranquilizaba y no era cierto.

Ahora, cuando lo rechazan, llama a `git-sincronizar.sh`, que es la regla 2 ter automatizada: rebasa,
COMPRUEBA por parche que el commit sobrevivió, lo recupera del reflog si no, y empuja. Y si tampoco
puede, lo dice y deja el comando para verlo, en vez de prometer un reintento que no existe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:33:58 +00:00
SergioandClaude Opus 5 3b5330b1cd estado: el metal cierra 7/7 y sólo queda rust en deuda
linux-firmware sella por primera vez (1,9 G, 4885 ficheros y los 2415 enlaces de WHENCE),
wireless-regdb entra al corpus y firmware-tigerlake deriva sus 55 blobs (wifi=28, i915=15, sof=10).
Con eso `metal-tigerlake` pasa de 2/6 a 7/7 y `cli` sigue en 142/142.

937 selladas, 3 ajenas, 1 en deuda: rust.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:33:07 +00:00
SergioandClaude Opus 5 a8077003c4 wireless-regdb: la base regulatoria del WiFi ya no viene en linux-firmware
`firmware-tigerlake` cortaba con «falta regulatory.db», y no era un fallo suyo: upstream sacó ese
fichero del tarball de linux-firmware y hoy sólo se publica en su propio proyecto de kernel.org.
Comprobado sobre el artefacto sellado de linux-firmware 20260910: `find -name "regulatory*"` no
devuelve nada.

Lo que pasa sin ella no es «no hay WiFi», que sería fácil de notar: cfg80211 arranca, la tarjeta
asocia, y se queda en el dominio `world` —pierde canales de 5 y 6 GHz y emite a la potencia mínima—.
Un WiFi que funciona y va mal.

La receta NO compila nada: el `make` del proyecto regenera la db y la firma con otra clave, y el
kernel valida contra la que lleva compilada, así que regenerarla la rompería. Copia `regulatory.db` y
su `.p7s` tal cual, y corta si la firma falta —sin firma, cfg80211 ignora la base y volvemos al
dominio world, que es justo lo que veníamos a arreglar—.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:31:48 +00:00
Sergio d05fbbd931 estado: cosecha granja 2026-09-17T21:31:27Z — avance del árbol KDE 2026-09-17 21:31:28 +00:00
SergioandClaude Opus 5 a37485365c linux-firmware: los 337 enlaces «sin destino» eran mi comprobación, no el tarball
Los que faltaban son casi todos de la forma `ath11k/WCN6855/hw2.1/regdb.bin -> ../hw2.0/regdb.bin`:
un `..` no resuelve si el directorio desde el que cuelga todavía no existe, y yo probaba el destino
ANTES del mkdir. Con el directorio creado primero resuelven los 2415 de 2415 — medido sobre el árbol
desempaquetado, no deducido.

Queda dicho en la receta porque la lectura fácil era la contraria: «WHENCE declara enlaces a blobs
que el tarball no trae», que habría llevado a bajar el umbral y sellar un artefacto con 337 firmwares
inalcanzables. Y si el destino de verdad no existe, ahora se borra el directorio vacío que el mkdir
haya dejado: un directorio vacío en el artefacto es exactamente lo que el §3 de CLAUDE.md prohíbe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:29:05 +00:00
SergioandClaude Opus 5 d1032c0336 linux-firmware: los enlaces de WHENCE se encadenan, así que van en tres pasadas
Primer intento con el arreglo: 2078 de 2415 (86 %), y el guardián cortó. La causa es que WHENCE
encadena —declara `a -> b` donde `b` es a su vez un enlace que aparece más abajo—, así que en una
sola pasada el destino todavía no existe cuando toca crear el primero. Se repite hasta que una
pasada no agregue nada, con tope de tres. Y ahora salta los que ya existen, para no recontar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:27:16 +00:00
SergioandClaude Opus 5 36c3634542 estado: 934 selladas — zsh entra y cli cierra 142/142
Regenerado tras sellar zsh estático y traer del store de la caja los tres artefactos que sólo vivían
allá (intel-ucode, sof-firmware, linux-generic; verificados por huella de contenido, no por `du`, que
cuenta directorios y difiere entre sistemas de ficheros).

Quedan tres: linux-firmware (nunca construida, bloquea a firmware-tigerlake) y rust en deuda.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:25:58 +00:00
SergioandClaude Opus 5 f7efbdb806 linux-firmware: los ~2400 enlaces que el tarball NO trae, y una aserción que envejecía mal
Dos defectos medidos el 2026-09-17 al construirla por primera vez en la caja.

1) La receta decía «cp -a preserva los symlinks internos, que es lo que el kernel espera». La premisa
es FALSA: `find -type l` sobre el árbol desempaquetado da 0. Los ~2400 enlaces los crea
copy-firmware.sh al instalar, leyéndolos de WHENCE, y esta receta evita ese script porque pide
rdfind. El resultado habría sido un artefacto de 1,9 G, sellado y reproducible, con iwlwifi MUDO: el
driver pide `iwlwifi-QuZ-a0-hr-b0-77.ucode` en la raíz y el blob vive en intel/iwlwifi/. Justo el
metal al que apunta firmware-tigerlake (TigerLake/AX201) se quedaba sin WiFi. Ahora se crean desde
WHENCE, cuyo destino es relativo al directorio del enlace, y sólo si el destino existe.

2) La aserción de completitud era `> 5000 ficheros`, medida sobre una versión vieja. La 20260910 trae
4889 y el guardián la rechazaba POR COMPLETA. Un umbral escrito a mano envejece y miente en la
dirección cara: frena lo bueno y hace dudar del tarball, que estaba pineado por sha256 y era el
correcto. Ahora se le pregunta a WHENCE, que viaja dentro del tarball y declara cuántos blobs hay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:25:58 +00:00
Sergio 13e3d9e367 estado: cosecha granja 2026-09-17T21:01:24Z — avance del árbol KDE 2026-09-17 21:23:32 +00:00
SergioandClaude Opus 5 c8744ace18 zsh sella estático, con sus módulos y con =~ — y dos teorías mías que eran falsas
El primer build confirmó la predicción que la propia receta traía escrita: `link = "static"` salió
DINÁMICO, contra la libncursesw del lab, y moría con `Error relocating ... symbol not found` —o sea,
habría arrancado en el sandbox de Alpine y no dentro de una imagen—. La causa de fondo es que zsh
carga sus módulos con dlopen; por eso va `--disable-dynamic`, que los mete en el binario.

Pero `--disable-dynamic` solo dejaba un zsh sin `=~`:

    zsh:1: failed to load module: zsh/regex
    zsh:1: -regex-match not available for regex

Dos explicaciones mías fueron falsas antes de la buena, y quedan escritas en la receta porque las dos
son plausibles y costaron un build cada una: (1) «quedan en link=dynamic, hay que sedearlos a static»
—el sed no cambió nada, los disponibles ya salían static—; (2) «el .mdd evalúa $ac_cv_func_regcomp y
config.status no tiene esas variables, hay que exportarlas» —el configure contesta yes a las cuatro y
el módulo seguía fuera—.

Lo zanjó `configure.ac`, que no opina: con dynamic apagado, un módulo que su .mdd declara `dynamic`
se DESCARTA (link=no) y sólo los que declaran `either` pasan a estático. regex.mdd dice `dynamic` y
complete.mdd dice `either`: por eso había completado y no había `=~`. El arreglo va sobre los .mdd,
antes del configure, y sobrevive a que make regenere config.modules.

Además `--enable-pcre` era una etiqueta, no un hecho: zsh 5.9 sólo habla PCRE1 y el corpus tiene
pcre2; el configure lo decía tres veces. Se quitan la bandera y la dep, que mentían sobre el cierre.

Y el control va EN LA RECETA: la instalación corta si el ELF tiene INTERP o si falta alguno de
{regex, complete, zle, parameter} dentro del binario. Ese guardián es quien atajó los dos intentos
fallidos en vez de sellarlos. Medido sobre el artefacto: statically linked, 16 módulos cargan,
`=~` contesta, 1663 completadores, 14 M.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:18:15 +00:00
SergioandClaude Opus 5 5d16b55e14 el visor del grafo reventaba con un estado que no conocía
`build-state-view.py` traía los cuatro estados del corpus cableados (sealed/debt/never/unhashable) y
contaba TODOS los nodos contra ese diccionario. `build-state.py` emite dos más —`wanted` (hueco
declarado en targets.toml, sin receta) y `ajeno` (imagen de qorpa, que no se construye desde
fuente)—, así que el visor moría con `KeyError: 'ajeno'` y el tablero HTML llevaba sin poder
generarse. O sea: agregar un estado nuevo rompía el tablero entero, en silencio para quien no lo
corriera.

Ahora los de fuera del corpus se cuentan aparte y se NOMBRAN en la leyenda —no se cuelan a la barra,
que es sobre `totals["recipes"]` y pasaría del 100 %—, y cualquier estado que aparezca mañana cae en
esa misma rama en vez de tumbar el visor.

De paso, el grafo regenerado: 930 selladas, 2 en deuda, 5 nunca, 3 ajenas; git ya figura sellado en
su hash nuevo (b3:215742cb…) y no arrastró a nadie, como decía el radio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:00:40 +00:00
SergioandClaude Opus 5 d4896c2999 pagada la deuda: el git del corpus vuelve a clonar por HTTPS
El §6.50 quinquies dejó la caja trayendo fuentes por SSH porque su git no tenía remote-https. Eso era
el síntoma; la causa estaba un paso antes de donde mirábamos —incluida la nota que yo mismo había
escrito, que culpaba al Makefile—: el `configure` de git prueba libcurl con AC_CHECK_LIB, que enlaza
`conftest.c -lcurl` y nada más, y con libcurl estática faltan sus privadas (-lssl -lcrypto -lz).

El test falla, config.mak.autogen se lleva NO_CURL=YesPlease, el Makefile excluye los tres helpers y
conserva git-http-backend —esa asimetría en el artefacto era la firma, y estuvo a la vista desde el
14 de septiembre—. El build termina en verde.

Medido sobre el artefacto: b3:215742cb… trae git-remote-http, -https, git-http-fetch y los ftp, y
CLONA por HTTPS (clone, no ls-remote). En la caja, hidratado y con la reescritura apagada, funcionan
tanto `git clone` como `git clone --mirror`, que es el comando exacto del fetch de takana.

Y el radio, medido en vez de temido: la nota vieja decía «raíz de perfil.base ⇒ radio grande»;
`yupana radio git` dice cero dependientes directos y cero transitivos. El miedo escrito a mano había
sobrestimado el costo de arreglarlo, y eso lo mantuvo roto tres días.

Las reescrituras url.insteadOf quedan retiradas de la caja; el guion se conserva como salida de
emergencia y así lo dice su cabecera.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 20:52:47 +00:00
SergioandClaude Opus 5 7f95035332 git vuelve a clonar por HTTPS: la causa era el configure, no el Makefile
El git sellado no traía `git-remote-http` —ni `-https`, ni `git-http-fetch`— pese a tener `curl` en
[deps], y el artefacto mostraba la forma exacta que deja el Makefile cuando NO_CURL está puesto:
`git-http-backend` (el lado servidor, que no usa curl) presente y los tres helpers ausentes.

La causa estaba un paso antes. El tarball de git trae `configure` y takana lo corre; su
`AC_CHECK_LIB` enlaza `conftest.c -lcurl` y nada más, y con libcurl ESTÁTICA eso no resuelve: le
faltan `-lssl -lcrypto -lz`, que son sus privadas. El test falla en silencio, `config.mak.autogen` se
lleva `NO_CURL=YesPlease` y el build TERMINA EN VERDE sellando un git mutilado:

    checking for SHA1_Init in -lcrypto...      yes
    checking for curl_global_init in -lcurl... no      ← acá
    checking for XML_ParserCreate in -lexpat... no

La cura son tres líneas: `LIBS="$(curl-config --libs)"` al configure (sale de curl-config, no escrito
a mano: si curl gana privadas mañana, las trae solo), `NO_EXPAT=1` porque con curl encendido el
Makefile agrega http-push.o y expat no está en deps, y un GUARDIÁN que corta si `config.mak.autogen`
quedó con NO_CURL=YesPlease — el modo de fallo es silencioso, así que se comprueba el HECHO.

Medido sobre el artefacto, no sobre el git del host: b3:215742cb… trae git-remote-http,
git-remote-https, git-http-fetch y los ftp; y CLONA por HTTPS de verdad —clone, no ls-remote, que es
donde murió la vez del ubsan—: árbol en disco y rev-list contesta.

Radio, medido con yupana: git tiene CERO dependientes transitivos ⇒ el re-sello no arrastra ninguna
reconstrucción. Toca las 7 imágenes que lo incluyen, que es otra cosa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 20:49:06 +00:00
Sergio 51e9f885f9 estado: cosecha granja 2026-09-17T20:32:16Z — avance del árbol KDE 2026-09-17 20:32:16 +00:00
Sergio b6f885619b estado: cosecha granja 2026-09-17T20:02:36Z — avance del árbol KDE 2026-09-17 20:02:37 +00:00
SergioandClaude Opus 5 32fea6c25a el control que cierra el ssh: la caja construyó una receta de tawasuyu de punta a punta
puriy-costura era la única de las 36 que NO estaba en el store de la caja —o sea, un fallo de caché
de verdad, no un cache-hit disfrazado de éxito—. La caja la hizo entera y sola: clonó por SSH
(111,7 M de mirror), vendoreó, compiló y selló.

El hash que predice el hub y el que selló la caja son IDÉNTICOS
(b3:7d63655aa5fea3cbc08724887617d7f58b325fcb2e00ac050b1937966c2459e6), que es lo único que prueba que
el transporte no contamina el artefacto; 2,2 M con contenido y el binario corre.

Con esto la caja cumple la condición que puso el usuario para poder borrar gioser —«con capacidad de
compilar tawasuyu y las recetas de takana»— desde FUENTE y por su cuenta, no por artefactos que le
llegan del worker. Los dos negativos del guion también están probados: un repo inexistente lo hace
fallar, y una huella cambiada la rechaza.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:47:20 +00:00
SergioandClaude Opus 5 0bb40af496 la caja trae fuentes git por SSH: el arreglo, en un guion idempotente
Lo que hasta ahora era estado a mano en la caja queda reproducible. Hace las cinco piezas medidas:
reescritura de URLs con url.insteadOf, la clave del host reexpresada SIN puerto (cargo la busca así),
la huella de github.com PINEADA contra la publicada —si no coincide, sale con error en vez de
aceptarla—, la IdentityFile por host, y `net.git-fetch-with-cli = true` para que cargo delegue en el
git de verdad. Además mueve CARGO_HOME fuera de la raíz de 5,9 G, que es lo que tiró la caja.

No mueve ningún hash: la URL es un localizador y no entra en hash_inputs (ADR 0013). Termina con un
control que CLONA de los dos orígenes; si alguno no llega, sale distinto de cero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:36:03 +00:00
SergioandClaude Opus 5 8d1e765bf7 las fuentes git se traen por ssh — y la raíz de 5,9 G tiró la caja entera
La caja no tiene git-remote-http, así que las 36 recetas que clonan de git.tawasuyu.net y las ~90 de
GitHub no se podían construir ahí. La salida es usar SSH, y sale gratis en hashes porque la URL es un
LOCALIZADOR y no entra en hash_inputs (ADR 0013): mismo commit por otro transporte, mismo artefacto.
No se toca ninguna receta —el worker sigue por HTTPS— sino la caja, con dos `url.insteadOf`.

Tres cosas medidas: (1) cargo vendor clona aparte y con su propio cliente ssh —se plantó por la clave
del host, que la tenía con puerto y cargo la busca sin él, y después por autenticación— y se resuelve
con `net.git-fetch-with-cli = true`; (2) la huella de github.com se PINEA contra la publicada, no se
acepta a ciegas; (3) CARGO_HOME estaba en /root/.cargo, o sea en la raíz de 5,9 G: el vendor bajó
2,7 G, la raíz llegó al 100 % y la caja NO se degradó, se cayó entera —sin HTTP, sin SSH y sin
responder al ping, con Hetzner informando «running»—. Rescate otra vez.

Rastro que deja un ENOSPC y que conviene saber leer: caddy escupiendo «no space left on device», y
mis propios `>>` dejando 105 bytes NULOS al final de /root/.ssh/config —un append que falla por
ENOSPC no deja el fichero como estaba: lo deja extendido y relleno de ceros—.

Reparado en el rescate y permanente: /root/.cargo → /work/cargo-home, poda horaria de logs
(ente-gitea.log había llegado SOLO a 114 M y nadie lo rotaba) y los dos configs reescritos. Tras el
arranque: raíz al 54 %, 18 entes corriendo, los 7 dominios de la caja en 200.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:33:38 +00:00
SergioandClaude Opus 5 f13f4f542d cómo se instala software de tawasuyu en la caja — y por qué no con actualizar-servidor.sh
`install-tawasuyu.sh` y `actualizar-servidor.sh` existen y siguen ahí (el monorepo está clonado en
/work/sergio/tawasuyu), pero lo que hacen es `cargo build --release` + `install -m755` a
/usr/local/bin: producen exactamente la clase de binario que la mudanza encontró irreproducible —2,7
G de sueltos que nadie provee, tres de ellos corriendo desde un inodo BORRADO— y además escriben
servicios en /etc/init.d/, que en esta caja no existe.

La forma de acá, hecha de punta a punta con shuma-tui: receta pinneada ⇒ `takana build` ⇒ sellado
b3:f795ee0f… ⇒ el artefacto viaja por rsync ⇒ symlink desde el store ⇒ `shuma-tui --help` contesta.
El camino completo es el del §6.46 (repo firmado + `takana install --require-signed`), que es lo que
convierte «copiar un binario» en «instalar un paquete».

⚠ Y el límite de hoy: el build NO se puede hacer en la caja para recetas que clonan por HTTPS,
porque su git no tiene remote-https. Las que bajan tarballs con curl sí construyen ahí (hoy sellaron
intel-ucode y sof-firmware); las de tawasuyu se construyen en el worker. Arreglarlo es re-sellar
`git` con libcurl, raíz de perfil.base: unidad propia.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:10:26 +00:00
Sergio 53f33c3d9f estado: cosecha granja 2026-09-17T19:01:18Z — avance del árbol KDE 2026-09-17 19:01:18 +00:00
Sergio 9f9a4c3487 estado: cosecha granja 2026-09-17T18:31:19Z — avance del árbol KDE 2026-09-17 18:56:07 +00:00
Sergio f9fca5f1e5 estado: cosecha granja 2026-09-17T18:05:02Z — avance del árbol KDE 2026-09-17 18:56:07 +00:00
SergioandClaude Opus 5 80ee577b40 receta de shuma-tui: así se instala software de tawasuyu en la caja
Responde a «¿cómo instalo shuma-tui?». En gioser se instalaba con `actualizar-servidor.sh`:
cargo build --release + install -m755 a /usr/local/bin. Eso deja un BINARIO SUELTO QUE NADIE PROVEE
— la clase que la mudanza encontró irreproducible (2,7 G de ellos, §6.35).

Acá el camino del corpus: receta pineada ⇒ takana build ⇒ artefacto sellado por hash ⇒ se instala
desde el store o desde el repo firmado. El binario de mañana se puede volver a fabricar bit a bit; el
de install -m755, no.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:53:53 +00:00
SergioandClaude Opus 5 c8ac57d46e sudo repuesto y el taller de trabajo: dónde se programa en la caja
sudo no andaba por DOS cosas a la vez, y una era el corte de energía: /usr/bin/sudo no tenía el bit
setuid, y /etc/sudoers.d/sergio HABÍA DESAPARECIDO — lo escribí minutos antes del reset del §6.51 y
se fue con el mismo corte que dejó el cortafuegos en NULs. Repuestos los dos, con sync, `sudo id`
devuelve uid=0. `visudo` no está instalado y no hace falta: el drop-in en /etc/sudoers.d/ es la forma
correcta. (doas sigue dando «Operation not permitted» aun setuid; queda como rareza sin explicar.)

LOS REPOSITORIOS ESTÁN EN LA CAJA: los 44 en el gitea mudado el 14-sep (sergio 939 M, tawasuyu
547 M). Lo que faltaba era un CLON DE TRABAJO: sólo estaba /opt/takana, el del hub. Ahora
`~/src → /work/sergio` con tawasuyu clonado (478 M) y su origin al gitea de la caja.

⚠ El taller va en /work y no en el home: la raíz tiene 2,4 G libres y /work 43 G.
⚠ Y clonar acá tiene dos trampas medidas: el git del corpus NO tiene remote-https (no se puede
clonar por HTTPS), y clonar por SSH exige la llave registrada en la CUENTA de gitea, no en el
authorized_keys del sistema. Mientras tanto se clona por ruta del sistema de ficheros, como root
(los dirs de organización son 0700 de gitea) y con `safe.directory`.

`PROY=/work/sergio/tawasuyu claude` abre el agente sobre ese repo — comprobado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:49:59 +00:00
SergioandClaude Opus 5 8db55db465 la cuenta necesitaba sus LLAVES, no su contraseña
El primer `ssh -i ~/.ssh/tawasuyu sergio@gioser.net -p 2345` dio «Permission denied (publickey)», y
era exacto: el sshd de la caja tiene `PasswordAuthentication no`, así que la clave sirve para `su` y
la consola, no para SSH. Faltaba `~/.ssh/authorized_keys`, que se creó vacío con la cuenta. Copiadas
las dos llaves de gioser (`tawasuyu` y `sergio@tatata`), entra — y `claude` abre en la misma sesión.

Dos detalles que hacen que se sienta como la máquina de siempre: `gioser.net` ya resuelve a la caja,
y la llave de host es LA MISMA que la de gioser (se copiaron en el cutover del gitea, §6.25),
comprobado: SHA256:ltSl+rgr2Uc… en las dos. Por eso no salta el aviso de «REMOTE HOST IDENTIFICATION
HAS CHANGED» al entrar al nombre de siempre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:37:52 +00:00
SergioandClaude Opus 5 a3e891a601 me dejé afuera de la caja: 20 minutos caída, y las tres causas encadenadas
Crear una cuenta tiró el servidor. Ninguna de las tres causas era la que parecía:

1. La caja NUNCA tuvo /etc/shadow. Crearlo con todas las cuentas bloqueadas (!) hizo que sshd
   RECHAZARA LA LLAVE de root: este sshd no acepta login por llave de una cuenta bloqueada. La forma
   correcta acá es la vieja — el hash en el campo 2 de /etc/passwd, sin shadow.
2. La caja NO reinicia por ACPI: `hcloud server reboot` no hizo nada (arje-zero no atiende la señal).
   La única forma de reiniciarla desde fuera es `reset`, que es un corte de energía.
3. Y ese corte dejó el reglaset del cortafuegos a MEDIO ESCRIBIR — 5 KB de NULs: ext4 journaló el
   tamaño y los datos nunca llegaron al disco. Un fichero medio escrito con `policy drop` y sin los
   `accept` es exactamente una caja que arranca, corre todo y no deja entrar a nadie.
   ⇒ después de escribir algo que el arranque necesita, `sync` ANTES de cualquier reset.

Lo que el rodeo destapó y vale más que el susto:
 · EL CORTAFUEGOS NO SE APLICABA EN NINGÚN ARRANQUE (su card sale 78 si nft -c falla, y con el
   fichero corrupto fallaba siempre): la caja llevaba días arrancando SIN FILTRAR, en silencio.
   Reparado, el arranque ya deja 21 reglas dport, comprobado tras reiniciar.
 · netup es DHCP-only y falló dos veces; se le añadió al init una IP estática de respaldo con la
   forma de Hetzner (/32 + puerta scope link)… que no corría, porque su guarda miraba /sys y el init
   NO MONTA /sys. El diagnóstico que lo probó hubo que escribirlo a FICHERO: sin red, el log tiene
   que estar en el disco.
 · el mapa real de particiones: sda2 root · sda3 state · sda4 work · sdb store.

El costo, sin adornos: ~20 minutos de caída de los ocho dominios y tres resets duros, uno de ellos
disparado por mí a los 400 s mientras la caja estaba haciendo su trabajo. En esta caja cada
diagnóstico remoto cuesta un ciclo de 6-8 minutos: el primero conviene gastarlo en dejar evidencia
en disco, no en probar una corazonada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:32:27 +00:00
SergioandClaude Opus 5 e7b77ff657 la cuenta de sergio en la caja, su jaula propia, y shuma corriendo como él
Pedido: «crea mi cuenta sergio con la clave tawasuyu, desde ahí abrir los claudes como lo estoy
haciendo acá con shuma sobre los proyectos».

  · cuenta `sergio` uid 1001 (el mismo que en gioser), home /home/sergio, clave verificada
  · su propia jaula `claude-sergio`, que concede SU home —su `.claude`: credenciales y memoria— y
    NO el de root. Con la instancia de root, sergio moría en «bwrap: Can't mkdir /root/.claude»
  · `claude` elige instancia por usuario; `PROY=/otra/ruta claude` abre el agente sobre otro proyecto
  · shuma-daemon y shuma-gateway pasan a correr como `sergio`, no como la cuenta de servicio `shuma`:
    shuma es su CONSOLA, y cada pestaña que abre tiene que ser una sesión suya — si no, `claude`
    buscaría una jaula `claude-shuma` que no existe

⚠ NO se eleva con doas/sudo: en esta caja el setuid NO TOMA («doas: Operation not permitted», con
NoNewPrivs 0 y `/` sin nosuid — queda como deuda entender por qué). No hace falta: `qorpa run`
levanta la jaula con user namespaces y anda sin privilegio, siempre que el directorio de la
instancia sea del usuario.

⚠ Y un detalle que costó una vuelta: sin `shift 2`, el HOME y el proyecto que el lanzador pasa como
posicionales se cuelan COMO ARGUMENTOS del agente — la primera sesión se abrió con el prompt
«/home/sergio», y el agente contestó, con razón, que eso es una ruta y no un comando.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:31:30 +00:00
SergioandClaude Opus 5 d6ba65e23c el lanzador arranca EN el repo: sin ese cd, el agente empieza sin memoria
La memoria se indexa por ruta de trabajo. `qorpa run` deja el shell en `/`, así que las primeras
pruebas usaron el proyecto `-` y el agente arrancaba en blanco aunque sus 163 ficheros estuvieran en
disco. Con `cd /opt/takana` usa `-opt-takana`, que es donde se renombró la memoria traída de gioser
(allá el repo era /mnt/vvv/takana).

Comprobado en la caja, con dos preguntas que sólo puede contestar leyendo:

  ¿el logo?    → «martillo 7×7, cuatro colores exactos» (está en su memoria)
  ¿la regla 1? → «todo takana build va envuelto en flock, y con -o», con el matiz del lock heredado

O sea: el agente de la caja arranca sabiendo lo que sabe éste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:36:41 +00:00
SergioandClaude Opus 5 eb96c9219e el agente corre en la caja y puede construir — la condición que el usuario puso para borrar gioser
$ claude -p "Decí exactamente: CLAUDE CORRIENDO EN LA CAJA"
  CLAUDE CORRIENDO EN LA CAJA

Turno real —API, credenciales, respuesta— desde 2.29.29.217. El CLI es un ELF glibc de 360 M y la
caja es musl, así que va enjaulado (ADR 0015, el caso de sergioh-api). La instancia declara red,
nesting y SEIS directorios: /opt/takana, /work/dev-fs, /store, /root/.claude, /root/.ssh (ro) y el
CLI (ro). Es ANCHA y está dicho: un agente que construye, commitea y empuja necesita lo mismo que un
humano; lo que la jaula compra es que el alcance esté escrito y acotado.

⚠ El `takana` que construye NO se instala adentro: sale del store, que ya está concedido — es
estático musl y corre igual dentro de una imagen glibc.

LA CAJA CONSTRUYE: sellados hoy `intel-ucode` (156 signatures) y `sof-firmware` (2 firmware + 8
topologías). Faltaba una pieza que el lab no traía: `.dev-fs/tools` (zig 0.13/0.16 y go, 1 G) — la
imagen del lab empaqueta `alpine/` y nada más, y el primer intento murió con «zig no encontrado».

🧱 Y lo que NO anda, con su rodeo: `takana build` dentro de la jaula muere con «bwrap: Can't mount
proc on /proc» aunque `nesting = true`. No es el anidamiento —un `bwrap --proc /proc` a mano SÍ
funciona adentro— sino el userns anidado del sandbox del build; probado también con `root = false`,
que es la sospecha que el propio código documenta, y falla igual. El rodeo que sí anda, escrito en el
lanzador: pedirle el build al ANFITRIÓN por ssh a 127.0.0.1:22022. Así se selló sof-firmware desde
dentro de la jaula.

Manifiesto y lanzador versionados en scripts/servidor/: una instancia que sólo existe en la caja se
pierde con la caja.

⚠ Pendiente menor: la memoria del agente está en `-mnt-vvv-takana` y allá el repo vive en
/opt/takana ⇒ el índice POR RUTA no coincide y arranca sin memoria del proyecto aunque los 733
ficheros estén en disco.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:34:56 +00:00
Sergio b3f0987677 estado: cosecha granja 2026-09-17T17:31:22Z — avance del árbol KDE 2026-09-17 17:31:23 +00:00
SergioandClaude Opus 5 7295106943 lo único que queda de la mudanza son 150 decisiones sobre datos — y el último rescate
Con las ocho puertas en verde no queda trabajo técnico: queda decidir. La hoja
(`docs/state/mudanza-decisiones-pendientes.txt`) trae 150 entradas y 31,9 G, ordenadas por tamaño,
con la sugerencia entre corchetes cuando hay un HECHO que la respalde y en blanco cuando sólo lo
sabe el dueño. Se aplica en una pasada con `planear.py --decidir`, sin teclas unitarias.

Y el último rescate antes de cerrar: `/mnt/vvv/gioserv` tenía 26 entradas sin commitear y su remoto
era una RUTA LOCAL de la máquina que se borra. Lo commiteado está a salvo (su HEAD es el mismo que
el del gitea nuevo); lo que no estaba en ningún lado —el diff completo, con `api/main.py` +92/−23, y
los 4 no rastreados— quedó en takana:/work/mudanza-secretos/gioserv-pendiente/, verificado con 0
ficheros faltantes. No reapunté el remoto ni commiteé nada: eso es del dueño del repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:06:19 +00:00
SergioandClaude Opus 5 d581c2c519 la hoja final: 150 entradas y 31,9 G que sólo puede decidir el usuario
Con las ocho puertas en verde, lo único que queda de la mudanza son decisiones sobre datos. Esta
hoja las junta todas, ordenadas por tamaño, con lo que la herramienta se anima a sugerir POR UN
HECHO entre corchetes (servido / usado por un proceso vivo / caché / repo limpio con remoto) y en
blanco lo que sólo sabe el dueño.

Se decide editando el fichero y aplicando en una pasada:

  python3 scripts/mudanza/planear.py --censo work/mudanza/censo-detalle.toml \
    --decidir docs/state/mudanza-decisiones-pendientes.txt --target root@2.29.29.217

⚠ Y dice qué NO cubre, porque una hoja titulada «lo que falta» que omite 329 G se lee mal: no están
`/mnt/vvv` (monorepo, repo, store, rustup — el store ya está respaldado y los repos viven en el gitea
de la caja) ni los servicios y dominios, que ya están decididos y ejecutados.

Va a `docs/state/` y no a `work/`, que está en .gitignore: el fichero donde el usuario decide no
puede vivir sólo en la máquina que la mudanza borra.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 17:05:54 +00:00
Sergio 424ad38e71 estado: cosecha granja 2026-09-17T17:01:02Z — avance del árbol KDE 2026-09-17 17:01:02 +00:00