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.
129 lines
7.8 KiB
Markdown
129 lines
7.8 KiB
Markdown
# Reglas del repo compartido
|
|
|
|
Este repo lo trabajan **varios agentes a la vez** (hoy: frente granja/store y frente kernel). Lo de
|
|
aquí abajo no son preferencias de estilo: son las dos formas conocidas de que un agente destruya el
|
|
trabajo de otro sin enterarse. El resto del diseño está en `docs/`.
|
|
|
|
## 1. Todo `takana build` va envuelto en `flock`
|
|
|
|
```sh
|
|
flock work/.farm-build.lock ./target/release/takana --store ./store build <receta>
|
|
```
|
|
|
|
Para una tanda, tomar el lock una sola vez y no por receta:
|
|
|
|
```sh
|
|
flock work/.farm-build.lock bash -c 'for r in ...; do ./target/release/takana --store ./store build "$r"; done'
|
|
```
|
|
|
|
**Por qué.** `takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds
|
|
concurrentes que compartan una dependencia se pisan el árbol de fuentes: uno hace fetch y lo borra
|
|
mientras el otro lo usa, y **el árbol queda roto para siempre** — reintentar no lo arregla. Medido a
|
|
escala: al invalidar libdrm, ~93 de 205 recetas KDE murieron con `/src/.zwrap/cc is not a full path
|
|
to an existing compiler tool`, que es el wrapper de zig que la receta deja EN EL ÁRBOL, barrido por
|
|
el fetch concurrente de otra receta. Es el **ADR 0012**, sin decidir; hasta que se decida
|
|
(lock por árbol / árbol privado / caché inmutable + copia), serializar es la única mitigación
|
|
correcta. Por eso el worker corre con `JOBS=1`.
|
|
|
|
⚠ **Y usar `flock -o`, no `flock` a secas — esto es medido, no teórico (2026-09-08).** El lock lo
|
|
sostiene la *descripción de fichero abierta*, y los hijos la HEREDAN: si un nieto se fuga, el lock
|
|
queda tomado para siempre aunque el `flock` haya terminado hace rato. Pasó: dos `firefox` colgados de
|
|
una caza de bugs sobrevivieron al `kill` del `bwrap` que los envolvía y **dejaron a la granja sin
|
|
poder compilar durante hora y media, en silencio** — nada falla, simplemente el siguiente `flock`
|
|
espera para siempre. Comprobado en los dos sentidos:
|
|
|
|
```sh
|
|
flock lock sh -c 'sleep 25 & exit 0' # ⇒ el nieto RETIENE el lock tras salir flock
|
|
flock -o lock sh -c 'sleep 25 & exit 0' # ⇒ lock LIBRE; -o cierra el fd antes de ejecutar
|
|
```
|
|
|
|
Si el lock parece tomado y no hay ningún build, `fuser -v work/.farm-build.lock` dice quién lo tiene;
|
|
casi siempre es un proceso fugado que nadie asocia con el lock. **Los scripts de `scripts/farm/` usan
|
|
el estilo `exec 9>` + `flock 9`, que es vulnerable igual** (haría falta `9>&-` en cada hijo): deuda
|
|
conocida, no barrida.
|
|
|
|
`scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh` ya toman **ese mismo fichero de lock**, así
|
|
que usarlo nos serializa con la granja además de entre nosotros. **No está dentro de `takana build`
|
|
a propósito**: esos scripts lo toman por fuera y takana se bloquearía contra ellos.
|
|
|
|
## 1 bis. El worker es el LXC PRESTADO, y es GRATIS: no se levanta nada en Hetzner
|
|
|
|
**`dev.gioser.net` (154.197.1.13, hostname interno `PruebasIA`) es dónde se compila.** Es un LXC
|
|
prestado en el Proxmox de gioser: 6 cores, 16 G RAM + 8 G swap, 196 G de disco, **coste €0**.
|
|
Se usa exactamente por eso — **para no gastar Hetzner**.
|
|
|
|
⇒ **No levantar cajas hcloud** (`farm-up.sh`, `farm-run.sh`) salvo que el usuario lo pida por su
|
|
nombre. El modelo efímero de pago sigue documentado y funcionando, pero está SUPERADO como sitio de
|
|
trabajo desde el 2026-09-05. Un `farm-up` por reflejo cuesta dinero real y no hace falta.
|
|
|
|
```sh
|
|
ssh -i ~/.ssh/github5 root@dev.gioser.net # github5 está en su authorized_keys
|
|
ssh -i ~/.ssh/sergio root@dev.gioser.net # la otra que entra; NO hay entrada en ssh/config
|
|
```
|
|
|
|
**Se enchufa por `scripts/farm/.fleet`** (`<nombre> <ip>` por línea), que **está en `.gitignore`**:
|
|
o sea que un hub recién clonado nace con la flota VACÍA y la granja queda desconectada **sin que
|
|
nada falle** — la cosecha dice «flota vacía» cada 30 min y los artefactos del worker no vuelven.
|
|
Pasó, y se descubrió por casualidad. Si `estado-granja.sh` dice «no hay worker vivo», mirá `.fleet`
|
|
antes de creerle.
|
|
|
|
echo "dev.gioser.net 154.197.1.13" > scripts/farm/.fleet
|
|
|
|
⚠ **Y hasta el 2026-09-08 eso sobrevivía por una CASUALIDAD DE NOMBRE.** El reaper de
|
|
`cosecha-cron.sh` saca de `.fleet` a todo lo que no esté en hcloud; 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 y no tiene nada que ver con él. Medido: el mismo host llamado `pruebasia-lxc` era expulsado
|
|
en el primer ciclo. **Arreglado** — ahora el reaper pregunta por SSH si el host responde en vez de
|
|
mirarle el nombre — pero la lección queda: *una protección que funciona por accidente se ve igual
|
|
que una que funciona por diseño*.
|
|
|
|
## 2. Nunca `git add -A` — y acotar el COMMIT, no sólo el `add`
|
|
|
|
Sólo rutas explícitas. Un `add -A` arrastra al commit los ficheros a medias de otro agente. Commits
|
|
granulares, en español, directo sobre `main`, y `git push` tras cada unidad de trabajo (el `origin`
|
|
empuja a gitea **y** al espejo privado de GitHub; ver `scripts/espejo-setup.sh`).
|
|
|
|
⚠ **«Rutas explícitas» en el `add` NO ALCANZA, y esto es medido, no teórico** (2026-09-06): el
|
|
commit `ff0b556` se llevó dentro un rename y un borrado de otro agente **habiendo usado
|
|
`git add recipes/firefox.toml` y `git commit` sin `-a`**. La causa es que **el índice es estado
|
|
COMPARTIDO**: `git commit` commitea el índice ENTERO, no lo que vos acabás de añadir, así que
|
|
cualquier cosa que el otro agente dejó en `git add` viaja en tu commit. Comprobado en un repo de
|
|
juguete, en los dos sentidos:
|
|
|
|
```sh
|
|
git add mio.txt && git commit -m … # ⇒ arrastra ajeno.txt (estaba staged por el otro)
|
|
git commit -m … -- mio.txt # ⇒ SÓLO mio.txt; lo del otro queda staged e intacto
|
|
```
|
|
|
|
**Entonces: `git commit -m "…" -- <rutas>`.** El `--` acota el commit por pathspec y es lo único que
|
|
aísla de verdad. Vale también para `git commit -F -`. Corolario: no dar por bueno el alcance sin
|
|
mirarlo — `git show --stat` sobre el commit recién hecho cuesta un segundo y es la única forma de
|
|
enterarse el mismo día en vez de por el otro agente.
|
|
|
|
## 3. Antes de dar un artefacto por presente, mirá que tenga contenido
|
|
|
|
Un directorio **vacío** en el store no es un artefacto: es un nombre. `Store::has` ya lo rechaza y
|
|
`respaldo-storagebox.sh --listar` los separa a `work/respaldo-vacios.txt`, pero la regla general
|
|
sigue valiendo para cualquier código nuevo — **un ausente falla ruidosamente; un vacío llega hasta
|
|
el final diciendo que todo fue bien**.
|
|
|
|
## 4. La superficie de la CLI va en INGLÉS; los mensajes, en castellano
|
|
|
|
Subcomandos, flags y nombres de opciones: **inglés** (`build`, `hash`, `hydrate`, `mirror push`,
|
|
`kernel closure`, `qorpa pull`). Lo que el usuario LEE —ayuda, errores, logs— va en castellano, y
|
|
los nombres propios del proyecto son quechua (`takana`, `qorpa`, `harkaq`, `yupana`, `arje`).
|
|
|
|
**La única excepción, y es cerrada:** el ADR 0016 admite **alias de marca** en castellano sobre un
|
|
verbo canónico —hoy uno solo, `forja` → `build`— para que la invocación de la hoja de marca
|
|
funcione. El alias **no reemplaza nada**: el canónico sigue siendo el inglés y es el que usan los
|
|
scripts, el cron y los runbooks. Un alias es superficie de marca; el contrato es el canónico.
|
|
Agregar otro alias es una decisión de ADR, no algo que se hace de paso.
|
|
|
|
**Por qué.** Un verbo de CLI es contrato: entra en scripts, cron y runbooks, y renombrarlo después
|
|
rompe llamadores que nadie recuerda. Media superficie en cada idioma obliga a adivinar en cada
|
|
comando nuevo, y ya pasó: el ADR 0015 nació con `traer/crear/correr` y hubo que corregirlo.
|
|
|
|
⚠ **Deuda conocida, NO barrida todavía:** varios scripts de `scripts/` exponen flags en castellano
|
|
(`--crear`, `--traer`, `--listar`, `--sha`). El barrido es su propia unidad de trabajo — tocarlos de
|
|
paso rompe cron y la granja. Código NUEVO nace en inglés desde hoy.
|