# 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 ``` 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/-` 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`** (` ` 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 "…" -- `.** 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. ⚠ **Pero el `--` NO protege la ruta que vos nombrás, y la intuición es la contraria** (medido 2026-09-15, repo de juguete): con un fichero en estado `MM` —stageado *y* modificado por otro—, `git commit -- f.txt` commitea **el ÁRBOL, no lo stageado**, y además deja el índice en la versión del árbol, o sea que **lo que el otro agente tenía stageado en ESA ruta desaparece**. ```sh # f.txt: v2 en el índice (del otro), v3 en el árbol (mío) git commit -m … -- f.txt # ⇒ commitea v3 · el índice queda en v3 · v2 se perdió ``` El pathspec aísla de lo que el otro dejó en **otras** rutas — que es lo del párrafo de arriba y sigue siendo cierto. Corolario: **un fichero que aparece `MM` y que vos no tocaste no se commitea ni con pathspec — se avisa.** Pasó con el `Cargo.lock` de tawasuyu (SDD 26 §7.quinquies), donde el árbol del otro agente ya traía la reparación que hacía falta. ## 2 ter. `git pull --rebase` puede DESCARTAR tu commit sin decir nada ⚠ **Medido acá el 2026-09-16, y cuesta una hora de trabajo si no sabés mirar el reflog.** Secuencia real: commit propio en `main` (10 ficheros nuevos) → `git push` → **gitea lo rechaza** porque otro agente empujó → `git pull --rebase` → el push vuelve a fallar y **el commit ya no está**, ni en el log ni en el ÁRBOL: los diez ficheros habían desaparecido del disco. El reflog lo explica y es lo único que lo explica: ``` 11a9ead6 HEAD@{0}: pull --rebase (finish): returning to refs/heads/main 11a9ead6 HEAD@{1}: pull --rebase (start): checkout 11a9ead6 f3d7be6c HEAD@{2}: commit: los 9 daemons propios entran al catálogo… ← el mío ``` **Entre `start` y `finish` no hay ni un `pick`**: el rebase no encontró NADA que reaplicar. Eso pasa cuando, en el momento del `pull`, el commit propio ya no colgaba de `main` — otro agente movió la rama (los tres commits que trajo el `pull` tienen de padre al commit ANTERIOR al mío, así que main había retrocedido). El `--rebase` no rompió nada: rebasó una rama de la que tu commit ya había sido sacado, y como `-q` calla, el resultado se ve igual que un pull limpio. **Qué hacer:** ```sh git reflog -15 # tu commit está acá, con su sha git cherry-pick # vuelve entero, ficheros incluidos ``` Y la comprobación que evita el susto: **`git log --oneline -1` después de cada `pull --rebase`.** Si tu commit no es el primero, no se rebasó — se perdió de la rama, y el reflog lo tiene. ⚠ **El espejo de GitHub queda DIVERGENTE en ese escenario**, y es normal: el `push` doble puede triunfar en un remoto y fallar en el otro (pasó: gitea rechazó, GitHub aceptó). Tras recuperar el commit, el espejo tiene un sha huérfano con el mismo contenido. Se repara empujando **sólo la URL de GitHub** con `--force-with-lease=main:` — con lease, que verifica que el remoto sigue donde creés antes de pisarlo. Comprobar antes que el contenido no se pierde: `git diff --stat HEAD`. ## 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.