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.
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 `hammer build` va envuelto en `flock`
|
|
|
|
```sh
|
|
flock work/.farm-build.lock ./target/release/hammer --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/hammer --store ./store build "$r"; done'
|
|
```
|
|
|
|
**Por qué.** `hammer 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 `hammer build`
|
|
a propósito**: esos scripts lo toman por fuera y hammer 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.
|