205 lineas en 73 ficheros de crates, mas la prosa de CLAUDE.md y del skill,
que se me habian quedado afuera de los barridos anteriores (no eran ni recetas
ni docs/ ni scripts/).
EL BARRIDO ANCHO ESTUVO A UN COMMIT DE ROMPER EL CORPUS ENTERO.
El primer intento reescribia los .rs completos, no solo los comentarios. Entre
las lineas de codigo que tocaba estaban SIETE etiquetas de separacion de
dominio, que son ENTRADA DE HASH:
b"hammer-tree-v1" <- el prefijo de ArtifactHash::of_tree (hash.rs:60)
b"hammer-seed-v1" la funcion que hashea TODOS los artefactos:
b"hammer-stage1-rootfs-v2" cambiarla mueve los 4750 hashes del store
b"hammer-product-rootfs-v3"
b"hammer-product-attested-v2"
b"hammer-builder-rootfs-v1"
b"hammer-attest-dev-rootkey-0001!!" <- clave raiz de atestacion, [u8;32]
Revertido y rehecho solo sobre comentarios, esquivando ademas las cadenas
crudas de Rust (r#"..."#) porque el SYSTEM_PROMPT del traductor tiene lineas
que empiezan como comentario.
Controles: las 7 etiquetas siguen ahi, el diff toca CERO lineas de codigo,
605 tests en verde y el hash de zlib sigue en b3:dc363f26.
La leccion es la misma de toda esta etapa: un literal que parece prosa puede
ser entrada de hash, y la unica forma de saberlo es mirar donde se usa.
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.
Etapa 2 (ADR 0016): el binario canónico es `takana` y `hammer` se sigue
emitiendo. Son DOS [[bin]] al mismo main.rs, no un symlink: la siembra de la
granja excluye /target (un symlink del hub no existiría en el worker) y
`cargo clean` lo borraría. Ningún llamador tocado; los 124 siguen andando.
Adoptados los dos puntos de la hoja de marca que chocaban con contratos:
- `forja` como ALIAS de clap sobre `build`, no como reemplazo. El canónico
sigue siendo el inglés, que es lo que usan scripts, cron y runbooks. Y se
enmienda la regla 4 de CLAUDE.md en el mismo commit: cambiar el
comportamiento dejando escrito el contrato viejo es lo peor de las dos
opciones, porque el otro agente del repo aplica lo que lee.
- `.tkn` como extensión de paquete. Salió barato y por una razón medida: la
extensión no es lógica sino salida — se escribe en UN solo lugar
(main.rs:1800) y el descubrimiento va por índice, no por glob
(PackageEntry.file, repo.rs:75). Los repos con entradas .swm siguen
resolviendo y un repo mixto es válido; cero ficheros .swm versionados.
Los tipos Swm/SwmBuild/swm_bridge no se tocan: son internos, van en la
etapa 4.
287 tests en verde (hammer-cli + hammer-core), incluidos los que fabrican
repos con nombres .swm a mano — que son justamente la prueba de que la
compatibilidad hacia atrás se sostiene.
El dato ya estaba en memoria y aun así se perdió dos veces, así que ahora vive en los sitios
que se leen sin buscarlos: regla 1 bis de CLAUDE.md (que carga todo agente en cada sesión) y
la skill 'granja' del repo. Se compila en dev.gioser.net PARA NO GASTAR HETZNER; no se
levantan cajas hcloud salvo petición explícita.
Y se arregla la fragilidad que salió al mirar: el reaper de cosecha-cron expulsa de .fleet
todo lo que no esté en hcloud, y el LXC se salvaba SÓLO porque su nombre contiene la subcadena
'gioser' y caía en una lista negra que existe para proteger al hub de Hetzner — no tiene nada
que ver con él. Medido con el bloque real del reaper contra un .fleet de juguete:
nombre CON 'gioser' → 'en LISTA NEGRA ⇒ intocable' sobrevive
el MISMO host como 'pruebasia-lxc' → 'ya no existe en hcloud' .fleet VACÍO
O sea que la granja se mantenía conectada por una casualidad de nombre, y con otro nombre
volvía a 'flota vacía' en el primer ciclo sin que nada fallara. Ahora el reaper pregunta por
SSH si el host responde, que es preguntarle a la máquina en vez de al nombre. Verificado en
las dos direcciones, con control:
host vivo, nombre sin 'gioser' → 'no es de hcloud pero RESPONDE ⇒ se queda (€0)'
host que no responde → 'no responde ⇒ lo saco de .fleet' (intención original)
Queda anotado también que .fleet está gitignored: un hub recién clonado nace con la flota
vacía y la granja queda desconectada en silencio — la cosecha dice 'flota vacía', los
artefactos no vuelven, y estado-granja.sh reporta 'no hay worker vivo' con el worker
compilando. Es como se descubrió esto hoy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
El lock lo sostiene la descripción de fichero abierta y los hijos la heredan. Medido hoy: dos
firefox colgados de una caza sobrevivieron al kill del bwrap que los envolvía y dejaron a la
granja sin poder compilar durante hora y media, sin que nada fallara — el siguiente flock
simplemente espera.
Comprobado en los dos sentidos con control positivo y negativo:
flock lock sh -c 'sleep 25 & exit 0' ⇒ el nieto retiene el lock
flock -o lock sh -c 'sleep 25 & exit 0' ⇒ lock libre
Se añade también cómo diagnosticarlo (fuser -v sobre el fichero de lock, que nombra al proceso
fugado) y se deja anotado que los scripts de scripts/farm/ usan el estilo 'exec 9>' + 'flock 9',
vulnerable igual, como deuda conocida sin barrer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
Medido hoy, y contra mi propia metida de pata: el commit ff0b556 se llevó dentro
un rename y un borrado de otro agente HABIENDO usado `git add <ruta explícita>`
y `git commit` sin -a. O sea, cumpliendo la regla al pie de la letra.
La causa es que el índice es estado COMPARTIDO entre los agentes que trabajan el
árbol: `git commit` commitea el índice entero, no lo que uno acaba de añadir.
Comprobado en un repo de juguete en los dos sentidos:
git add mio.txt && git commit -m … -> arrastra el ajeno.txt que el otro tenía staged
git commit -m … -- mio.txt -> sólo mio.txt; lo del otro queda staged e intacto
La regla decía «sólo rutas explícitas» y esa frase apunta al `add`, que no es
donde está el peligro. Queda apuntando al `commit`, que es donde sí.
Lo levantó la sesión hammer-f8 al ver sus ficheros dentro de mi commit.
La regla existía —kernel_cmd.rs la cita como «Regla 7.bis»— pero no estaba
escrita en ningún sitio que un agente lea, así que nadie la respetó: el ADR
0015 nació proponiendo traer/crear/correr. Queda en CLAUDE.md, que es lo que
se carga en cada sesión.
Con la deuda declarada en vez de tapada: varios scripts/ exponen flags en
castellano y el barrido es su propia unidad de trabajo, porque tocarlos de
paso rompe cron y la granja. Código nuevo nace en inglés desde hoy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5cQtQrYNjWJVXVE6aEpQ2
El repo lo trabajan varios agentes a la vez y las dos formas conocidas de
destruir el trabajo ajeno no estaban escritas en ninguna parte que un agente
lea al arrancar.
1. Todo `hammer build` envuelto en flock work/.farm-build.lock. hammer
comparte work/sources/<dep>-<sha>: dos builds que compartan una dep se
pisan el arbol y queda ROTO PARA SIEMPRE (ADR 0012, sin decidir; ~93 de
205 recetas KDE murieron asi al invalidar libdrm). Los scripts de la
granja ya toman ESE fichero, asi que nos serializa tambien con ellos. No
va dentro de hammer build a proposito: se bloquearia contra esos scripts.
2. Nunca `git add -A`: arrastra los ficheros a medias del otro.
Y la regla general del vacio, que es de donde salio todo lo de hoy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>