Primera tanda de los ADR 0017/0018, con las mediciones que la corrigieron.
ADR 0018 §3 — install-image-efi.sh y takana-live-install.sh escriben el
kernel en \EFI\takana\takanax64.efi ADEMAS de la ruta fallback, con un
guardián de capacidad que comprueba ANTES y con los números a la vista
(duplicar el kernel cuesta, y el costo se dice). En el live-install la
verificación es por TAMAÑO, no por presencia: un cp truncado en FAT32
deja el fichero ahí y test -e diría que todo salió bien.
Verificado con imagen real en OVMF: las dos copias dan el mismo sha256
que el bzImage del store, y la imagen arranca hasta arje-zero PID1.
CONTROL NEGATIVO: pisando la fallback con 4K de basura el firmware dice
"No bootable option or device was found" y no aparece HAMMER-EFI ni una
vez ⇒ el escenario de Windows, reproducido.
Y lo que NO se pudo verificar cambia el alcance, así que va en el ADR:
sin entrada NVRAM el firmware NO busca el vendor path. La copia vendor
es hoy un seguro que no se cobra solo — el §3 es necesario y NO
suficiente sin el §1. Eso asciende la receta efibootmgr a bloqueante.
ADR 0017 §4 — store-gc.sh suma como raíz el kernel EN EJECUCIÓN,
identificado por .config byte a byte. Sin esto el artefacto del kernel
vivo cae en "superados" cuando la receta se movió, y se borra: el
sistema sigue andando perfecto hasta el día que hace falta volver atrás.
Probado en los tres sentidos, incluido el control que TIENE que seguir
condenado.
Además, dos bugs preexistentes que aparecieron al ir a medir:
- install-image-efi.sh abortaba con "ROOTFS sin /sbin/init" en TODO
rootfs sano: /sbin/init es un symlink ABSOLUTO (→/usr/bin/arje-zero) y
[ -e ] lo sigue contra la raíz del HOST. Un chequeo que validaba algo
distinto de lo que creía validar. Arreglado resolviendo el destino
dentro del rootfs.
- (no arreglado, es del entorno) el cp -al del staging da EXDEV si
ROOTFS y STAGE no están en el MISMO MOUNT — y el bind-mount del store
cuenta como otro mount aunque sea el mismo /dev/sdb.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.
El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.
Y el hallazgo caro: casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.
Además 14 rutas de módulo en docs, que el barrido anterior no tocó
porque no es frontera de palabra.
TAKANA_X con caída a HAMMER_X (ADR 0016). No es un sed: estas variables son
contrato de usuario —knobs del instalador, el entorno del worker, mirror-env.sh—
y viven en perfiles de shell y units FUERA del repo, así que un renombre duro no
falla ruidosamente: la variable no aparece, se toma el default y el build se
comporta distinto sin que nada lo diga.
Rust: takana_core::env::{var,var_os} toma el nombre canónico y deriva el viejo
cambiando el prefijo — se le pasa TAKANA_* para que un grep del nombre nuevo
encuentre todas las lecturas. 5 tests, con DOS controles negativos: sin ninguna
de las dos no hay valor, y un nombre sin prefijo no inventa una caída.
Migrados los 17 sitios directos y los indirectos que el grep no mostraba
(bases_de_mirror, las constantes de kernel_cmd, ROOT_ENV de qorpa, env_path de
recover). takana-recover lleva la caída inline: es un mini-binario que se copia
a /usr/sbin y no vale arrastrarle una dep entera por dos líneas.
Scripts: 30 lecturas pasan a default, manteniendo el
nombre INTERNO de la variable para no tocar sus 190 usos.
Y donde el script EXPORTA en vez de leer, se ponen LAS DOS (mirror-env.sh y el
fragmento in-VM de bootstrap): ahí el lector puede ser un binario viejo —worker
sin recompilar, el /usr/bin/hammer pinado del baseline— que sólo conoce HAMMER_*.
La caída sirve para lectores nuevos; los viejos necesitan que la vieja siga puesta.
Verificado de punta a punta con el binario, no sólo con unit tests: HAMMER_LAB
sigue surtiendo efecto, TAKANA_LAB hace lo mismo, y con las dos gana TAKANA_LAB.
605 tests en verde y el hash de zlib sigue en b3:dc363f26… , intacto.
Cambio de comportamiento que va aparte y hay que decir: el hostname por defecto
de una instalación nueva pasa de 'hammer' a 'takana' (sólo si no se fija ninguna
de las dos variables).
NO se tocan: las rutas /var/lib/hammer de sistemas instalados, el volid
HAMMER_LIVE del ISO, ni el namespace HARKAQ_*, que es de otro subsistema.
Los llamadores EJECUTABLES: scripts/ (incluida toda la granja), los runbooks y
CLAUDE.md. Seguro porque la 3a ya garantiza que el worker emite los dos
binarios, y porque en farm-lab-sync.sh el cargo build remoto precede a la
invocación remota en el mismo script.
Verificado: sintaxis de los 49 (bash -n / py_compile — ojo que
why-differs-barrido.sh es Python con extensión .sh) y `takana hash` devuelve
hash real sobre el store.
NO se toca en esta etapa, a propósito:
- La variable de entorno HAMMER=. Es interfaz de los scripts entre sí y hay
llamadores que la fijan; renombrarla va con la etapa 4.
- docs/evidencia/ y los HANDOFF: son REGISTRO de lo que se corrió ese día.
Reescribir un comando dentro de una evidencia la falsifica.
- docs/state/: es generado, se regenera solo.
- Los ADR y los docs de diseño: texto, y `hammer` sigue funcionando. Van con
la etapa 5, que es la de churn de texto.
Hoy `--aplicar` borro los 256 superados y acto seguido murio con
scripts/store-gc.sh: line 143: work/store-gc-superados.txt: No such file or directory
Esa combinacion —borrado hecho, ledger sin escribir— es exactamente la que
reabre el bucle churn que 2026-08-07 costo 362 artefactos y 24 G: `farm-sync`
usa ese fichero como --exclude-from, y sin el la proxima cosecha del worker nos
devuelve lo mismo que acabamos de borrar. El sintoma seria «dos podas con el
mismo numero», que ya sabemos leer como señal.
El manifiesto de la linea 109 SI se escribio, y la diferencia entre los dos es
que aquel hace `mkdir -p work` antes. Ahora el ledger usa ruta ABSOLUTA
($ROOT/work), hace mkdir+touch, y si aun asi no puede escribirse el script
FALLA RUIDOSAMENTE diciendo que los borrados van a volver y como reconstruir el
registro a mano — en vez de informar exito como hasta ahora.
El ledger de esta tanda queda reconstruido desde su manifiesto (256 entradas).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5DComN9Nu47TBpYMBHBn
Podé el store por la mañana: 362 artefactos superados, 24 G. Lo volví a podar por la tarde:
362 artefactos, 24 G. Comparados los dos manifiestos, **solapamiento del 100%: son los
MISMOS 362**. No era casualidad ni recuento nuevo — es un bucle.
LA CAUSA no estaba en el gc sino en `farm/farm-sync.sh`, que bajaba el store del worker
ENTERO sin filtro. El worker nunca se poda, así que conservaba los superados y nos los
devolvía en cada cosecha. Podar → cosechar → vuelven → podar. El disco pagaba 24 G por
vuelta y el gc informaba éxito cada vez, lo que es peor que fallar: parecía que se avanzaba.
Es la misma familia de fallo que el gc que reportaba borrados falsos, sólo que un nivel más
arriba: entonces el borrado no ocurría, ahora ocurre y se deshace solo. En los dos casos el
mensaje de éxito era cierto y engañoso a la vez.
EL ARREGLO es darle memoria al sistema. `store-gc.sh` acumula lo podado en
`work/store-gc-superados.txt` y `farm-sync.sh` lo usa como `--exclude-from` al bajar el
store del worker. Es seguro porque el store es CAS: los nombres `<hash>-<paquete>` son
inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda —
y si retrocediera, se reconstruye, que es barato.
Disco: 159 G → 182 G tras la poda de hoy, y esta vez debería quedarse.
De paso, en el respaldo: rsync 24 («ficheros del origen desaparecieron») NO es un fallo, es
justo lo que pasa al podar mientras se sube. El bucle de reintentos lo tomaba por error real
y habría parado el respaldo entero por algo inofensivo — y encima cuando el disco aprieta,
que es cuando menos conviene quedarse sin copia.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El script informó «364 artefactos borrados · libres: 24G → 48G» y no había
borrado ninguno: los 364 de su propio manifiesto seguían enteros en el store.
`xargs rm -rf` salió con 0 y el echo se lo creyó. Se reprodujo: corriéndolo en
SEGUNDO PLANO el borrado no se materializa; en primer plano sí.
Da igual la causa: un rm que devuelve 0 no es prueba de que el fichero se fue.
La válvula de escape del disco estaba rota EN SILENCIO y encima reportaba
éxito, que es peor que fallar — el disco llegó al 98% mientras yo creía haber
liberado 24G.
Ahora recuenta sobre el filesystem y sale distinto de cero si sobrevivió algo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El disco llegó a 100% y el gordo no estaba en `work/`: el store guarda un artefacto
por cada SELLADO, no uno por receta. Cuando una dep cambia, el ArtifactHash de todo
lo que cuelga cambia y el sellado nuevo SE SUMA al viejo. Medido: 1996 artefactos
para 1130 recetas — 12 copias de libqalculate, 9 de qt6-qtdeclarative, 8 de gtk4.
51G de 74G eran versiones anteriores.
El conjunto VIVO es `hammer hash` sobre todas las recetas de todas las colas: la
respuesta a "¿cuál es el hash vigente HOY?", que el store solo no puede dar. Lo que
no está ahí es rancio — pero rancio son DOS cosas muy distintas, y confundirlas es
la diferencia entre podar y perder:
SUPERADO — su nombre TIENE un artefacto vigente presente. Versión anterior de algo
ya sellado al día. Se reconstruye desde la receta, que está en git.
HUÉRFANO — ningún artefacto con ese nombre es el vigente. El hash de hoy NO está
sellado y éste es el ÚNICO ejemplar. Borrarlo sí pierde.
Por eso el default borra sólo los superados; `--huerfanos` hay que quererlo aparte.
Aplicado: 702 superados = 34G, de 5,4G libres a 43G. La verificación de que el
criterio era correcto no fue el `df`: fue que `hydrate-cosmic.sh` siguió dando 83/83
recetas y 0 faltantes.
Los 255 huérfanos (17G) quedan INTACTOS y son un hallazgo aparte: casi todos KDE
(kio×8, kparts×8, kcmutils×8), o sea que las recetas KDE se movieron después del
último sellado y el escritorio hidratado vive de artefactos que ya no son vigentes.
Dos avisos que el script lleva escritos porque cuestan al descubrirlos solos: el
espacio NO siempre se libera (los rootfs de work/ son hardlinks al store — borrar el
dir no rompe nada pero tampoco libera hasta que el rootfs se vaya), y `--store` por
defecto apunta a /store, no a ./store.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>