tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ 1ca210a311ecd2a8f8cd6db07b68853b34b27e99 CLAUDE.md Markdown · 196 siq'ikuna · 11.7 KB Ñawpa willay ch'uya

regla 2 ter: el comando NO es el culpable — probado en un repo de juguete 61bf2cfd9 · Sergio ·

1# Reglas del repo compartido
2
3Este repo lo trabajan **varios agentes a la vez** (hoy: frente granja/store y frente kernel). Lo de
4aquí abajo no son preferencias de estilo: son las dos formas conocidas de que un agente destruya el
5trabajo de otro sin enterarse. El resto del diseño está en `docs/`.
6
7## 1. Todo `takana build` va envuelto en `flock`
8
9```sh
10flock work/.farm-build.lock ./target/release/takana --store ./store build <receta>
11```
12
13Para una tanda, tomar el lock una sola vez y no por receta:
14
15```sh
16flock work/.farm-build.lock bash -c 'for r in ...; do ./target/release/takana --store ./store build "$r"; done'
17```
18
19**Por qué.** `takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds
20concurrentes que compartan una dependencia se pisan el árbol de fuentes: uno hace fetch y lo borra
21mientras el otro lo usa, y **el árbol queda roto para siempre** — reintentar no lo arregla. Medido a
22escala: al invalidar libdrm, ~93 de 205 recetas KDE murieron con `/src/.zwrap/cc is not a full path
23to an existing compiler tool`, que es el wrapper de zig que la receta deja EN EL ÁRBOL, barrido por
24el fetch concurrente de otra receta. Es el **ADR 0012**, sin decidir; hasta que se decida
25(lock por árbol / árbol privado / caché inmutable + copia), serializar es la única mitigación
26correcta. Por eso el worker corre con `JOBS=1`.
27
28⚠ **Y usar `flock -o`, no `flock` a secas — esto es medido, no teórico (2026-09-08).** El lock lo
29sostiene la *descripción de fichero abierta*, y los hijos la HEREDAN: si un nieto se fuga, el lock
30queda tomado para siempre aunque el `flock` haya terminado hace rato. Pasó: dos `firefox` colgados de
31una caza de bugs sobrevivieron al `kill` del `bwrap` que los envolvía y **dejaron a la granja sin
32poder compilar durante hora y media, en silencio** — nada falla, simplemente el siguiente `flock`
33espera para siempre. Comprobado en los dos sentidos:
34
35```sh
36flock    lock sh -c 'sleep 25 & exit 0'   # ⇒ el nieto RETIENE el lock tras salir flock
37flock -o lock sh -c 'sleep 25 & exit 0'   # ⇒ lock LIBRE; -o cierra el fd antes de ejecutar
38```
39
40Si el lock parece tomado y no hay ningún build, `fuser -v work/.farm-build.lock` dice quién lo tiene;
41casi siempre es un proceso fugado que nadie asocia con el lock. **Los scripts de `scripts/farm/` usan
42el estilo `exec 9>` + `flock 9`, que es vulnerable igual** (haría falta `9>&-` en cada hijo): deuda
43conocida, no barrida.
44
45`scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh` ya toman **ese mismo fichero de lock**, así
46que usarlo nos serializa con la granja además de entre nosotros. **No está dentro de `takana build`
47a propósito**: esos scripts lo toman por fuera y takana se bloquearía contra ellos.
48
49## 1 bis. El worker es el LXC PRESTADO, y es GRATIS: no se levanta nada en Hetzner
50
51**`dev.gioser.net` (154.197.1.13, hostname interno `PruebasIA`) es dónde se compila.** Es un LXC
52prestado en el Proxmox de gioser: 6 cores, 16 G RAM + 8 G swap, 196 G de disco, **coste €0**.
53Se usa exactamente por eso — **para no gastar Hetzner**.
54
55⇒ **No levantar cajas hcloud** (`farm-up.sh`, `farm-run.sh`) salvo que el usuario lo pida por su
56nombre. El modelo efímero de pago sigue documentado y funcionando, pero está SUPERADO como sitio de
57trabajo desde el 2026-09-05. Un `farm-up` por reflejo cuesta dinero real y no hace falta.
58
59```sh
60ssh -i ~/.ssh/github5 root@dev.gioser.net     # github5 está en su authorized_keys
61ssh -i ~/.ssh/sergio  root@dev.gioser.net     # la otra que entra; NO hay entrada en ssh/config
62```
63
64**Se enchufa por `scripts/farm/.fleet`** (`<nombre> <ip>` por línea), que **está en `.gitignore`**:
65o sea que un hub recién clonado nace con la flota VACÍA y la granja queda desconectada **sin que
66nada falle** — la cosecha dice «flota vacía» cada 30 min y los artefactos del worker no vuelven.
67Pasó, y se descubrió por casualidad. Si `estado-granja.sh` dice «no hay worker vivo», mirá `.fleet`
68antes de creerle.
69
70    echo "dev.gioser.net 154.197.1.13" > scripts/farm/.fleet
71
72⚠ **Y hasta el 2026-09-08 eso sobrevivía por una CASUALIDAD DE NOMBRE.** El reaper de
73`cosecha-cron.sh` saca de `.fleet` a todo lo que no esté en hcloud; el LXC se salvaba sólo porque su
74nombre contiene la subcadena `gioser` y caía en una lista negra que existe para proteger al HUB de
75Hetzner y no tiene nada que ver con él. Medido: el mismo host llamado `pruebasia-lxc` era expulsado
76en el primer ciclo. **Arreglado** — ahora el reaper pregunta por SSH si el host responde en vez de
77mirarle el nombre — pero la lección queda: *una protección que funciona por accidente se ve igual
78que una que funciona por diseño*.
79
80## 2. Nunca `git add -A` — y acotar el COMMIT, no sólo el `add`
81
82Sólo rutas explícitas. Un `add -A` arrastra al commit los ficheros a medias de otro agente. Commits
83granulares, en español, directo sobre `main`, y `git push` tras cada unidad de trabajo (el `origin`
84empuja a gitea **y** al espejo privado de GitHub; ver `scripts/espejo-setup.sh`).
85
86⚠ **«Rutas explícitas» en el `add` NO ALCANZA, y esto es medido, no teórico** (2026-09-06): el
87commit `ff0b556` se llevó dentro un rename y un borrado de otro agente **habiendo usado
88`git add recipes/firefox.toml` y `git commit` sin `-a`**. La causa es que **el índice es estado
89COMPARTIDO**: `git commit` commitea el índice ENTERO, no lo que vos acabás de añadir, así que
90cualquier cosa que el otro agente dejó en `git add` viaja en tu commit. Comprobado en un repo de
91juguete, en los dos sentidos:
92
93```sh
94git add mio.txt && git commit -m …          # ⇒ arrastra ajeno.txt (estaba staged por el otro)
95git commit -m … -- mio.txt                  # ⇒ SÓLO mio.txt; lo del otro queda staged e intacto
96```
97
98**Entonces: `git commit -m "…" -- <rutas>`.** El `--` acota el commit por pathspec y es lo único que
99aísla de verdad. Vale también para `git commit -F -`. Corolario: no dar por bueno el alcance sin
100mirarlo — `git show --stat` sobre el commit recién hecho cuesta un segundo y es la única forma de
101enterarse el mismo día en vez de por el otro agente.
102
103⚠ **Pero el `--` NO protege la ruta que vos nombrás, y la intuición es la contraria** (medido
1042026-09-15, repo de juguete): con un fichero en estado `MM` —stageado *y* modificado por otro—,
105`git commit -- f.txt` commitea **el ÁRBOL, no lo stageado**, y además deja el índice en la versión
106del árbol, o sea que **lo que el otro agente tenía stageado en ESA ruta desaparece**.
107
108```sh
109# f.txt: v2 en el índice (del otro), v3 en el árbol (mío)
110git commit -m … -- f.txt     # ⇒ commitea v3 · el índice queda en v3 · v2 se perdió
111```
112
113El pathspec aísla de lo que el otro dejó en **otras** rutas — que es lo del párrafo de arriba y
114sigue siendo cierto. Corolario: **un fichero que aparece `MM` y que vos no tocaste no se commitea
115ni con pathspec — se avisa.** Pasó con el `Cargo.lock` de tawasuyu (SDD 26 §7.quinquies), donde el
116árbol del otro agente ya traía la reparación que hacía falta.
117
118## 2 ter. `git pull --rebase` puede DESCARTAR tu commit sin decir nada
119
120⚠ **Medido acá el 2026-09-16, y cuesta una hora de trabajo si no sabés mirar el reflog.**
121
122Secuencia real: commit propio en `main` (10 ficheros nuevos) → `git push` → **gitea lo rechaza**
123porque otro agente empujó → `git pull --rebase` → el push vuelve a fallar y **el commit ya no está**,
124ni en el log ni en el ÁRBOL: los diez ficheros habían desaparecido del disco.
125
126El reflog lo explica y es lo único que lo explica:
127
128```
12911a9ead6 HEAD@{0}: pull --rebase (finish): returning to refs/heads/main
13011a9ead6 HEAD@{1}: pull --rebase (start): checkout 11a9ead6
131f3d7be6c HEAD@{2}: commit: los 9 daemons propios entran al catálogo…     ← el mío
132```
133
134**Entre `start` y `finish` no hay ni un `pick`**: el rebase no encontró NADA que reaplicar, y con
135`-q` eso se ve igual que un pull limpio.
136
137⚠ **PASÓ DOS VECES EL MISMO DÍA** (2026-09-16 y 2026-09-17), con el mismo reflog: `commit` propio,
138`(start): checkout <otro>`, `(finish): returning to refs/heads/main` — y ni un `pick` en el medio.
139
140**Y el comando NO es el culpable: se reprodujo en un repo de juguete y ahí funciona bien.** Dos
141clones, el otro empuja, yo commiteo local, `git pull --rebase -q origin main` ⇒ **mi commit se
142reaplica y mi fichero sigue ahí**. O sea que lo que falla no es `pull --rebase`: es que este árbol
143lo comparten varios agentes y **alguien mueve `refs/heads/main` mientras el rebase está en vuelo**.
144La receta práctica no cambia; el diagnóstico sí, y evita ir a buscar el bug al lugar equivocado.
145
146⚠ **La CAUSA exacta no está determinada, y conviene decirlo así en vez de inventarla.** Lo medido es el
147reflog de arriba y que los diez ficheros desaparecieron también del ÁRBOL, que es lo que un
148`reset --hard` o un `checkout` concurrente de otro agente sí explicaría. Lo que NO es evidencia —y
149se escribió como si lo fuera— es que los commits que trajo el `pull` cuelguen del commit anterior al
150mío: eso es lo normal cuando el otro lado empujó desde una máquina que había hecho `fetch` antes, y
151no dice nada de lo que pasó acá. **Que el árbol perdiera los ficheros es el dato fuerte; el resto,
152hipótesis.**
153
154**Qué hacer:**
155
156```sh
157git reflog -15                      # tu commit está acá, con su sha
158git cherry-pick <sha>               # vuelve entero, ficheros incluidos
159```
160
161Y la comprobación que evita el susto: **`git log --oneline -1` después de cada `pull --rebase`.** Si
162tu commit no es el primero, no se rebasó — se perdió de la rama, y el reflog lo tiene.
163
164⚠ **El espejo de GitHub queda DIVERGENTE en ese escenario**, y es normal: el `push` doble puede
165triunfar en un remoto y fallar en el otro (pasó: gitea rechazó, GitHub aceptó). Tras recuperar el
166commit, el espejo tiene un sha huérfano con el mismo contenido. Se repara empujando **sólo la URL de
167GitHub** con `--force-with-lease=main:<sha-huérfano>` — con lease, que verifica que el remoto sigue
168donde creés antes de pisarlo. Comprobar antes que el contenido no se pierde:
169`git diff --stat <huérfano> HEAD`.
170
171## 3. Antes de dar un artefacto por presente, mirá que tenga contenido
172
173Un directorio **vacío** en el store no es un artefacto: es un nombre. `Store::has` ya lo rechaza y
174`respaldo-storagebox.sh --listar` los separa a `work/respaldo-vacios.txt`, pero la regla general
175sigue valiendo para cualquier código nuevo — **un ausente falla ruidosamente; un vacío llega hasta
176el final diciendo que todo fue bien**.
177
178## 4. La superficie de la CLI va en INGLÉS; los mensajes, en castellano
179
180Subcomandos, flags y nombres de opciones: **inglés** (`build`, `hash`, `hydrate`, `mirror push`,
181`kernel closure`, `qorpa pull`). Lo que el usuario LEE —ayuda, errores, logs— va en castellano, y
182los nombres propios del proyecto son quechua (`takana`, `qorpa`, `harkaq`, `yupana`, `arje`).
183
184**La única excepción, y es cerrada:** el ADR 0016 admite **alias de marca** en castellano sobre un
185verbo canónico —hoy uno solo, `forja` → `build`— para que la invocación de la hoja de marca
186funcione. El alias **no reemplaza nada**: el canónico sigue siendo el inglés y es el que usan los
187scripts, el cron y los runbooks. Un alias es superficie de marca; el contrato es el canónico.
188Agregar otro alias es una decisión de ADR, no algo que se hace de paso.
189
190**Por qué.** Un verbo de CLI es contrato: entra en scripts, cron y runbooks, y renombrarlo después
191rompe llamadores que nadie recuerda. Media superficie en cada idioma obliga a adivinar en cada
192comando nuevo, y ya pasó: el ADR 0015 nació con `traer/crear/correr` y hubo que corregirlo.
193
194⚠ **Deuda conocida, NO barrida todavía:** varios scripts de `scripts/` exponen flags en castellano
195(`--crear`, `--traer`, `--listar`, `--sha`). El barrido es su propia unidad de trabajo — tocarlos de
196paso rompe cron y la granja. Código NUEVO nace en inglés desde hoy.

CLAUDE.md

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

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:

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:

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.

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:

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.

⚠ 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.

# 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, y con -q eso se ve igual que un pull limpio.

⚠ PASÓ DOS VECES EL MISMO DÍA (2026-09-16 y 2026-09-17), con el mismo reflog: commit propio, (start): checkout <otro>, (finish): returning to refs/heads/main — y ni un pick en el medio.

Y el comando NO es el culpable: se reprodujo en un repo de juguete y ahí funciona bien. Dos clones, el otro empuja, yo commiteo local, git pull --rebase -q origin main ⇒ mi commit se reaplica y mi fichero sigue ahí. O sea que lo que falla no es pull --rebase: es que este árbol lo comparten varios agentes y alguien mueve refs/heads/main mientras el rebase está en vuelo. La receta práctica no cambia; el diagnóstico sí, y evita ir a buscar el bug al lugar equivocado.

⚠ La CAUSA exacta no está determinada, y conviene decirlo así en vez de inventarla. Lo medido es el reflog de arriba y que los diez ficheros desaparecieron también del ÁRBOL, que es lo que un reset --hard o un checkout concurrente de otro agente sí explicaría. Lo que NO es evidencia —y se escribió como si lo fuera— es que los commits que trajo el pull cuelguen del commit anterior al mío: eso es lo normal cuando el otro lado empujó desde una máquina que había hecho fetch antes, y no dice nada de lo que pasó acá. Que el árbol perdiera los ficheros es el dato fuerte; el resto, hipótesis.

Qué hacer:

git reflog -15                      # tu commit está acá, con su sha
git cherry-pick <sha>               # 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:<sha-huérfano> — 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 <huérfano> 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.