La primera corrida completa desde la caja (puerta 7) se cayó 40 veces seguidas con el MISMO fichero:
open ".../fonts/dejavu/.rsync-partial/DejaVuSans-Oblique.ttf" failed: No such file… (2)
.. store: corte de red (rsync 23). Intento 39; reanudo en 60s.
!! store: 40 intentos y sigue cayéndose. Dejo lo subido y paro.
No era red. Una transferencia interrumpida el 11-sep dejó ese PARCIAL en el destino con modo
-r--r--r-- (el del origen: el store es de sólo lectura por diseño), y ningún intento posterior puede
reescribirlo. El fallo es ETERNO, y el bucle reintentaba cada 60 s contra esa pared — exactamente lo
que la cabecera de la función de reintento dice que no hay que hacer.
Dos arreglos:
1. `--chmod=Fu+w`: los ficheros del respaldo quedan escribibles por su dueño, así que un corte a
mitad se RETOMA en vez de trabarse. El precio es no conservar el bit de sólo-lectura en la copia;
es bajo: el contenido es lo que se respalda y los permisos los reconstruye el store.
2. Al rsync 23 se le dan TRES intentos, no cuarenta, y al tercero se NOMBRA la causa probable (un
parcial de sólo lectura en el destino) en vez de llamarlo corte de red. El 23 es «some files were
not transferred», que tanto puede ser un cable como una pared.
Comprobado: borrado el parcial envenenado, ese fichero sube a la primera.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 store entero ✅ 1320 artefactos, 0 vacíos, /store al 62 % tras el gc
4 la granja late allá ✅ §6.43 — con el lab pineado, y la caja gana por uno en los dos grafos
5 el host se instala de su repo ✅ §6.46 — 171 publicados, install --require-signed en verde
7 el respaldo desde la caja ⏳ primera corrida completa en marcha
1,3,6,8 ✅
Lo que separa esto del borrado de gioser ya no es técnico: es la corrida de respaldo terminando y la
decisión del usuario — a mano, con las ocho en verde, nunca por automatización. Y lo que queda vivo
en gioser es trabajo de la mudanza, no servicios: los ~10 G de árboles personales sin decidir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Estrenando el guión con su propio commit saltó el caso: la comprobación por mensaje dio negativo,
el cherry-pick de recuperación salió con «the previous cherry-pick is now empty» y el guión abortó
con error… teniendo el contenido ya en el árbol.
Ahora distingue los dos casos: vacío ⇒ falso positivo de la comprobación, se aborta el cherry-pick y
se sigue; cualquier otro error ⇒ se aborta, se imprime el reflog y se sale ≠0 para que lo mire un
humano. Lo que importa es que el CONTENIDO esté, no que el sha coincida.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
En dos días, `git pull --rebase` se llevó por delante CINCO commits propios en este árbol
compartido, siempre con el mismo reflog: `commit:` mío, `(start): checkout <otro>`, `(finish):
returning to main`, y ni un `pick` en el medio. El comando no es el culpable —en un repo de juguete
reaplica bien— sino que alguien mueve `refs/heads/main` mientras el rebase está en vuelo.
La receta manual (CLAUDE.md regla 2 ter) funciona: mirar el log y recuperar con reflog +
cherry-pick. Esto es esa receta automatizada, porque **una regla que hay que acordarse de aplicar en
cada push es una regla que un día no se aplica**.
Hace, en orden: anota su HEAD · rebasa · COMPRUEBA que el commit sigue en la rama —por su mensaje,
no por el sha, que el rebase reescribe— · si no está lo recupera del reflog · empuja · verifica que
gitea quedó donde tiene que quedar · y si el espejo de GitHub quedó divergente lo realinea con
`--force-with-lease` (nunca `--force` a secas) y sólo tras comprobar que su contenido ya está en
nuestra historia.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Estaba a medias desde el §5.4 por el lab. Con el lab instalado (§6.43), cerrada en dos movimientos,
los dos en la caja:
publicar: 171 paquetes con expected_hash anclado, 3 sin ancla, índice verificado contra ./trust,
firmado con la clave de release que se mudó el 16-sep. Falló uno: os-release.
instalar: `takana install duf --require-signed --trust ./trust` ⇒ «release: trusted (by release)»,
deps resueltas, apply OK, registrado. Bajo --prefix y --db propios, sin tocar el sistema.
⚠ El matiz honesto vale más que el verde: el artefacto YA ESTABA en el store, así que se ejercitó
resolver + verificar firma + hidratar, NO reproducir desde fuente. Lo que el lab destrabó y sí quedó
probado aparte es el HASH (§6.43, cuatro de cuatro iguales a gioser), que es donde moría antes. Queda
sin ejercitar «paquete que no está en el store» — y en una caja de producción es discutible que se
quiera: un servidor no tiene por qué compilar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El paso de poda del latido moría con `/dev/fd/63: No such file or directory` y un `viejos: unbound
variable` de consecuencia. La causa no era el script: la sustitución de procesos de bash necesita
/dev/fd y UNA CAJA TAKANA NO LO TIENE (gioser sí). O sea que ningún `<(...)` de ningún script del hub
funcionaba allá; éste fue el primero en toparse.
Arreglado en los dos lados: /dev/fd -> /proc/self/fd en la caja con Card OneShot en el genesis
(comprobado: `cat <(echo funciona)` responde), y en el script fuera `<(...)` y fuera `df -B1
--output=avail`, que es GNU — sin eso corría pero decía «libres 0.0 G», y un 0 se lee como disco
lleno.
Resultado: 2 árboles podados, 5,4 G. Con el store-gc del §6.44 son 32,2 G recuperados hoy en la
caja: /store al 62 %, /work al 29 %.
⇒ Van dos parches con forma de Card OneShot para cosas que debería hacer el init (hostname y ahora
/dev/fd). Es una lista que conviene no alargar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El latido, corrido desde la caja, moría así:
poda-fuentes.sh: line 72: /dev/fd/63: No such file or directory
poda-fuentes.sh: line 73: viejos: unbound variable
⚠ poda de fuentes falló (rc=1) — sigo
El segundo error es consecuencia del primero (el `mapfile` nunca corrió), y el primero no nombra la
causa: **la sustitución de procesos de bash necesita `/dev/fd`, y una caja takana no lo tiene**.
gioser sí: `/dev/fd -> /proc/self/fd`. O sea que NINGÚN `<(...)` de NINGÚN script del hub funcionaba
allá — esto era sólo el primero en toparse.
Dos arreglos, y los dos hacían falta:
1. En el script: `<(...)` fuera (fichero temporal, que anda en cualquier shell y con cualquier /dev)
y `df -B1 --output=avail`, que es GNU, por `df -k` + awk. Es la poda de `work/sources`, justo lo
que un hub necesita para no llenarse.
2. En la caja: `/dev/fd -> /proc/self/fd`, con una Card OneShot en el genesis para que sobreviva al
reinicio (como la de `hostname`). Comprobado después: `bash -c "cat <(echo funciona)"` responde.
Lo correcto a futuro es que lo haga el init o el product-rootfs, no una Card — queda anotado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/store pasó de 84,7 G usados (91 %) a 57,9 G (62 %): 337 superados borrados y VERIFICADOS, 1657 →
1320 artefactos. Los 150 huérfanos (11,4 G) quedan intactos por diseño: son el único ejemplar de su
nombre.
EL CONTROL QUE NO ES OBVIO: 11 binarios de /usr/bin de esa caja son symlinks que apuntan DENTRO del
store — los daemons instalados estos dos días. Un gc que borre el artefacto equivocado no rompe el
store, rompe el /usr/bin de una máquina en producción. Comprobado antes (los 11 enlazan al hash
vigente) y después (los 11 resuelven, 21 entes corriendo).
Y el gc no sabía decir sus dos números: `du --files0-from=-` es GNU y busybox no lo tiene; `df -h
/home` estaba cableado cuando el store vive en /store (sin /home, `tail -1` devolvía la CABECERA: de
ahí «libres: Available → Available»). El primer arreglo también estaba mal —`xargs -d` es otra
extensión GNU— y eso imprimió «huérfanos ?»: ese `?` es deliberado, un cero inventado habría dicho
«no hay nada que ganar» con 11,4 G en huérfanos. Con `xargs -0` la caja ya contesta 11.4G.
⇒ Cinco tropiezos del mismo día con la misma forma (/dev/tcp, llaves, find -newermt, sustitución de
procesos, y estos dos): un script del hub escrito en una máquina GNU no corre en la caja hasta que se
prueba ahí.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El primer intento cambió `du --files0-from=-` (GNU) por `xargs -d '\n' du -sk`… y `-d` también es
extensión GNU. En la caja quedó «espacio: superados 0 · huérfanos ?». Va con `tr '\n' '\0' | xargs
-0`, que busybox sí tiene, y se probó la función suelta en las dos máquinas.
Lo que SÍ funcionó del intento anterior es el `?`: cuando el total no se puede calcular, la función
lo DICE en vez de imprimir 0. Un cero inventado habría dicho «no hay nada que ganar» justo cuando
había 150 huérfanos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Corrido por primera vez en la caja (una máquina busybox, no GNU), el recolector funcionó —337
artefactos borrados y verificados, 26,8 G liberados— pero sus dos NÚMEROS salieron vacíos:
==> espacio: superados · huérfanos
==> 337 artefactos borrados y VERIFICADOS (0 sobrevivientes) · libres: Available → Available
1. `du -sch --files0-from=-` es de GNU coreutils y busybox NO lo tiene ⇒ `espacio()` devolvía vacío.
Un número que falta se lee como un número chico: sin él nadie puede decidir si vale la pena
correrlo, que es justo para lo que está el dry-run. Ahora `xargs du -sk` + awk, que anda en los
dos mundos.
2. `df -h /home` estaba CABLEADO, y el store casi nunca vive ahí: en gioser es un bind-mount del
volumen y en una caja takana es `/store`, su propia partición. En la caja no hay `/home`, así que
`tail -1` se quedó con la CABECERA y el resultado fue «Available → Available». Se mide `$STORE`.
Medido de verdad con `df` a mano: /store pasó de 84,7 G usados (91 %) a 57,9 G (62 %) — 26,8 G
liberados, 1657 → 1320 artefactos. Y el control que importa después de un gc: los 11 enlaces de
`/usr/bin` que apuntan DENTRO del store siguen resolviendo y los 21 entes siguen corriendo.
⚠ Vale anotar la advertencia que el propio gc imprime y que en esa caja no se puede satisfacer: «sin
.config legible ⇒ NO se protege ningún kernel por esta vía». El default (sólo superados) no toca lo
vigente, pero `--huerfanos` en una máquina que arranca de su store hay que pensarlo dos veces.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El §6.42 dijo qué faltaba y el usuario eligió darle el lab a la caja. Hecho.
EL LAB NO SE COPIA, SE PINEA Y SE TRAE. `lab-image.sh` lo empaqueta determinista (--sort=name
--mtime=@1 --owner=0, excluyendo var/log/apk.log), lo publica al Storage Box con el sha en el nombre,
y `--traer` VERIFICA EL SHA ANTES DE EXTRAER. El empaquetado de hoy devolvió exactamente el sha que
el repo ya pineaba (d1e341d5…): o sea que el lab de gioser nunca derivó Y que el empaquetado es
reproducible de verdad, no una promesa del comentario.
LA PRUEBA QUE DECIDE no es que arranque: la caja calcula los MISMOS ArtifactHash que gioser en las
cuatro recetas de control (zlib, busybox, caddy, shuma-daemon). Con otro lab serían otros números y
el store no lo notaría.
⚠ Y UNA TRAMPA QUE ME TENDÍ SOLO: al copiar los 24 artefactos que le faltaban al store de la caja,
`rsync -a --files-from=<lista de DIRECTORIOS>` mandó 2.281 bytes y «total size is 0» — pero creó los
24 DIRECTORIOS VACÍOS. `--files-from` no recursa sin `-r`, y un directorio vacío en el store ES UN
CACHE-HIT (§3 de CLAUDE.md): `build` lo habría dado por sellado sin construir. Detectado contando,
borrados los 24, repetido con `-r`: 3,18 GB y 0 vacíos de 1655.
Stores convergidos (copiados también obs-studio y spectacle, justo los dos que el worker no logra
construir). La caja no empata, gana por uno: corpus 930 selladas / 1 deuda contra 929 / 2; KDE
1106 / 1 contra 1105 / 2.
Latido mudado: cron apagado en gioser, encendido en la caja, y un ciclo corrido a mano mirando lo que
publica — `repo al día (ff)` → `estado commiteado+pusheado` con los números buenos.
⚠ Lo que queda apretado es el disco: /store de la caja al 91%, 8,2 G libres. Un hub sella y cosecha:
ése es el próximo muro, y `store-gc.sh` no se corrió nunca allá.
⇒ PUERTA 4 EN VERDE. De las ocho quedan dos a medias (5 y 7) y ninguna en rojo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pasó dos veces el mismo día, con el mismo reflog: commit propio, `(start): checkout <otro>`,
`(finish): returning to main`, y ni un `pick` en el medio.
Antes de escribir otra hipótesis, se probó: dos clones de juguete, el otro empuja, yo commiteo local,
`git pull --rebase -q origin main` ⇒ mi commit SE REAPLICA y mi fichero sigue. O sea que `pull
--rebase` hace lo suyo, y lo que falla es que este árbol lo comparten varios agentes y alguien mueve
`refs/heads/main` mientras el rebase está en vuelo.
La receta práctica no cambia (mirar `git log -1` después de cada pull; recuperar con reflog +
cherry-pick), pero el diagnóstico sí: evita ir a buscar el bug al lugar equivocado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El ciclo que corrió desde la caja (commit 45b5685a) dejó en `main` un `build-state*.json` diciendo
`unhashable: 936` y `unhashable: 1112` — o sea, que NO SE PUDO CALCULAR el hash de ninguna receta,
porque el lab entra en el ArtifactHash y esa máquina no lo tiene (§6.42).
Regenerado desde gioser, que sí lo tiene: 929 selladas de 936 · KDE 1105 de 1112. El grafo CIERRA y
el topo-sort pasa en los dos.
⚠ Lo que esto enseña sobre el propio fichero: un `build-state` con `unhashable` en el total no es
«el corpus está mal», es «quien lo generó no podía mirarlo». Los dos se leen igual en un tablero.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Se intentó el último intercambio —cron de cosecha de gioser a la caja— y salió mal en el primer
ciclo. Vuelta atrás hecha en minutos; queda escrito porque el motivo no era ninguno de los previstos.
Funcionó todo lo mecánico: la caja sembró al worker, leyó su manifiesto, regeneró los ocho ficheros
de estado, pasó los vigías, se puso al día por fast-forward y commiteó+empujó. Lo que publicó es el
problema:
caja : {recipes: 1112, nodes: 1115, unhashable: 1112, ajeno: 3}
gioser: {recipes: 1112, nodes: 1115, sealed: 1105, never: 5, debt: 2}
`unhashable: 1112` no es «sin sellar»: es que NO SE PUDO CALCULAR el hash de ninguna receta, porque
EL LAB ENTRA EN EL ArtifactHash y una caja de producción no lo tiene — a propósito. El commit
45b5685a dejó en main un build-state que dice que el corpus entero no existe.
⇒ La puerta 4 NO era una línea de crontab, aunque todas las piezas que se miraron (git, python3,
rsync, ssh, takana, flock, .fleet, la llave) estuvieran. Un hub es la máquina que tiene el LAB: el
tercer paso del latido no es copiar, es PENSAR sobre el corpus. Tres salidas, y ninguna es «poner el
cron»: darle el lab a la caja · partir el latido (siembra/cosecha allá, grafo donde haya lab) · que
el hub sea otra máquina (el LXC ya tiene lab y muele 24/7). Es decisión del usuario, y es lo único
que queda entre esto y borrar gioser.
El latido de gioser, reactivado, repara el daño solo en su siguiente ciclo.
Y el ciclo destapó tres cosas en la caja: `poda-fuentes.sh` NO corre en takana (usa sustitución de
procesos, que busybox ash no tiene — cuarto tropiezo del día con la misma forma); los vigías
reportaron 1 ofensor nuevo de raíces (bzip2), 3 herramientas selladas no invocables y 7 errores en
servicios.txt; y `arjectl status | head -2` hace paniquear a arjectl con un EPIPE sin manejar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cosecha-cron.sh` commitea el estado y lo empuja, pero nunca hacía `pull`. En el hub no se notaba:
el árbol lo mantiene al día el agente que trabaja ahí. Al mover el latido a una caja donde NO hay
nadie trabajando —que es justo lo que pide la puerta 4— el repo se queda atrás en el primer push
ajeno y a partir de ahí TODOS los ciclos fallan el push, cada uno anotando «reintenta próximo
ciclo». Un latido que late y no publica.
`git pull --ff-only`, y nunca `--rebase`: este script corre en un árbol COMPARTIDO con otros agentes
y ahí un `pull --rebase` ya se llevó un commit por delante (regla 2 ter). Fast-forward no reescribe
nada: o adelanta limpio, o falla sin tocar el árbol y se anota en el log.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Apagados en gioser (relay, serve y willay-crosscheck), encendidos en la caja: `tejido` en :4102 y
`willay-crosscheck` en :4103, los dos alcanzables desde fuera.
La prueba de que la identidad VIAJÓ no es que el servicio arranque —eso pasaría igual con semillas
nuevas, que es el fallo silencioso que esto existe para evitar— sino que la semilla sea la misma:
`device.seed` y `roster.postcard` con sha256 IDÉNTICO a los dos lados. El PeerId sale de ahí, así
que sigue siendo 12D3KooWBw2u….
⚠ LO QUE SÍ CAMBIA: el multiaddr que los clientes tienen configurado lleva la IP.
antes /ip4/204.168.193.248/tcp/4102/p2p/12D3KooWBw2u…
ahora /ip4/2.29.29.217/tcp/4102/p2p/12D3KooWBw2u… (mismo PeerId, otra IP)
🧱 SIN EL CORTAFUEGOS HABRÍA SIDO UN VERDE FALSO: el reglaset abría 80/443/1137/2345/22022 y nada
más. Con el relay encendido y el 4102 filtrado, `status` diría «corriendo · 0 reinicios» y la flota
no llegaría. Abiertos 4102 y 4103 con su ban por tasa; NO el 33097, que era el puerto efímero de un
`tejido serve` cliente. Y el fichero quedó IDEMPOTENTE (crear+borrar la tabla antes de definirla),
que era deuda del §6.33: 15 reglas dport antes, 21 después — no 36.
⚠ La política que genera ese reglaset NO EXISTE en ningún disco: ni la ruta que su cabecera nombra
ni el binario `cortafuegos`. El generado sobrevivió a su fuente, así que hoy se edita a mano.
🆔 Y takana acepta ULIDs que arje-zero RECHAZA: la card no encarnaba por `invalid character`, y era
la `L` de «RELAY» — carácter excluido del alfabeto Crockford. `service-cards` lo dio por bueno («26
alfanuméricos») y el consumidor lo tiró. El validador del productor más laxo que el del consumidor
siempre termina en un fallo lejos de donde se escribió.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Era la décima de la tanda y es la única que no selló. Dos hallazgos, y el segundo vuelve irrelevante
al primero.
1. NO CONSTRUYE. Su dependencia `atspi-common` muere con `custom attribute panicked — message: File
has no extension.` en el proc-macro `#[validate(signal: …)]`: lee un fichero en tiempo de
compilación y no resuelve la ruta dentro del sandbox (el árbol vive en `/src/...`). De ahí salen
292 errores en cascada (`ObjectRef` sin declarar), que es lo que se ve primero y no es la causa.
2. NO HACE FALTA. Su propio Cargo.toml la describe como «Mini-ventana Llimphi compatible con
SUDO_ASKPASS»: es una VENTANA (llimphi-ui, theme, widget-text-input, clipboard). En un servidor
sin pantalla no tiene dónde dibujarse — y las sesiones de shuma son PTYs, así que el prompt por
TTY no es una degradación, es el camino correcto.
El aviso que el daemon imprime al arrancar («askpass: no encontré shuma-askpass … pedirán la clave
por el TTY») es para el escritorio, no para el servidor. Queda escrito en la cabecera de
`recipes/shuma-daemon.toml`, que es donde alguien va a leerlo cuando vuelva a ver el aviso.
Balance de la tanda: 9 de 10 selladas y declaradas; la décima retirada con motivo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La tabla del §6.3 era del 10 de septiembre. Hoy, entera:
2 store entero ✅ 1632 artefactos, 0 vacíos (eran 1362) · ⚠ /store al 88%
4 la granja late allá ❌ el latido SIGUE en gioser
6 dominios vivos 200 ✅ los 8 contra 2.29.29.217, `sergio` incluido
7 respaldo desde allá ⬖ el script corre y LISTA el Storage Box desde la caja
1,3,8 ✅ · 5 ⬖ (sirve, no consume: reproducir exige el lab entero)
La puerta 4 es ahora UNA LÍNEA DE CRONTAB. La caja ya tiene git, python3, rsync, ssh, takana, flock,
la llave del worker y el `.fleet`; le faltaban dos costuras y están hechas: `store -> /store` (el
script espera ./store, acá es partición) y `target/release/takana -> /usr/bin/takana` (no hay árbol
de build en una caja instalada). Prueba de sólo lectura: `estado-granja.sh` desde la caja ve al
worker, lee el grafo KDE y dice «CRON DE COSECHA: NO instalado».
NO lo instalé, por el mismo motivo que tejido: con el cron en las dos máquinas, las dos harían
`rsync --delete` al worker y las dos cosecharían y commitearían. El latido se INTERCAMBIA, no se
duplica, y es el último movimiento antes del borrado.
De paso, el repo de /opt/takana estaba 5 días atrasado y quedó en origin/main exacto (0 diferencias),
con tar de respaldo de los 92 ficheros previos. ⚠ Esos 92 no eran trabajo local: era contenido que
YA estaba río arriba metido en el árbol sin commitear — un árbol que parece sucio y está atrasado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`/etc/arje/cards.d/openrc-openclaw.json` de gioser tiene una credencial en claro adentro del JSON —
2 coincidencias con patrón de api_key/token. Ese directorio está en `fuera_de_git`, así que la
mudanza lo copia y un respaldo lo multiplica.
No lo toco: rotar una credencial es del dueño. Queda anotado con las dos salidas (rotarla y moverla
a un fichero 600 que la Card EXIJA, como hace thasnuna; o que openclaw muera y se revoque igual), y
con la que no es opción: copiarla tal cual.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nueve de las diez sellaron y están promovidas. Cinco corren en la caja (matilda, tupu, pacha,
pacha-secretos, willay-daemon, más el par de shuma de ayer); cuatro quedan instaladas y FRENADAS a
propósito, cada una con su guarda diciendo qué falta.
⚠ LA PAUTA DE LOS VHOSTS NO SIRVE PARA UNA IDENTIDAD. Con los sitios la regla fue «mudá el DNS y
dejá el origen encendido, así volver cuesta un minuto» (§6.26). Con `tejido` es justo lo contrario:
su README dice que la clave que el roster atesta ES la identidad de transporte libp2p
(`~/.tejido/device.seed`), así que dos máquinas con la misma semilla no son dos réplicas — son el
MISMO PeerId en dos sitios, dos impostores mutuos para la flota. `tejido` se INTERCAMBIA: se apaga
allá, se enciende acá, en ese orden. Todo listo para el intercambio (binario, cuenta 964, identidad
700 instalada) y su Card en `cards.d` pero NO en el `genesis`, para que un reinicio no lo encienda.
Y arrastra a `willay-crosscheck`, que no es independiente: corriendo su línea a mano dice «no hay
roster en /root/.tejido/roster.postcard — emparejá primero». Vive dentro de la red de tejido.
`thasnuna` tampoco puede: su INSTALAR.md —escrito hoy por el frente tawasuyu para esta mudanza—
pide `sandokan-mcp` y `claude` AUTENTICADO, y el CLI de claude es glibc de ~300 M (jaula qorpa).
De ahí sale un hallazgo que vale para el respaldo entero: la Card `openrc-openclaw` de gioser lleva
la API key de su proveedor EN CLARO dentro del JSON de /etc/arje/cards.d/, que se respalda y se
copia. Un secreto dentro de una Card viaja a todas partes.
`--label` de willay-crosscheck NOMBRA A LA MÁQUINA: en gioser decía `momento`. Ahora sale de
`hostname` y la guarda frena si no hay.
Y tres veredictos FALSOS en una tarde, los tres por probar con lo que la caja no tiene: `/dev/tcp`
(busybox ash no lo tiene) dio los 5 puertos de gioser «cerrados»; la expansión de llaves dijo que
los artefactos no habían llegado; y `find -newermt "-2 minutes"` dijo que tupu no escribía. Tupu SÍ
escribe: su fichero tiene tamaño CONSTANTE —es una serie fija— así que ni los bytes ni ese find
prueban nada; lo que decide es el mtime, medido dos veces con 40 s de por medio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sellaron `matilda`, `pacha`, `pacha-secretos` y `sandokan-watch`; las cuatro promovidas a canónicas
(hash idéntico antes y después de moverlas) y las tres primeras ya supervisadas por arje.
`matilda` archivó 78 ficheros leyendo el access log de caddy; `pacha` corre con SUS reglas y no con
las de fábrica; `pacha-secretos` vuelve a existir como binario — en gioser era un inodo borrado.
TRES CORREN COMO ROOT, declarado en vez de heredado. No es descuido: `matilda` lee
`/var/log/caddy/requests.json`, que caddy crea `-rw------- root root` y recrea igual en cada
rotación; y los tres del trío escriben en el MISMO `/var/lib/tupu`, cuyos 322 ficheros en gioser son
root:root — bajar a uno solo deja mezcla de dueños en un árbol compartido. O los tres, o ninguno. La
salida limpia (caddy con `mode 640` + cuenta propia del árbol) queda anotada en la receta. `pacha` y
`pacha-secretos` sí bajan a cuenta propia: en gioser tampoco corrían como root.
🕳️ LA CAJA NO TENÍA HOSTNAME. `hostname` decía `(none)` y `/etc/hostname` no existía — NINGUNA caja
takana lo fija: ni netup, ni el product-rootfs. Nunca importó porque nada archivaba por nombre,
hasta que llegó matilda: sus primeros 72 ficheros fueron a `/var/lib/tupu/none/`, y dos máquinas
distintas archivarían en la misma carpeta sin que nada falle. Puesto `/etc/hostname` + una Card
OneShot en el genesis para que sobreviva al reinicio. Lo correcto es que lo haga `netup`.
🛡️ Y `sandokan-watch` quedaba VERDE SIN VIGILAR: en takana no hay `auth.log` ni journalctl, así que
arrancaba, decía «sin fuente de accesos: no se guarda nada» y se quedaba «corriendo · 0 reinicios».
Su Card ahora sale 78 nombrando el problema y NO entra al genesis: un vigía que no vigila es peor
que uno caído, porque el caído se ve.
De paso, dos veredictos FALSOS en una tarde por probar con algo que la caja no tiene: `/dev/tcp` no
existe en busybox ash (dio «cerrado» para los cinco puertos de gioser, incluidos :80 y :443, que
sirven) y la expansión de llaves tampoco (dijo que los artefactos no habían llegado; habían
llegado). Misma familia que el §6.23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El texto de esta mañana afirmaba que «otro agente movió la rama», y daba como prueba que los commits
que trajo el `pull` cuelgan del commit ANTERIOR al mío. Eso no es prueba de nada: es lo normal
cuando el otro lado empujó desde una máquina que había hecho `fetch` antes — en este caso la laptop,
cuyos tres commits llevan su huso (-04:00) y no pasaron por acá.
Lo MEDIDO es el reflog (entre `start` y `finish` no hay un solo `pick`) y que los diez ficheros
desaparecieron también del ÁRBOL, cosa que un `reset --hard`/`checkout` concurrente sí explicaría.
Queda escrito como hipótesis, que es lo que es.
La receta práctica no cambia y es lo que vale: mirar `git log --oneline -1` después de cada
`pull --rebase`, y si el commit propio no está, `git reflog` + `cherry-pick`.
De paso, descartado el sospechoso obvio: `cosecha-cron.sh` NO hace pull ni reset — commitea acotado
por pathspec y si el push falla sólo lo anota.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`work/` está en .gitignore, así que el fichero donde vive lo que el usuario decidió —con el motivo de
cada línea— no sobrevivía a la máquina que la mudanza borra. Ahora es `docs/state/`.
Nueve entradas marcadas `respalda`: terapeuta.ec (279 M, sin DNS hace tiempo), los cuatro «valens»,
`los-grandes-mensajes.pdf`, `kkk` (46 B, credencial en claro) y los tres clones de terceros CON
cambios sin commitear — lo commiteado se vuelve a clonar, lo de encima existe sólo en este disco.
Juntas en `/mnt/cosecha/respaldo-mudanza` (475 M) con LEEME y SHA256SUMS de 5472 ficheros. El
control se hizo ANTES del manifiesto: ficheros contados en origen y en corral, las nueve coinciden
(3805, 1243, 183, 1, 164, 1, 1, 72, 1).
⚠ NO entraron `~/hammer`, `~/takana` ni `~/tawasuyu`: son SYMLINKS a /mnt/vvv. El censo los veía
como entradas de 0 bytes CON remoto git —porque `git -C` sigue el enlace— y la recomendación
automática decía «respalda» por tener cambios sin commitear. Respaldarlos habría guardado tres
enlaces rotos. Un dato que parece del objeto y es del enlace.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Medido hoy en este repo compartido: commit propio con 10 ficheros → push rechazado porque otro
agente empujó → `git pull --rebase` → el commit YA NO ESTÁ, ni en el log ni en el árbol (los diez
ficheros desaparecidos del disco).
El reflog lo explica: entre `pull --rebase (start)` y `(finish)` no hay ni un `pick`. El rebase no
encontró nada que reaplicar porque, para cuando corrió, el commit ya no colgaba de `main` — los tres
commits que trajo el pull tienen de padre al commit ANTERIOR al mío, o sea que otro agente movió la
rama hacia atrás. Con `-q`, el resultado se ve igual que un pull limpio.
La recuperación es `git reflog` + `git cherry-pick <sha>`: vuelve entero. Y la comprobación que
evita el susto es mirar `git log --oneline -1` después de cada `pull --rebase`.
De paso, el corolario del espejo: el push doble puede triunfar en un remoto y fallar en el otro
(gitea rechazó, GitHub aceptó), así que tras recuperar queda un sha huérfano en GitHub con el mismo
contenido. Se repara empujando SÓLO esa URL con `--force-with-lease=main:<huérfano>`, después de
comprobar con `git diff --stat` que no se pierde nada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Decisión del usuario: «viven todos los tawasuyu». Se construyen, no se copian — y no es preferencia:
tres de ellos (`tejido`, `pacha-secretos` y el `shuma-gateway` de ayer) corren en gioser desde un
INODO BORRADO, o sea que el binario ya no existe en disco y el proceso vive porque nadie lo mató.
Las diez, todas contra el mismo pin `23a292863` que `shuma-*` y `puriy-costura` — el árbol de
fuentes se comparte por `<dep>-<sha>`, así que un pin distinto es otro vendoreo de 2,4 G:
willay-daemon · willay-crosscheck · tejido · tupu · matilda · thasnuna · sandokan-watch ·
pacha · pacha-secretos · shuma-askpass
⚠ CUATRO NO SE LLAMAN COMO SU PAQUETE, y por eso `-p` y `--bin` no son redundantes:
`willay-crosscheck`←`willay-cruce`, `tupu`←`tupu-cli`, `thasnuna`←`thasnuna-daemon`,
`sandokan-watch`←`sandokan-seguridad-core`, `pacha`←`pacha-cli`. Comprobado crate por crate en el
commit pinneado: los diez son miembros del workspace, así que `-p` resuelve.
Cada cabecera lleva lo que MIDIÓ el censo y que la receta sola no dice: `willay-crosscheck` escucha
con `--label momento`, que nombra a la MÁQUINA y no puede viajar tal cual; `tupu`, `matilda` y
`sandokan-watch` escriben los tres en `/var/lib/tupu`, así que necesitan el mismo dueño o dos fallan
en silencio; `matilda` lee el access log de caddy; `tejido` y `thasnuna` tienen identidad y token que
NO son reproducibles —generar otros es ser otra máquina para la flota, o un bot sin sus teléfonos—,
así que su Card tendrá que exigirlos, no crearlos.
`takana hash` da los diez sin construir. Van a `recipes/incoming/`, que es la cola que el worker
muele; se promueven a canónicas cuando sellen, como shuma.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`https://sergio.gioser.net/` y `/shuma/` contestan 200 contra 2.29.29.217, con TLS público.
**Ningún dominio resuelve ya a gioser.**
⚠ Y el 200 de `/shuma/` había que mirarlo dos veces: el bloque termina en `try_files … /index.html`,
así que CUALQUIER ruta inexistente devuelve 200 con la SPA — un `curl -o /dev/null` habría dado el
mismo verde con el proxy mal puesto. Lo que decide es el cuerpo (17 bytes: «shuma-gateway ok») y el
control es compararlo con lo que el origen viejo sigue sirviendo. Igual con `/shuma/rpc`: las dos
máquinas contestan el mismo 400 con el mismo JSON.
Lo construido, en orden: las dos recetas sellaron en el worker con los hashes anticipados y salen
`statically linked` (nada de cargador, que era lo que le faltaba al binario glibc de gioser) · la
cuenta `shuma` declarada con `[[user]]`, uid 967, y el descenso con `setuidgid` en el argv de la
Card porque el payload Native de arje NO tiene campo de usuario · la config que no se regenera
(`gateway-token`, `identity.x25519`) instalada, y la guarda de la Card la EXIGE en vez de crearla:
un token nuevo deja fuera a los clientes ya emparejados · las dos Cards en `cards.d` Y en el genesis
de la semilla · las dos recetas y los dos labels declarados en `perfil.servidor`.
⚠ LA SHELL DE LA CUENTA NO ES UN DETALLE: el default de `[[user]]` es `/bin/false` —correcto para
gitea o squid, exactamente lo contrario para un demonio que abre PTYs—. Con la shell inerte el
servicio arranca, se supervisa, contesta 200 y cada pestaña muere al instante: un fallo que no se ve
al desplegar, se ve al usarlo. Va `shell = "/bin/sh"`.
`[[user]]` y `[[service]]` están fuera de `hash_inputs`: declarar todo eso no movió ningún
ArtifactHash, comprobado antes y después en los dos.
Y una corrección al §6.25: para cambiar el VALOR de un rrset no hace falta DELETE+POST (que deja una
ventana sin registro). La API tiene
`POST /v1/zones/<id>/rrsets/<n>/<t>/actions/set_records`, que es atómica. Con TTL 60 la resolución
pública cambió en menos de 8 segundos.
El origen de gioser sigue corriendo a propósito, como en §6.26: volver atrás es una llamada.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El worker molió las dos recetas: `b3:107dfb54…-shuma-gateway` (6,3 M) y `b3:f9b92acc…-shuma-daemon`
(5,6 M), los hashes que `takana hash` había anticipado antes de encolarlas.
Y el control que vale no es que sellen ni que reproduzcan, sino CORRER LA HERRAMIENTA del artefacto
(la lección de las 79 de llvm21). `file` dice «statically linked» — nada de cargador, que era
exactamente lo que le faltaba al binario glibc de gioser — y al ejecutarlo arranca, inicializa y
llega a escuchar:
INFO shuma_gateway: UnifiedPush endpoints cargados endpoints=0
Error: Address in use (os error 98)
El puerto estaba tomado por el `shuma-gateway` que YA corre en esa máquina (pid 1127226 en
127.0.0.1:7378): el fallo es del entorno, no del binario.
Promovidas de `recipes/incoming/` a `recipes/` con el control que la memoria de colisiones pide: no
hay receta homónima previa y el ArtifactHash NO se movió al cambiarlas de sitio (la resolución de
deps es hermano→padre, así que un fichero idéntico en otra cola PUEDE sellar distinto; acá no, no
tienen deps). Ahora se pueden declarar en `perfil.servidor`.
Falta la tarjeta de arje —con la cuenta sin privilegio, que el payload `Native` no sabe expresar— y
el corte del vhost.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tres cosas que salieron de usar la herramienta de verdad.
1. `--decidir <fichero>`: decidir EN LOTE desde `<ruta-o-nombre> <decision>` por línea.
El usuario: «no sé cómo activar o desactivar cosas aquí desde shuma llimphi remoto, que las
opciones son con teclas unitarias». El modo interactivo lee una tecla por entrada, y hay
terminales donde eso no se puede usar — una herramienta cuya única forma de decidir exige un
tipo de terminal no es una herramienta, es una herramienta PARA ESA TERMINAL. El fichero anda en
cualquiera, se revisa antes de aplicarlo, se versiona y se vuelve a correr.
Una clave que no empareja con nada, o que empareja con varias, es ERROR RUIDOSO y no se escribe
NADA: un lote a medias deja decidido lo que nadie revisó.
2. `/mnt/vvv` entra a las raíces del censo. No estaba, y ahí vive el trabajo: los repos de la
persona, el monorepo, el store, work/sources. El agujero se vio preguntando por `humanoid`: el
censo sólo conocía `/home/sergio/humanoid` —200 M de `build/` de junio— mientras el proyecto de
verdad, con su `.git`, estaba en `/mnt/vvv/humanoid`, fuera de toda raíz censada. `repos_git` SÍ
lo miraba: una parte del censo conocía el árbol y la otra no.
3. 25 rutas más en `rutas-fuera-de-git.txt`, del barrido que hizo el frente tawasuyu sobre su home:
`~/.wawa/seeds` (sin él ninguna app nueva del génesis nace), `~/.tejido` (la identidad de esta
máquina en la flota), `~/.local/share/agora`, `~/.config/{wawa,minga,thasnuna,shuma,mirada,hcloud}`,
`~/keys` y `~/fdroid` (⚠ firma de apps Android), `~/.gnupg`, `~/.pgpass`, `/etc/wireguard`,
`/etc/{shuma,sandokan,tawasuyu/agente,local.d}`. Son identidades y semillas: no se «vuelven a
generar», porque generar otras significa ser OTRA máquina para el resto de la flota.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pedido del usuario: «una lista de cada cosa con una corta descripción y la opción de yo elegir mudar,
mover a un directorio para respaldar ese directorio, o abandonarlo». Eran tres huecos distintos.
1. «CADA COSA». El censo medía RAÍCES: `/home` era UNA entrada de 25 G y UNA decisión, con repos,
SDKs, 4,3 G de caché y trabajo irrepetible adentro. Ahora emite un renglón por cosa —169 en
gioser contra 5—, un nivel hacia adentro de cada raíz y DOS en `/home`, porque su primer nivel
son usuarios y la decisión no es por usuario. `--solo-totales` conserva la vista vieja, que es la
que dimensiona la mudanza; las dos viajan en el censo (`datos` y `datos_raiz`).
⚠ El primer intento mandaba las ~110 sondas en UNA llamada, se pasaba del timeout y devolvía
lista VACÍA: «no hay datos» en vez de «no pude medirlos». Va en tandas, y una tanda que falla se
nombra.
2. «UNA CORTA DESCRIPCIÓN», y son hechos: lo sirve el servidor web · lo usa un proceso vivo · es
repo git y a dónde apunta · tiene cambios sin commitear · adentro hay node_modules/target/venv ·
lo más nuevo que hay dentro. «Sin señales» también es un hecho, y es el que dice dónde mirar.
3. «ELEGIR ENTRE TRES»: DECISIONES pasa de (muda, muere) a (muda, RESPALDA, muere). Faltaba el
camino que se usa de verdad —no lo quiero corriendo allá, tampoco lo quiero perder—; con dos
opciones, todo lo dudoso se marcaba `muda` y la mudanza engordaba.
La regla de fondo no cambió (qué datos VALEN no lo dice la máquina), pero cuatro hechos sí los
sabe: servido/usado ⇒ muda · caché ⇒ muere · repo limpio con remoto ⇒ muere · repo sin remoto o
sucio ⇒ respalda. Y el cruce que evita el peor error: un remoto que apunta a ESTA MISMA máquina
no es un respaldo, es el mismo disco (medido: /mnt/vvv/humanoid → ssh://git@127.0.0.1:2345).
4. LAS DECISIONES SE GUARDAN. `--decide` las usaba sólo para el plan de esa corrida: cerrabas la
terminal y se perdían las 169. Ahora reescribe el censo (temporal+fsync+rename, `.bak`, y releído
antes de pisar nada; aborta si perdió alguna). Para eso hubo que hacer el emisor IDEMPOTENTE:
escribía `decision = ""` fijo, así que re-emitir un censo decidido duplicaba la clave y el TOML
dejaba de parsear — la herramienta no podía reescribir su propio fichero.
5. EL PASO `respaldo`: corral por defecto leído de respaldo-storagebox.sh (no una segunda dirección
a mano), nombre aplanado, y NO se instala en el destino. Sin corral, aborta: un rsync a ninguna
parte devuelve 0 y se lee como un respaldo. Probarlo con rotura a propósito Y con el control que
tiene que pasar destapó dos bugs: `--mkpath` es obligatorio (el primer respaldo siempre estrena
un nivel) y un FICHERO no lleva barra final — `rsync fichero/ dst/` es un error, y el paso de
`datos` tenía el mismo defecto latente. La verificación es rsync en seco contra el destino real,
no `du` ni `find`: el Storage Box no tiene find, no acepta tuberías, y su `du` comprime.
6. De paso: `aplicar.py --dry-run` decía «✅ todos los pasos ejecutados y verificados» sin haber
ejecutado nada. Ahora dice EN SECO: N mostrados, NINGUNO ejecutado y NINGUNO verificado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Con las decisiones puestas, se movió lo que no dependía de nada más. Cada copia se verificó del lado
del destino, que es lo que el rsync con exit 0 y 1367 artefactos vacíos dejó escrito:
27 guiones (75 KB) → takana:/work/rescate-guiones sha256sum -c ⇒ 27 OK
memoria de Claude (6,3 M) → takana:/root/.claude 733 ficheros = 733
transcripts (1,3 G) → StorageBox claude-gioser rsync -an ⇒ 1 pendiente (el vivo)
clave PRIVADA de release → takana:/root/.config/takana/keys sha256 igual + pública == trust/
credenciales y config → takana:/work/mudanza-secretos 27 ficheros = 27
estado de sigma/tupu/willay → takana:/work/mudanza-estado 351 ficheros, 68 M
Las credenciales NO se instalaron en su sitio a propósito: la caja ya tiene su /root/.ssh, su
crontab y su Caddyfile, y pisarlos rompe lo que funciona. El .gitconfig es el caso más claro — trae
los `insteadOf` que reescriben remotos. Se instalan cuando se mude el hub. La excepción es la clave
de release, que sí fue a su ruta canónica: sin ella nadie puede volver a firmar el repo, y eso no
falla el día que se borra gioser sino la próxima vez que alguien publica.
Y un «576 M contra 1,3 G» que parecía copia truncada: el Storage Box COMPRIME, y su shell
restringida no tiene find ni acepta tuberías, así que el conteo de ficheros ahí no se puede hacer.
Lo que sí: preguntarle a rsync en seco, que compara contra el destino real. Un número que no cuadra
merece una segunda medición antes de un diagnóstico.
No se movieron ~10 G de árboles personales de /home ni /opt: qué datos valen no se deduce de la
máquina, y ésos son proyectos del usuario, no servicios de un censo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El pin de `puriy-costura` subio a `23a292863` y con eso se apagaron los dos rojos del cable. La suite
entera sobre `b3:e556024b`: **19 verdes, 0 rojos, 32,1 min**. No se toco ningun guardian — que la
suite existiera ANTES del arreglo es lo que permite afirmarlo: el cuadro de ayer y el de hoy son el
mismo instrumento.
La medicion que cierra es la que abrio el §7.quinquies, leida al reves: `strings` sobre el binario
sellado da `vault` **10** veces donde daba 0, con `cas`/`sct` de control; y `vigia-atuq-verbos` pasa
de 3 verbos sin dueno a cero, con su control positivo intacto.
Lo destrabo publicar el `Cargo.lock` de tawasuyu (`23a292863` alla), regenerado en un arbol limpio:
215 lineas, todas de contabilidad de deps por ruta, cero `checksum` y cero `source` movidos. Y el
remate, que es la leccion: **ese lock era byte a byte el que la otra sesion ya tenia en su arbol sin
commitear**. Tres semanas de bloqueo y el fichero correcto estaba escrito, sin empujar, a un
directorio de distancia. Antes de decir «bloqueado por otro repo», mirar si lo que falta no esta ya
hecho y sin publicar.
Commiteado alla con indice temporal (`GIT_INDEX_FILE` + `commit-tree`), que es la unica forma de
publicar una ruta en `MM` sin llevarse por delante lo del otro agente: su arbol quedo byte a byte
igual, comprobado con `cmp` antes y despues.
⚠ Y la cabecera del runner se desmintio SOLA dos veces: anuncio «un rojo» cuando eran dos, y «dos»
cuando ya no quedaba ninguno. Un numero escrito como prediccion envejece callado. Con todo en verde
el numero ya no distingue un producto sano de un runner que no corre nada, asi que lo que sostiene la
corrida queda escrito: la puerta del sello (verificada contra un store vacio) y el control interno de
cada guardian.
El usuario pidió el detalle de los binarios que ningún paquete provee, que es lo que la mudanza no
puede reconstruir. Medido en los tres directorios (`/usr/local/bin`, `~/.local/bin`,
`/root/.local/bin`):
tawasuyu 57 ficheros 1003 M salida del monorepo ⇒ se reconstruye
terceros 10 ficheros 841 M kiro-cli 571 M, kcl, qdrant, listmonk… ⇒ se re-bajan
respaldo/duplicado 21 ficheros 876 M /root/.local/bin es una COPIA de kiro-cli + el CLI claude
guiones 27 ficheros <1 M git-deploy, webhook-deploy.py, mirada-session… ⇒ NADA los provee
Lo caro no es lo valioso: 1,8 G de los 2,7 son reconstruibles o duplicados, y lo único que no está
en ningún repo ni paquete son 27 guiones que suman menos de un megabyte. Y los 572 M que el censo no
veía (§6.34) eran justamente la copia duplicada de kiro-cli.
De `.claude` decide mi criterio, por pedido: de 6,8 G viajan 15 M — `projects/*/memory` (6,2 M),
`settings.json`, `plugins` e `history.jsonl`. `jobs` (5,3 G), `downloads`, `archivo-sesiones` y los
caches no viajan; los transcripts crudos (973 M) van al Storage Box, no a la caja: no hacen falta
para funcionar, la memoria es su destilado.
⚠ Y la trampa que haría inútil todo eso: la memoria se indexa POR RUTA (`-mnt-vvv-takana`), y en la
caja el repo está en `/opt/takana` — copiarla tal cual deja 163 ficheros que NO se encuentran, sin
que nada falle.
⚠ Segundo hallazgo: la caja no tiene usuario `sergio` (sólo root, sshd, gitea, proxy) y shuma corre
sin privilegio en gioser. La tarjeta que lo declare tiene que crear la cuenta, y el payload Native
de arje no tiene campo de usuario.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Decisión del usuario: `/shuma/*` —lo único que todavía ata `sergio.gioser.net` a la máquina que se
borra— se construye desde el monorepo de tawasuyu, no se muda como binario.
Y no es una preferencia de estilo: el `shuma-gateway` que corre hoy en gioser es ELF **glibc**,
puesto a mano, y su **inodo está BORRADO del disco** — vive mientras el proceso no muera. El
`shuma-daemon` igual. Copiarlos sería mudar dos binarios que nadie puede reconstruir a una caja que
no tiene su cargador.
Dos recetas Cargo contra el monorepo, pinneadas a `23a292863` — **el mismo commit que ya usa
`puriy-costura`**, a propósito: el árbol de fuentes se comparte por `<dep>-<sha>`, así que dos pines
distintos son dos vendoreos de 2,4 G en vez de uno. Estáticas musl con zig-cc, que es lo que pide un
servicio que arranca el init de la caja.
Comprobado antes de encolar: los dos crates son miembros del workspace en ese commit, el
`Cargo.lock` está commiteado, `rust-version = 1.80` contra el 1.97.0 del lab, y reqwest va por
rustls (nada de openssl). `takana hash` da b3:107dfb54 (gateway) y b3:f9b92acc (daemon), ninguno
sellado todavía.
Van a `recipes/incoming/` —la cola que el worker muele, primera en QUEUES— y no a `recipes/`
canónico: se promueven cuando sellen, que es el orden documentado. Un fichero en `recipes/` a secas
NO lo muele nadie.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El censo ya tenía tres estados a propósito (`ausente` / `sin_permiso` / `ok`), pero el tercero sólo
se detectaba sobre la PROPIA ruta: cuando el que no deja pasar es un ANCESTRO, `[ -e "$p" ]`
contesta que no existe y la ruta caía en `ausente`, que el censo descarta sin decir nada.
Medido en gioser: `/root/.local` es 0700 ⇒ un censo corrido como `sergio` daba `/root/.local/bin`
por AUSENTE. Como root son 572 M de binarios puestos a mano que ningún paquete provee — justo la
clase que la mudanza no puede reconstruir.
Arreglado: si un ancestro existe y no es atravesable, el estado es `sin_permiso`. Probado en los dos
sentidos, que es lo que separa un guardián de un adorno:
ancestro 0700 de otro dueño → sin_permiso (sale en el ⚠ del resumen)
ruta que NO existe de verdad → ausente (no se reporta) ← el control que tiene que pasar
Con el arreglo, el censo sin privilegio pasa de 2 rutas ciegas a 13, entre ellas el home entero de
`artix`, que antes no figuraba de ninguna manera.
Y el §6.34 con el estado MEDIDO de la mudanza hoy: de los 29 dominios queda UNO sirviéndose desde
gioser (`sergio.gioser.net`); los otros seis vivos ya contestan 200 contra 2.29.29.217. El rescate
del §6.20 sigue coincidiendo bit a bit con cuatro de los seis procesos que corren desde un inodo
borrado, y los otros dos tienen en disco una versión más nueva. Lo que queda abierto son los pasos 6
y 7: el montón de daemons propios (shuma, willay, pacha, tejido, tupu, matilda…) y los datos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El pin viejo (`e19bb0e5`) era ANCESTRO del commit que agrego los verbos de la boveda, asi que `atuq`
mandaba `vault.match` y el host contestaba «verbo desconocido»; la extension lo lee como «no hay
boveda», borra la insignia y se calla. Navegador sin la funcion y ficheros en orden — el fallo mas
caro de encontrar que tiene este cable (SDD 26 §7.quinquies).
Estaba bloqueado por algo que no era de takana: el `Cargo.lock` de tawasuyu no cerraba para este
crate. Publicado alla como `23a292863`, regenerado en un arbol LIMPIO del commit: 215 lineas, todas
de contabilidad de deps por ruta, **cero `checksum` y cero `source` movidos** — ninguna version de
registry se toco. Y el lock publicado resulto ser BYTE A BYTE el que la otra sesion ya tenia en su
arbol sin commitear, asi que no compite con su trabajo: es el suyo.
Commiteado alla con un indice TEMPORAL (`GIT_INDEX_FILE` + `commit-tree`) porque ese fichero estaba
en vuelo en el clon compartido y `git commit -- Cargo.lock` se habria llevado el arbol pisando lo
que el otro agente tuviera stageado (regla 2 de CLAUDE.md, el segundo aviso).
⚠ El artefacto todavia NO esta: el vendoreo son 2,4 G y el disco del hub esta al 99% (lo llena
`tawasuyu/target`, que no es mio para podar), asi que se construye en el worker. Hasta que vuelva,
los guardianes que usan el host quedan en rojo por ausencia del artefacto, no por rotura.
La primera version dejaba afuera al guardian de notificaciones (`dunst-headless.sh --via-atuq`) con
la excusa de que vive en el arnes de sway, y lo escribia en la cabecera «para que su ausencia no se
lea como cobertura». Eso dura tres dias: al cuarto la lista ES la cobertura y el parrafo no lo lee
nadie. Corrido aparte: verde en 180 s. Entra.
Es ademas el que mide lo que ningun auditor de ELF puede ver: una pagina llama `new
Notification(...)`, `libxul` hace `dlopen("libnotify.so.4")` —que no es NEEDED de nada—, el bus
activa dunst y dunst DIBUJA. Cinco piezas y la unica forma de saber que estan las cinco es verlo.
Para que entrara, la lista pasa a llevar el COMANDO de cada guardian en vez de su nombre de fichero:
lo que lo tenia afuera era el despacho por extension, no una decision.
Corrida entera de los diecinueve sobre `b3:e556024b`: **17 verdes, 2 rojos, 32,2 min**, que es
exactamente lo que la cabecera predice — y la cabecera es el control, no un adorno.
Los caros: sct 242 s, archivo semantico 238 s, ia 182 s, instalacion 180 s, notificaciones 180 s.
Los cuatro primeros salen en 5 s y no tocan el navegador.
Del `access.log`, sin ambigüedad. La IP del usuario (`45.234.61.160`, autenticada como `sergio`,
2709 peticiones) tuvo tres silencios: **93,2 min (11:59:37 → 13:32:49 UTC)**, 50,1 min (10:44:58 →
11:35:06) y 10,0 min (11:45:06 → 11:55:05). Los tres son el `timeout` de 10 minutos del ban
disparándose una y otra vez: cada vez que volvía, su primera ráfaga lo baneaba de nuevo. El corte
grande terminó exactamente cuando vacié los sets.
🧨 Y LA EXENCIÓN QUE TENÍA NO SERVÍA: la ACL histórica de squid exime `45.234.60.0/24` y él entra
desde `45.234.61.160` — **una red de al lado**. Parecía cubierto y no lo estaba. La pertenencia se
MIDE (`ip in red`), no se deduce del parecido del prefijo.
DECISIÓN DE FONDO: en un servicio que YA AUTORIZA no va límite por tasa. El uso de esta caja es
INTENSIVO —varias sesiones de agente en paralelo, cada una abriendo túneles a la vez— y un límite
calibrado para «una persona normal navegando» no protege de nada: el atacante ajusta su ritmo, el
usuario no puede trabajar más despacio. Squid ya pide usuario y contraseña y tiene su ACL. El ban del
puerto queda apagado (60000/min, ráfaga 1000) y contra un flood queda el tope GLOBAL
`syn_nuevas_por_seg`, que no distingue usuarios ni banea a nadie por ser rápido.
Control tras aplicar: su túnel pasando —`45.234.61.160 … CONNECT api.anthropic.com:443 sergio`— y
`45.234.61.160 ∈ 45.234.60.0/23` comprobado con aritmética de redes, no a ojo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Preguntaron si el proxy estaba caído. NO lo estaba: el ente llevaba horas corriendo con ↻ 0 y
contestando 407. Lo caído era el ACCESO — el cortafuegos de ayer baneaba a los autorizados.
La prueba en una línea: **154.197.1.2, una IP que está en la ACL de squid (gente con contraseña),
apareció en `@ban4`**, con el contador del ban en 38.246 paquetes tirados.
LA CAUSA NO ES LA TASA, ES LA RÁFAGA: `limit rate over 300/minute burst 5 packets` tolera cinco
conexiones por encima del promedio, y un navegador o un agente detrás de un proxy abre DECENAS de
CONNECT en el mismo instante aunque su promedio sea bajísimo. Y como @ban4 es UN SOLO set consultado
antes que todo, caer por el puerto del proxy te tira también el web y el git.
⚠ El tipo ya traía el campo `rafaga` (default 5) con un comentario que dice «para un servicio web va
alto (100-200)». Otra vez leí el .ron de EJEMPLO y no el TIPO.
Hecho, en orden de urgencia: flush de los sets (servicio restaurado en el acto) · los 21 IPs y 5
redes de la ACL de squid a `confiables` · `rafaga` por servicio según su clientela (ssh 5, git 20,
web 100, proxy 200) · proxy a 1200/min con ban de 60 s, que es red de contención contra un flood y
no control de uso. Control después: CERO autorizados en el set, 24 baneados (escáneres) y tráfico
real pasando —`154.194.14.37 … CONNECT api.deepseek.com:443 sigma`, 298 K tunelados—.
⚠⚠ Y un fallo de método que casi lo tapa: **`nft -f` SUMA a la tabla existente**. Cargué el reglaset
corregido encima del viejo y quedaron 30 reglas con las VIEJAS ADELANTE, así que los `confiables`
nuevos no se evaluaban nunca. Por eso `cortafuegos apply-input` borra la tabla antes de cargar.
La lección: un cortafuegos correcto y uno usable no son lo mismo, y la diferencia NO se ve al
aplicarlo. El control que lo habría cazado es el que no corrí: una ráfaga de conexiones desde una IP
que no esté en `confiables`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El build con `ld.lld` en el spec murió compilando `std`:
ld.lld: error: unknown argument '-Wl,-z,origin'
ld.lld: error: unknown argument '-Wl,-rpath,$ORIGIN/../lib'
Con el enlazador invocado DIRECTO (sin driver de C), los `-Wl,…` que el bootstrap añade para el
rpath de sus propios binarios llegan tal cual a lld, que no los entiende — esa sintaxis existe para
que un `cc` los traduzca.
El rpath sobra igual: con `prefix = "/usr"` las librerías quedan en `/usr/lib`, que ya está en el
camino del cargador. Es lo que hacen las distros que empaquetan rust.
Costó 6 minutos de build descubrirlo, que es lo que vale tener la cadena en la granja: cada intento
falla temprano y con el mensaje exacto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La prueba de la caja recién instalada —`cargo build` en un proyecto nuevo, sin flags ni rutas a
mano— falló con `linker `cc` not found`, y destapó que la pieza que había empaquetado era falsa:
**cargo NO LEE `/etc/cargo/config.toml`**. Sólo lee `$CARGO_HOME/config.toml`, los
`.cargo/config.toml` del proyecto y sus padres, y lo que se le pase con `--config` — que es lo que yo
venía pasando a mano en cada prueba, y por eso «funcionaba».
Y el arreglo no era mover el fichero: **el que enlaza es `rustc`, y rustc no lee la config de
cargo**. Las flags van DENTRO del compilador, en el spec del triple:
· `linker = "ld.lld"` + `linker_flavor = Gnu(Cc::No, Lld::Yes)` — sin esto rustc busca `cc`, que una
caja takana no tiene.
· `crt_static_default = true` — sin esto enlaza dinámico y pide `-lgcc`, que no existe (la distro se
construye con zig, que trae compiler-rt).
· `crt_static_allows_dylibs = true` — su pareja obligatoria: sin ella `crt-static` apaga los dylibs
y con ellos los PROC-MACROS (serde_derive, clap_derive, las 44 recetas `cargo-*` con `derive`).
El compilador en sí sigue dinámico: el `bootstrap.toml` fija `crt-static = false` para el build, y
eso gana sobre el default del spec.
⇒ `cargo-config` (receta, árbol y copia en scripts/) se van enteros: un paquete que instala un
fichero que nadie abre es peor que no tenerlo.
Y de paso, `lld21` instala los cuatro alias como SYMLINKS y no como copias: cmake los duplicaba, 5 ×
96 M = 478 M de artefacto para 97 M de enlazador. Ahora 108 M, y `ld.lld` sigue despachando por
`argv[0]`, que es como upstream lo diseñó.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sale `rust-toolchain-bin` —799 M de bytes AJENOS sellados tal cual (`foreign`)—, que era el escalón 1
del SDD 31 y cumplió su función: con él se construyó el escalón 2, y el escalón 2 ya compila,
enlaza y corre.
Entran TRES, que son el mismo hecho («esta caja compila Rust») partido en piezas:
· `rust` (353 M) — el compilador y cargo, de la granja, desde fuente.
· `lld21` — el enlazador. NO es opcional: una caja takana no tiene `cc`, y sin enlazador `rustc`
compila objetos y no produce un ejecutable.
· `cargo-config` — receta nueva que publica `/etc/cargo/config.toml` con las tres flags que unen a
los dos. Sin ellas `cargo build` muere con «linker `cc` not found»: el toolchain estaría completo
y MUDO, que es [[subcomando-sin-driver]] otra vez. Es config de SISTEMA y no de usuario a
propósito — un `~/.cargo/config.toml` funcionaría para quien lo escribió y no para el siguiente.
⚠ Y `rust` se re-hashea otra vez (702094a8 → fe277b32) por una línea que descubrió el control de
PATH: **x.py instala en `/usr/local` por defecto, y el PATH de una caja takana es
`/bin:/usr/bin:/sbin:/usr/sbin`** — medido en la caja. O sea que el perfil habría proyectado un
`rustc` perfecto que nadie puede invocar: «existe ≠ se encuentra». Ahora `[install] prefix = "/usr"`,
como el resto del corpus.
Control previo que sí pasó: `rustc` y `cargo` corren SIN `LD_LIBRARY_PATH` — su `RUNPATH` es
`$ORIGIN/../lib`, así que la proyección del artefacto basta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>