1# ADR 0016 — Renombre del sistema: `hammer` → `takana`
2
3- **Estado:** ACEPTADO (decisión del usuario, 2026-09-09)
4- ⚠ **Este fichero queda FUERA de todo barrido de renombre.** Habla *sobre* el cambio de nombre, así
5 que necesita seguir diciendo `hammer` donde corresponde. El barrido de la etapa 5 lo pasó por
6 arriba y lo dejó diciendo «Renombre del sistema: takana → takana»; se revirtió. Si alguien vuelve
7 a barrer docs, excluir este fichero explícitamente.
8- **Reemplaza:** nada. **Afecta:** toda la superficie de CLI (regla 4 de `CLAUDE.md`).
9
10## Contexto
11
12Los nombres propios del proyecto se reparten por **rol**, no por estética: `khipu` registra
13(`docs/state/build-state*.json`), `yupana` reckona sobre el khipu (`scripts/yupana.py`), `harkaq`
14guarda (la jaula), `qorpa` hospeda (imágenes ajenas), `churay` pone (distribución multi-origen).
15Cada uno dice qué hace su pieza. El único nombre que designa **el todo** es el del constructor.
16
17`takana` (quechua y aimara: *martillo, mazo*) es la traducción literal de `hammer`. Conserva la
18metáfora que el proyecto ya usa —forja, yunque, sellar— y mantiene el registro agentivo del resto de
19la familia. Es hermana de `tawasuyu`: el territorio (apps, compositor) × la herramienta (la forja).
20
21Se descartaron: `chani` (valor/precio — nombra el *hash*, no el motor; y "valor" es cosa, no rol),
22`tocapu` (la marca inscripta — mismo hueco que `chani`), `anta` (cobre — es materia, no rol; y no
23suena quechua: sin q, k, h, ll ni ñ).
24
25## Decisión
26
271. El sistema se llama **takana**. La marca vive en `docs/marca/`.
282. El renombre va **por etapas con alias**, nunca de un saque. `hammer` sigue funcionando hasta que
29 el último llamador migre.
303. Se adoptan los dos puntos de la hoja de marca que chocaban con contratos vigentes —el verbo
31 `forja` y la extensión `.tkn`—, implementados sin romper llamadores (ver §La hoja de marca).
32
33## Lo medido (2026-09-09)
34
35Nada de esto es estimación; sale de `git grep` y de leer `recipe.rs`.
36
37| qué | cuánto | consecuencia |
38|---|---|---|
39| ficheros que nombran `hammer` | 1242 (5779 ocurrencias) | radio total |
40| en `recipes/`, y son **comentarios** de importación | 692 | **gratis**: ver abajo |
41| recetas con `.hammer-zig-cc` **dentro de una fase** | 10 | **caro**: re-hashea |
42| recetas con `cargo_vendor_dir = ".hammer-cargo-vendor"` | 5 | **congelar**: ver abajo |
43| ficheros que **invocan** la CLI | 124 | migración real |
44| crates `hammer-*` (+ `hammerd`) | 12 | renombre de paquetes |
45
46**El hallazgo que decide todo:** `Recipe::hash_inputs` (`crates/hammer-core/src/recipe.rs:535`) no
47hashea el fichero crudo — construye una **lista de campos**: `source_id`, `compiler`, `target`,
48`link`, huella del lab, `zig_version`/`strip_debug` si están fijados, los patches, los `flags`, las
49**fases** (`configure`/`compile`/`install`) y los hashes de las deps.
50
51- ⇒ **Los comentarios no entran.** Renombrar «hammer» en los 692 comentarios de recetas importadas
52 **no mueve ni un ArtifactHash**. Es churn de diff, no de build.
53- ⇒ **Las fases sí entran**, con etiqueta (`phase:compile=…`). Las 10 recetas que escriben
54 `.hammer-zig-cc` lo hacen *dentro* de la fase: renombrar ese literal las re-hashea y obliga a
55 reconstruirlas **y a todo su cono descendente**. El literal es un temporal interno del árbol de
56 build, invisible al usuario. **Se congela como está.**
57- ⇒ `cargo_vendor_dir` **no** está en `hash_inputs`. Renombrar `.hammer-cargo-vendor` sería
58 invisible al hash **pero puede cambiar los bytes del artefacto** — la misma familia de problema
59 que «el lab está fuera de `hash_inputs`». **Se congela como está.**
60
61**Y el cron apunta a una ruta absoluta:** `*/30 * * * * /mnt/vvv/hammer/scripts/farm/cosecha-cron.sh`.
62Renombrar el **directorio** del repo mata el latido de la granja **en silencio** — no falla nada,
63simplemente deja de cosechar. El directorio es lo **último** que se toca, y con el cron parado.
64
65## Plan por etapas
66
671. **Marca + este ADR.** Aditivo, no rompe nada. ← *hecho*
682. **Alias.** ← *hecho (2026-09-09)*. El binario canónico es `takana`; `hammer` se sigue emitiendo.
69 Son **dos `[[bin]]` apuntando al mismo `main.rs`**, no un symlink: la siembra de la granja excluye
70 `/target`, así que un symlink hecho en el hub no existiría en el worker, y `cargo clean` lo borra.
71 Cuesta una recompilación del `main.rs` y un warning de cargo («present in multiple build targets»)
72 que se va solo en la etapa 6. Los dos nombres funcionan; no se tocó ningún llamador.
733. **Llamadores.** ← *hecho (2026-09-09)*, en dos commits y en este orden, que no es cosmético:
74 - **3a** — los `cargo build --release --bin hammer` pasan a `--bin takana --bin hammer` (6
75 sitios). Va **solo y primero**: el worker compila desde fuente (`farm-worker-loop.sh`) y la
76 siembra excluye `/target`. Si se cambiaran antes las invocaciones, habría una ventana en la
77 que el worker sincroniza scripts nuevos y sólo tiene el binario viejo — y eso **no falla
78 ruidosamente: deja de cosechar en silencio**.
79 - **3b** — las 49 invocaciones de `./target/release/hammer` → `takana`, en `scripts/`,
80 `docs/runbooks/` y `CLAUDE.md`. Verificado con `bash -n`/`py_compile` los 49 (ojo:
81 `why-differs-barrido.sh` es Python con extensión `.sh`) y, sobre todo, con el **ciclo real
82 del cron** inmediatamente posterior al cambio: `2026-09-09T18:30:00Z → 18:32:21Z`, siembra ✓,
83 manifiesto ✓, los 9 JSON del grafo regenerados (los produce `build-state.py`, que ahora invoca
84 `takana`) y estado commiteado+pusheado. Cero errores. Esa es la prueba que importa: la
85 regeneración del khipu pasa por el binario renombrado.
86
87 **Excluido a propósito de la etapa 3:**
88 - La variable de entorno `HAMMER=`. Es interfaz entre scripts y hay llamadores que la fijan;
89 renombrarla va con la etapa 4.
90 - `docs/evidencia/` y los `HANDOFF-*`: son **registro** de lo que se corrió ese día. Reescribir
91 un comando dentro de una evidencia la falsifica.
92 - `docs/state/`: generado, se regenera solo.
93 - ADRs y docs de diseño: texto, y `hammer` sigue funcionando. Van con la etapa 5.
94
95 **Cómo converge el worker** (medido el 2026-09-09, no supuesto). Su checkout vive en
96 `/opt/hammer`, **no es un clon git** —lo pone el rsync de la siembra— y `takana-farm.service`
97 está `enabled` allá, corriendo `farm-worker-loop.sh` como servicio largo. En el momento del
98 cambio el worker tenía scripts viejos y binario del 6-sep: **coherente**. Los dos estados
99 intermedios también lo son, y por eso la 3a iba primero:
100 - *Antes de reiniciar el servicio*: el loop viejo en memoria sigue llamando `hammer` y
101 recompilando `--bin hammer`, que existe. Funciona.
102 - *Después de reiniciar*: el loop nuevo compila **los dos** y usa `takana`; si `takana` todavía
103 no existe, `rebuild_si_hace_falta` compila y devuelve 0 —salta el ciclo, no aborta—.
104
105 La unit apunta a la RUTA DEL SCRIPT, no al binario, así que nada de la etapa 3 la toca. El
106 nombre del fichero de unit y `/opt/hammer` son etapa 6.
107
108 **Convergido y comprobado (2026-09-09 18:40–18:45Z).** Reiniciado `takana-farm.service`, el
109 worker compiló los dos binarios y **cerró un ciclo de build real con el nombre nuevo**:
110 `✓ dunst b3:29a79859…`, BUILD-YIELD 1/1, 1 promovido. No es que «no se rompió nada visible»: el
111 camino de construir y sellar se ejercitó entero.
1124. **Crates.** ← *hecho parcialmente (2026-09-09)*. Los 10 de librería + CLI
113 (`hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}`) son
114 `takana-*`. **Verificado que no mueve el corpus**: `takana hash recipes/zlib.toml` devuelve
115 `b3:dc363f26…`, idéntico a antes. 600 tests en verde.
116
117 **Dos binarios congelados, con razón medida:**
118 - `hammerd` — paquete **y** binario. Es componente de Stage 1 de la distro (musl, busybox,
119 hammerd, arje-zero), arje-zero lo supervisa en el sistema arrancado y `PRESEED=hammerd` lo
120 nombra en `selfhost-verify.sh`.
121 - `hammer-recover` — el paquete es `takana-recover`, **el binario sigue siendo
122 `hammer-recover`**: `hammer-live-install.sh` lo copia a `/usr/sbin/hammer-recover` en sistemas
123 **ya instalados** y hornea un hook de arranque que lo invoca por ese nombre. Renombrarlo no
124 rompe el repo: rompe máquinas instaladas.
125
126 **Consecuencia que hay que anotar igual:** al renombrar `hammer-core`, los bytes de `hammerd`
127 cambian de todos modos —linkea contra un crate con otro nombre y el nombre va en los símbolos—,
128 así que **el baseline `of_tree` del selfhost hay que rehacerlo**. Es efecto de la etapa 4, no de
129 un cambio en `hammerd`.
130
1314 bis. **Variables de entorno: leen las dos, gana la nueva.** ← *hecho (2026-09-09)*.
132
133 Se midió antes de tocarlas y el problema tenía otra forma que la del plan: no era «la variable
134 `HAMMER=`», eran **14**, ~260 apariciones en 58 ficheros, **y el binario las lee**. Con knobs del
135 instalador entre ellas, el entorno del worker trayéndolas puestas y `mirror-env.sh` exportándolas
136 — todo eso vive **fuera del repo**, en perfiles de shell y units. Un `sed` no falla ruidosamente:
137 la variable deja de aparecer, se toma el default y el build se comporta distinto en silencio.
138
139 - **Rust:** `takana_core::env::{var, var_os}` recibe el nombre **canónico** (`TAKANA_…`) y deriva
140 el viejo cambiando el prefijo. Se le pasa el nuevo a propósito, para que un `grep` del nombre
141 nuevo encuentre todas las lecturas. 5 tests, **con dos controles negativos**: sin ninguna de las
142 dos no hay valor, y un nombre sin prefijo `TAKANA_` no inventa una caída.
143 - Migrados los 17 sitios directos **y los indirectos que el grep de `env::var("HAMMER_…")` no
144 mostraba**: `bases_de_mirror`, las constantes de `kernel_cmd`, `ROOT_ENV` de qorpa y `env_path`
145 de recover. `takana-recover` lleva la caída **inline**: es un mini-binario que se copia a
146 `/usr/sbin` y no vale arrastrarle una dep entera por dos líneas.
147 - **Scripts:** 30 lecturas pasan a `${TAKANA_X:-${HAMMER_X:-default}}`, conservando el nombre
148 **interno** de la variable para no tocar sus 190 usos.
149 - **Donde el script EXPORTA en vez de leer, se ponen las dos** (`mirror-env.sh` y el fragmento
150 in-VM de `takana-bootstrap`). Ahí el lector puede ser un binario **viejo** —un worker sin
151 recompilar, el `/usr/bin/hammer` pinado del baseline— que sólo conoce `HAMMER_*`. La caída
152 sirve al lector nuevo; al viejo hay que seguirle dejando la suya puesta.
153
154 **Verificado con el binario, no sólo con unit tests:** `HAMMER_LAB` sigue surtiendo efecto,
155 `TAKANA_LAB` hace exactamente lo mismo, y con las dos puestas gana `TAKANA_LAB`. 605 tests en
156 verde y `hash zlib` sigue en `b3:dc363f26…`.
157
158 **Cambio de comportamiento que va dicho aparte:** el hostname por defecto de una instalación
159 nueva pasa de `hammer` a `takana` (sólo si no se fija ninguna de las dos variables).
160
161 **No se tocan:** las rutas `/var/lib/hammer` de sistemas ya instalados, el `-volid HAMMER_LIVE`
162 del ISO (es identidad horneada en el medio) y el namespace `HARKAQ_*`, que es de otro subsistema.
163
1645. **Texto.** ← *hecho (2026-09-09)*, en tres commits.
165
166 - **5a — recetas (741, 1427 comentarios).** Cero ArtifactHash movidos, y está **medido**: se
167 hashearon las 741 antes y después y los ficheros de hashes son idénticos byte a byte.
168
169 **El guardián valió la pena.** El primer barrido filtraba por «la línea empieza con `#`» y
170 movió el hash de `helix`, `lsof` y `steam-runtime-sniper`. La causa: una fase se escribe como
171 `compile = …` y sus comentarios de **shell** también empiezan con `#`, pero viven
172 dentro del **valor** — y las fases sí entran en `hash_inputs`. El barrido definitivo calcula
173 los rangos de las cadenas multilínea de TOML y no entra ahí. Sin la línea base de hashes esto
174 se descubría meses después, en un rebuild.
175
176 - **5b — 59 docs de diseño, runbooks y ADR.** Los ADR entran porque en **este** repo son
177 documentos vivos, no registros: el 0013 tiene 5 commits y el 0009 dos. Se comprobó antes de
178 decidir. **Excluidos** `docs/evidencia/`, el HANDOFF de la noche de KDE y `docs/state/`
179 (generado). **Y este fichero**, que habla *sobre* el renombre: el barrido lo dejó titulado
180 «Renombre del sistema: takana → takana».
181
182 - **5c — 250 comentarios en 151 scripts,** con el control de que el diff no toca ni una línea
183 que no empiece por `#`. El barrido saltea heredocs, y ese guardián **disparó 3 veces**: eran
184 los `MOTD` que el script escribe **dentro de la imagen construida** — texto del producto, no
185 comentario. Se cambiaron aparte, como rebranding deliberado.
186
187 **El hallazgo caro de la etapa 5, que en realidad es un bug que introdujo la 4:**
188 `scripts/atribuir-fallos.py` casaba contra `hammer_build: fetch …`. Ese prefijo es el **target
189 de `tracing`**, que por defecto es `module_path!()` — o sea **el nombre del crate**. Al renombrar
190 `hammer-build` → `takana-build`, el target pasó a `takana_build` y el script quedó **casando
191 nada**: no fallaba, imprimía cero atribuciones, indistinguible de un log sin problemas.
192 Comprobado con el binario (`RUST_LOG=info` sobre zlib), no deducido. Ahora acepta las dos, y
193 **tiene que seguir aceptándolas**: los logs viejos que ya están en disco dicen la forma vieja.
194
195 *(El mensaje del commit de 5c perdió cuatro fragmentos por sustitución de comandos del shell con
196 comillas invertidas. No se enmendó: `main` es compartida y el cron empuja cada 30 min, así que
197 reescribir historia es peor que un mensaje incompleto. El detalle vive acá.)*
198
199 **Congelados y verificados con controles:** `/opt/hammer`, `/var/lib/hammer`, `/usr/bin/hammer`,
200 `/mnt/vvv/hammer`, la URL de gitea, `takana-farm.service`, `hammer-live-install.sh`,
201 `BRIEFING-hammer.md`, `hammerd`, `hammer-recover`, `HAMMER_LIVE`, `.hammer-zig-cc`,
202 `.hammer-cargo-vendor` y dos identificadores más que aparecieron al barrer:
203 - `hammer-build-state/1` — el `schema` del estado generado. Nadie lo valida, pero el valor de un
204 identificador de esquema **es** ser estable; renombrarlo no gana nada.
205 - `hammer-edit` — es una **receta con artefacto sellado**: su `name` está en la ruta del store
206 (`store/cf075804…-hammer-edit`). Renombrarla huerfaniza el artefacto.
2075 bis. **El texto que faltaba** (comentarios de `crates/`, `CLAUDE.md`, el skill de granja).
208 ← *hecho*. 205 líneas. **Y el barrido ancho estuvo a un commit de romper el corpus entero**: el
209 primer intento reescribía los `.rs` completos y entre las líneas de código que tocaba había
210 **siete etiquetas de separación de dominio, que son entrada de hash**:
211
212 | literal | qué es |
213 |---|---|
214 | `b"hammer-tree-v1"` | **el prefijo de `ArtifactHash::of_tree`** (`takana-core/src/hash.rs:60`) — la función que hashea TODOS los artefactos. Cambiarlo mueve los 4750 hashes del store |
215 | `b"hammer-seed-v1"`, `b"hammer-stage1-rootfs-v2"`, `b"hammer-product-rootfs-v3"`, `b"hammer-product-attested-v2"`, `b"hammer-builder-rootfs-v1"` | entradas de hash de la cadena de bootstrap |
216 | `b"hammer-attest-dev-rootkey-0001!!"` | clave raíz de atestación, `[u8; 32]` |
217
218 Se revirtió y se rehízo sólo sobre comentarios, esquivando además las cadenas crudas de Rust
219 (`r#"…"#`), porque el `SYSTEM_PROMPT` del traductor tiene líneas que empiezan como comentario.
220 **La lección de toda la etapa 5: un literal que parece prosa puede ser entrada de hash, y la
221 única forma de saberlo es mirar dónde se usa.**
222
2236. **Retirar las compatibilidades.** ← *no es un barrido: son seis despliegues coordinados, y tres
224 no se hacen sin decidirlo.* Estado al 2026-09-09:
225
226 - ✅ **`--takana-bin`** con `--hammer-bin` de alias. Era la última superficie de CLI con el
227 nombre viejo.
228 - ✅ **Destrabado el retiro de las caídas de entorno.** Se midió que la unit del worker era el
229 **único** sitio fuera del repo que traía una variable vieja puesta
230 (`Environment=HAMMER_DIR=/opt/hammer`); ahora fija las dos, desplegada y verificada.
231 - ⏳ **Retirar el binario `hammer`** — quedan 0 llamadores en el repo y el worker usa `takana`.
232 Es la red de seguridad de lo que no se encontró: conviene esperar un ciclo completo de granja
233 y una corrida de selfhost antes de sacarla.
234 - ⏳ **Retirar las caídas `HAMMER_X`** — posible ya para `HAMMER_DIR`; el resto espera a
235 confirmar que ningún entorno las trae puestas.
236 - 🔒 **`/usr/bin/hammer` dentro del rootfs del producto** — está **dentro de un árbol que se
237 hashea**. Moverlo cambia el hash del producto y obliga a rehacer el baseline del selfhost.
238 Va junto con esa deuda, no antes.
239 - ⚠ **`takana-farm.service` y `/opt/hammer`** — despliegue coordinado en el worker: parar,
240 renombrar, `daemon-reload`, arrancar. Reversible, pero es hacia afuera.
241 - ✅ **`/mnt/vvv/hammer` → `/mnt/vvv/takana`, y el repo renombrado en gitea a `sergio/takana`**
242 (2026-09-09 ~20:00Z). Se coordinó primero: se avisó a las dos sesiones `hammer-*` y `hammer-9f`
243 contestó «vía libre», con los locks comprobados libres y sin builds en vuelo.
244
245 **Lo que hacía imposible un `mv` a secas** —y no estaba en el plan— eran **cuatro bind-mounts
246 desde `/mnt/cosecha` hacia dentro del árbol** (`store`, `work/out`, `.dev-fs/cache`,
247 `work/tarballs`), con sus entradas en `/etc/fstab`. Secuencia: sacar el cron, desmontar los
248 cuatro, `mv`, reescribir `fstab`, remontar, reponer el cron con la ruta nueva.
249
250 **Y dos cosas más que sólo aparecieron al mirar:**
251 - `~/hammer` era un **symlink a `/mnt/vvv/hammer`** y quedó **colgado**. Ahora hay `~/takana`
252 y `~/hammer` apuntando los dos a `/mnt/vvv/takana`, con la misma lógica de alias que el
253 binario.
254 - `hammer-9f` aportó dos sitios fuera del repo con la ruta absoluta que no estaban en el
255 inventario: `~/.config/zellij/layouts/rescate.kdl` y `~/.config/shuma/tandas.ron`.
256 Corregidos. *Avisar sirvió para algo más que la cortesía.*
257
258 **Verificado de punta a punta, no por inspección:** el ciclo del cron de las 20:00 corrió
259 completo desde la ruta nueva (`20:00:00Z → 20:02:18Z`), regeneró los nueve JSON del grafo y
260 **commiteó y pusheó al gitea renombrado**. Hash de zlib intacto, 1327 artefactos en el store.
261
262 El token de gitea que se creó para el renombre (`write:repository`) **se revocó** al terminar;
263 comprobado que responde 401.
264
265 Los `bwrap` de otra sesión que seguían vivos no se rompieron: `bwrap` resuelve sus montajes al
266 arrancar y el `mv` fue un rename, así que conservan el inodo.
267
268 **Deuda que sigue abierta desde la etapa 4:** rehacer el baseline `of_tree` del selfhost.
269
270Etapas 2–6 no arrancan hasta que la 1 esté pusheada, y ninguna se mezcla con otra en un commit.
271
272## La hoja de marca: los dos puntos que contradecían contratos vigentes
273
274La hoja (`docs/marca/README.md`) trae dos ejemplos que chocan con contratos ya escritos. Se
275plantearon como no adoptables; **el usuario los reafirmó el 2026-09-09 y se adoptaron**. Se
276implementaron de forma que la decisión se cumpla sin romper a los llamadores existentes.
277
278### 1. `forja` — verbo de marca en castellano
279
280Choca con la **regla 4** de `CLAUDE.md` («la superficie de CLI va en inglés»). Adoptado como
281**alias de clap sobre `build`**, no como reemplazo:
282
283- `takana forja <receta>` funciona — la invocación de la hoja de marca es real.
284- `takana build <receta>` sigue siendo el **canónico**: es lo que usan los 124 llamadores, el cron
285 y los runbooks, y ninguno se tocó.
286- `CLAUDE.md` §4 quedó **enmendada** en el mismo movimiento. Cambiar el comportamiento y dejar
287 escrito el contrato viejo es peor que cualquiera de las dos opciones: el otro agente del repo
288 sigue aplicando lo que lee.
289- La excepción es **cerrada**: un alias, el que está. Otro alias es decisión de ADR.
290
291### 2. `.tkn` — extensión del paquete
292
293Choca con `.swm` (266 menciones; Etapa F entera encima). Resultó **mucho más barato de lo
294estimado**, y por una razón medida: la extensión no es lógica, es salida.
295
296- Se escribe en **un solo lugar** (`crates/hammer-cli/src/main.rs:1800`).
297- El descubrimiento de paquetes va por **índice**, no por glob: `PackageEntry.file`
298 (`crates/hammer-core/src/repo.rs:75`) guarda el nombre real de cada fichero.
299- ⇒ **Los repos existentes no se rompen y un repo mixto es válido**: un índice con entradas `.swm`
300 sigue resolviendo, y los paquetes nuevos se escriben `.tkn`. No hace falta migrar nada.
301- Cero ficheros `.swm` versionados en el repo, así que no hubo qué renombrar.
302- **`Swm`, `SwmBuild`, `swm_path`, `swm_bridge` NO se tocan.** Son nombres de tipo y de módulo, no
303 superficie de usuario; renombrarlos es churn puro dentro de la etapa 4.
304
305## Nota sobre los ficheros de marca
306
307Los dos SVG difieren correctamente (chispa `#FFF6DE` sobre oscuro, `#17130E` sobre claro) —
308verificado por sha256 y por los colores presentes, no por el nombre del fichero. Ambos traen un
309manifiesto **C2PA** de procedencia embebido; se conserva tal cual vino, sin limpiar.
0016-renombre-takana.md
ADR 0016 — Renombre del sistema: hammer → takana
- Estado: ACEPTADO (decisión del usuario, 2026-09-09)
- ⚠ Este fichero queda FUERA de todo barrido de renombre. Habla sobre el cambio de nombre, así
que necesita seguir diciendo
hammer donde corresponde. El barrido de la etapa 5 lo pasó por
arriba y lo dejó diciendo «Renombre del sistema: takana → takana»; se revirtió. Si alguien vuelve
a barrer docs, excluir este fichero explícitamente.
- Reemplaza: nada. Afecta: toda la superficie de CLI (regla 4 de
CLAUDE.md).
Contexto
Los nombres propios del proyecto se reparten por rol, no por estética: khipu registra
(docs/state/build-state*.json), yupana reckona sobre el khipu (scripts/yupana.py), harkaq
guarda (la jaula), qorpa hospeda (imágenes ajenas), churay pone (distribución multi-origen).
Cada uno dice qué hace su pieza. El único nombre que designa el todo es el del constructor.
takana (quechua y aimara: martillo, mazo) es la traducción literal de hammer. Conserva la
metáfora que el proyecto ya usa —forja, yunque, sellar— y mantiene el registro agentivo del resto de
la familia. Es hermana de tawasuyu: el territorio (apps, compositor) × la herramienta (la forja).
Se descartaron: chani (valor/precio — nombra el hash, no el motor; y "valor" es cosa, no rol),
tocapu (la marca inscripta — mismo hueco que chani), anta (cobre — es materia, no rol; y no
suena quechua: sin q, k, h, ll ni ñ).
Decisión
- El sistema se llama takana. La marca vive en
docs/marca/.
- El renombre va por etapas con alias, nunca de un saque.
hammer sigue funcionando hasta que
el último llamador migre.
- Se adoptan los dos puntos de la hoja de marca que chocaban con contratos vigentes —el verbo
forja y la extensión .tkn—, implementados sin romper llamadores (ver §La hoja de marca).
Lo medido (2026-09-09)
Nada de esto es estimación; sale de git grep y de leer recipe.rs.
| qué | cuánto | consecuencia |
ficheros que nombran hammer | 1242 (5779 ocurrencias) | radio total |
en recipes/, y son comentarios de importación | 692 | gratis: ver abajo |
recetas con .hammer-zig-cc dentro de una fase | 10 | caro: re-hashea |
recetas con cargo_vendor_dir = ".hammer-cargo-vendor" | 5 | congelar: ver abajo |
| ficheros que invocan la CLI | 124 | migración real |
crates hammer-* (+ hammerd) | 12 | renombre de paquetes |
El hallazgo que decide todo: Recipe::hash_inputs (crates/hammer-core/src/recipe.rs:535) no
hashea el fichero crudo — construye una lista de campos: source_id, compiler, target,
link, huella del lab, zig_version/strip_debug si están fijados, los patches, los flags, las
fases (configure/compile/install) y los hashes de las deps.
- ⇒ Los comentarios no entran. Renombrar «hammer» en los 692 comentarios de recetas importadas
no mueve ni un ArtifactHash. Es churn de diff, no de build.
- ⇒ Las fases sí entran, con etiqueta (
phase:compile=…). Las 10 recetas que escriben
.hammer-zig-cc lo hacen dentro de la fase: renombrar ese literal las re-hashea y obliga a
reconstruirlas y a todo su cono descendente. El literal es un temporal interno del árbol de
build, invisible al usuario. Se congela como está.
- ⇒
cargo_vendor_dir no está en hash_inputs. Renombrar .hammer-cargo-vendor sería
invisible al hash pero puede cambiar los bytes del artefacto — la misma familia de problema
que «el lab está fuera de hash_inputs». Se congela como está.
Y el cron apunta a una ruta absoluta: */30 * * * * /mnt/vvv/hammer/scripts/farm/cosecha-cron.sh.
Renombrar el directorio del repo mata el latido de la granja en silencio — no falla nada,
simplemente deja de cosechar. El directorio es lo último que se toca, y con el cron parado.
Plan por etapas
-
Marca + este ADR. Aditivo, no rompe nada. ← hecho
-
Alias. ← hecho (2026-09-09). El binario canónico es takana; hammer se sigue emitiendo.
Son dos [[bin]] apuntando al mismo main.rs, no un symlink: la siembra de la granja excluye
/target, así que un symlink hecho en el hub no existiría en el worker, y cargo clean lo borra.
Cuesta una recompilación del main.rs y un warning de cargo («present in multiple build targets»)
que se va solo en la etapa 6. Los dos nombres funcionan; no se tocó ningún llamador.
-
Llamadores. ← hecho (2026-09-09), en dos commits y en este orden, que no es cosmético:
- 3a — los
cargo build --release --bin hammer pasan a --bin takana --bin hammer (6
sitios). Va solo y primero: el worker compila desde fuente (farm-worker-loop.sh) y la
siembra excluye /target. Si se cambiaran antes las invocaciones, habría una ventana en la
que el worker sincroniza scripts nuevos y sólo tiene el binario viejo — y eso no falla
ruidosamente: deja de cosechar en silencio.
- 3b — las 49 invocaciones de
./target/release/hammer → takana, en scripts/,
docs/runbooks/ y CLAUDE.md. Verificado con bash -n/py_compile los 49 (ojo:
why-differs-barrido.sh es Python con extensión .sh) y, sobre todo, con el ciclo real
del cron inmediatamente posterior al cambio: 2026-09-09T18:30:00Z → 18:32:21Z, siembra ✓,
manifiesto ✓, los 9 JSON del grafo regenerados (los produce build-state.py, que ahora invoca
takana) y estado commiteado+pusheado. Cero errores. Esa es la prueba que importa: la
regeneración del khipu pasa por el binario renombrado.
Excluido a propósito de la etapa 3:
- La variable de entorno
HAMMER=. Es interfaz entre scripts 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/: generado, se regenera solo.
- ADRs y docs de diseño: texto, y
hammer sigue funcionando. Van con la etapa 5.
Cómo converge el worker (medido el 2026-09-09, no supuesto). Su checkout vive en
/opt/hammer, no es un clon git —lo pone el rsync de la siembra— y takana-farm.service
está enabled allá, corriendo farm-worker-loop.sh como servicio largo. En el momento del
cambio el worker tenía scripts viejos y binario del 6-sep: coherente. Los dos estados
intermedios también lo son, y por eso la 3a iba primero:
- Antes de reiniciar el servicio: el loop viejo en memoria sigue llamando
hammer y
recompilando --bin hammer, que existe. Funciona.
- Después de reiniciar: el loop nuevo compila los dos y usa
takana; si takana todavía
no existe, rebuild_si_hace_falta compila y devuelve 0 —salta el ciclo, no aborta—.
La unit apunta a la RUTA DEL SCRIPT, no al binario, así que nada de la etapa 3 la toca. El
nombre del fichero de unit y /opt/hammer son etapa 6.
Convergido y comprobado (2026-09-09 18:40–18:45Z). Reiniciado takana-farm.service, el
worker compiló los dos binarios y cerró un ciclo de build real con el nombre nuevo:
✓ dunst b3:29a79859…, BUILD-YIELD 1/1, 1 promovido. No es que «no se rompió nada visible»: el
camino de construir y sellar se ejercitó entero.
-
Crates. ← hecho parcialmente (2026-09-09). Los 10 de librería + CLI
(hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}) son
takana-*. Verificado que no mueve el corpus: takana hash recipes/zlib.toml devuelve
b3:dc363f26…, idéntico a antes. 600 tests en verde.
Dos binarios congelados, con razón medida:
hammerd — paquete y binario. Es componente de Stage 1 de la distro (musl, busybox,
hammerd, arje-zero), arje-zero lo supervisa en el sistema arrancado y PRESEED=hammerd lo
nombra en selfhost-verify.sh.
hammer-recover — el paquete es takana-recover, el binario sigue siendo
hammer-recover: hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas
ya instalados y hornea un hook de arranque que lo invoca por ese nombre. Renombrarlo no
rompe el repo: rompe máquinas instaladas.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de hammerd
cambian de todos modos —linkea contra un crate con otro nombre y el nombre va en los símbolos—,
así que el baseline of_tree del selfhost hay que rehacerlo. Es efecto de la etapa 4, no de
un cambio en hammerd.
4 bis. Variables de entorno: leen las dos, gana la nueva. ← hecho (2026-09-09).
Se midió antes de tocarlas y el problema tenía otra forma que la del plan: no era «la variable
HAMMER=», eran 14, ~260 apariciones en 58 ficheros, y el binario las lee. Con knobs del
instalador entre ellas, el entorno del worker trayéndolas puestas y mirror-env.sh exportándolas
— todo eso vive fuera del repo, en perfiles de shell y units. Un sed no falla ruidosamente:
la variable deja de aparecer, se toma el default y el build se comporta distinto en silencio.
- Rust:
takana_core::env::{var, var_os} recibe el nombre canónico (TAKANA_…) y deriva
el viejo cambiando el prefijo. Se le pasa el nuevo a propósito, para que un grep del nombre
nuevo encuentre todas las lecturas. 5 tests, con dos controles negativos: sin ninguna de las
dos no hay valor, y un nombre sin prefijo TAKANA_ no inventa una caída.
- Migrados los 17 sitios directos y los indirectos que el grep de
env::var("HAMMER_…") no
mostraba: bases_de_mirror, las constantes de kernel_cmd, ROOT_ENV de qorpa y env_path
de recover. takana-recover lleva la caída inline: es un mini-binario que se copia a
/usr/sbin y no vale arrastrarle una dep entera por dos líneas.
- Scripts: 30 lecturas pasan a
${TAKANA_X:-${HAMMER_X:-default}}, conservando el nombre
interno de la variable para no tocar sus 190 usos.
- Donde el script EXPORTA en vez de leer, se ponen las dos (
mirror-env.sh y el fragmento
in-VM de takana-bootstrap). Ahí el lector puede ser un binario viejo —un worker sin
recompilar, el /usr/bin/hammer pinado del baseline— que sólo conoce HAMMER_*. La caída
sirve al lector nuevo; al viejo hay que seguirle dejando la suya puesta.
Verificado con el binario, no sólo con unit tests: HAMMER_LAB sigue surtiendo efecto,
TAKANA_LAB hace exactamente lo mismo, y con las dos puestas gana TAKANA_LAB. 605 tests en
verde y hash zlib sigue en b3:dc363f26….
Cambio de comportamiento que va dicho aparte: el hostname por defecto de una instalación
nueva pasa de hammer a takana (sólo si no se fija ninguna de las dos variables).
No se tocan: las rutas /var/lib/hammer de sistemas ya instalados, el -volid HAMMER_LIVE
del ISO (es identidad horneada en el medio) y el namespace HARKAQ_*, que es de otro subsistema.
-
Texto. ← hecho (2026-09-09), en tres commits.
-
5a — recetas (741, 1427 comentarios). Cero ArtifactHash movidos, y está medido: se
hashearon las 741 antes y después y los ficheros de hashes son idénticos byte a byte.
El guardián valió la pena. El primer barrido filtraba por «la línea empieza con #» y
movió el hash de helix, lsof y steam-runtime-sniper. La causa: una fase se escribe como
compile = … y sus comentarios de shell también empiezan con #, pero viven
dentro del valor — y las fases sí entran en hash_inputs. El barrido definitivo calcula
los rangos de las cadenas multilínea de TOML y no entra ahí. Sin la línea base de hashes esto
se descubría meses después, en un rebuild.
-
5b — 59 docs de diseño, runbooks y ADR. Los ADR entran porque en este repo son
documentos vivos, no registros: el 0013 tiene 5 commits y el 0009 dos. Se comprobó antes de
decidir. Excluidos docs/evidencia/, el HANDOFF de la noche de KDE y docs/state/
(generado). Y este fichero, que habla sobre el renombre: el barrido lo dejó titulado
«Renombre del sistema: takana → takana».
-
5c — 250 comentarios en 151 scripts, con el control de que el diff no toca ni una línea
que no empiece por #. El barrido saltea heredocs, y ese guardián disparó 3 veces: eran
los MOTD que el script escribe dentro de la imagen construida — texto del producto, no
comentario. Se cambiaron aparte, como rebranding deliberado.
El hallazgo caro de la etapa 5, que en realidad es un bug que introdujo la 4:
scripts/atribuir-fallos.py casaba contra hammer_build: fetch …. Ese prefijo es el target
de tracing, que por defecto es module_path!() — o sea el nombre del crate. Al renombrar
hammer-build → takana-build, el target pasó a takana_build y el script quedó casando
nada: no fallaba, imprimía cero atribuciones, indistinguible de un log sin problemas.
Comprobado con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas: los logs viejos que ya están en disco dicen la forma vieja.
(El mensaje del commit de 5c perdió cuatro fragmentos por sustitución de comandos del shell con
comillas invertidas. No se enmendó: main es compartida y el cron empuja cada 30 min, así que
reescribir historia es peor que un mensaje incompleto. El detalle vive acá.)
Congelados y verificados con controles: /opt/hammer, /var/lib/hammer, /usr/bin/hammer,
/mnt/vvv/hammer, la URL de gitea, takana-farm.service, hammer-live-install.sh,
BRIEFING-hammer.md, hammerd, hammer-recover, HAMMER_LIVE, .hammer-zig-cc,
.hammer-cargo-vendor y dos identificadores más que aparecieron al barrer:
hammer-build-state/1 — el schema del estado generado. Nadie lo valida, pero el valor de un
identificador de esquema es ser estable; renombrarlo no gana nada.
hammer-edit — es una receta con artefacto sellado: su name está en la ruta del store
(store/cf075804…-hammer-edit). Renombrarla huerfaniza el artefacto.
5 bis. El texto que faltaba (comentarios de crates/, CLAUDE.md, el skill de granja).
← hecho. 205 líneas. Y el barrido ancho estuvo a un commit de romper el corpus entero: el
primer intento reescribía los .rs completos y entre las líneas de código que tocaba había
siete etiquetas de separación de dominio, que son entrada de hash:
| literal | qué es |
b"hammer-tree-v1" | el prefijo de ArtifactHash::of_tree (takana-core/src/hash.rs:60) — la función que hashea TODOS los artefactos. Cambiarlo mueve los 4750 hashes del store |
b"hammer-seed-v1", b"hammer-stage1-rootfs-v2", b"hammer-product-rootfs-v3", b"hammer-product-attested-v2", b"hammer-builder-rootfs-v1" | entradas de hash de la cadena de bootstrap |
b"hammer-attest-dev-rootkey-0001!!" | clave raíz de atestación, [u8; 32] |
Se revirtió y se rehízo sólo sobre comentarios, esquivando además las cadenas crudas de Rust
(r#"…"#), porque el SYSTEM_PROMPT del traductor tiene líneas que empiezan como comentario.
La lección de toda la etapa 5: un literal que parece prosa puede ser entrada de hash, y la
única forma de saberlo es mirar dónde se usa.
-
Retirar las compatibilidades. ← no es un barrido: son seis despliegues coordinados, y tres
no se hacen sin decidirlo. Estado al 2026-09-09:
-
✅ --takana-bin con --hammer-bin de alias. Era la última superficie de CLI con el
nombre viejo.
-
✅ Destrabado el retiro de las caídas de entorno. Se midió que la unit del worker era el
único sitio fuera del repo que traía una variable vieja puesta
(Environment=HAMMER_DIR=/opt/hammer); ahora fija las dos, desplegada y verificada.
-
⏳ Retirar el binario hammer — quedan 0 llamadores en el repo y el worker usa takana.
Es la red de seguridad de lo que no se encontró: conviene esperar un ciclo completo de granja
y una corrida de selfhost antes de sacarla.
-
⏳ Retirar las caídas HAMMER_X — posible ya para HAMMER_DIR; el resto espera a
confirmar que ningún entorno las trae puestas.
-
🔒 /usr/bin/hammer dentro del rootfs del producto — está dentro de un árbol que se
hashea. Moverlo cambia el hash del producto y obliga a rehacer el baseline del selfhost.
Va junto con esa deuda, no antes.
-
⚠ takana-farm.service y /opt/hammer — despliegue coordinado en el worker: parar,
renombrar, daemon-reload, arrancar. Reversible, pero es hacia afuera.
-
✅ /mnt/vvv/hammer → /mnt/vvv/takana, y el repo renombrado en gitea a sergio/takana
(2026-09-09 ~20:00Z). Se coordinó primero: se avisó a las dos sesiones hammer-* y hammer-9f
contestó «vía libre», con los locks comprobados libres y sin builds en vuelo.
Lo que hacía imposible un mv a secas —y no estaba en el plan— eran cuatro bind-mounts
desde /mnt/cosecha hacia dentro del árbol (store, work/out, .dev-fs/cache,
work/tarballs), con sus entradas en /etc/fstab. Secuencia: sacar el cron, desmontar los
cuatro, mv, reescribir fstab, remontar, reponer el cron con la ruta nueva.
Y dos cosas más que sólo aparecieron al mirar:
~/hammer era un symlink a /mnt/vvv/hammer y quedó colgado. Ahora hay ~/takana
y ~/hammer apuntando los dos a /mnt/vvv/takana, con la misma lógica de alias que el
binario.
hammer-9f aportó dos sitios fuera del repo con la ruta absoluta que no estaban en el
inventario: ~/.config/zellij/layouts/rescate.kdl y ~/.config/shuma/tandas.ron.
Corregidos. Avisar sirvió para algo más que la cortesía.
Verificado de punta a punta, no por inspección: el ciclo del cron de las 20:00 corrió
completo desde la ruta nueva (20:00:00Z → 20:02:18Z), regeneró los nueve JSON del grafo y
commiteó y pusheó al gitea renombrado. Hash de zlib intacto, 1327 artefactos en el store.
El token de gitea que se creó para el renombre (write:repository) se revocó al terminar;
comprobado que responde 401.
Los bwrap de otra sesión que seguían vivos no se rompieron: bwrap resuelve sus montajes al
arrancar y el mv fue un rename, así que conservan el inodo.
Deuda que sigue abierta desde la etapa 4: rehacer el baseline of_tree del selfhost.
Etapas 2–6 no arrancan hasta que la 1 esté pusheada, y ninguna se mezcla con otra en un commit.
La hoja de marca: los dos puntos que contradecían contratos vigentes
La hoja (docs/marca/README.md) trae dos ejemplos que chocan con contratos ya escritos. Se
plantearon como no adoptables; el usuario los reafirmó el 2026-09-09 y se adoptaron. Se
implementaron de forma que la decisión se cumpla sin romper a los llamadores existentes.
1. forja — verbo de marca en castellano
Choca con la regla 4 de CLAUDE.md («la superficie de CLI va en inglés»). Adoptado como
alias de clap sobre build, no como reemplazo:
takana forja <receta> funciona — la invocación de la hoja de marca es real.
takana build <receta> sigue siendo el canónico: es lo que usan los 124 llamadores, el cron
y los runbooks, y ninguno se tocó.
CLAUDE.md §4 quedó enmendada en el mismo movimiento. Cambiar el comportamiento y dejar
escrito el contrato viejo es peor que cualquiera de las dos opciones: el otro agente del repo
sigue aplicando lo que lee.
- La excepción es cerrada: un alias, el que está. Otro alias es decisión de ADR.
2. .tkn — extensión del paquete
Choca con .swm (266 menciones; Etapa F entera encima). Resultó mucho más barato de lo
estimado, y por una razón medida: la extensión no es lógica, es salida.
- Se escribe en un solo lugar (
crates/hammer-cli/src/main.rs:1800).
- El descubrimiento de paquetes va por índice, no por glob:
PackageEntry.file
(crates/hammer-core/src/repo.rs:75) guarda el nombre real de cada fichero.
- ⇒ Los repos existentes no se rompen y un repo mixto es válido: un índice con entradas
.swm
sigue resolviendo, y los paquetes nuevos se escriben .tkn. No hace falta migrar nada.
- Cero ficheros
.swm versionados en el repo, así que no hubo qué renombrar.
Swm, SwmBuild, swm_path, swm_bridge NO se tocan. Son nombres de tipo y de módulo, no
superficie de usuario; renombrarlos es churn puro dentro de la etapa 4.
Nota sobre los ficheros de marca
Los dos SVG difieren correctamente (chispa #FFF6DE sobre oscuro, #17130E sobre claro) —
verificado por sha256 y por los colores presentes, no por el nombre del fichero. Ambos traen un
manifiesto C2PA de procedencia embebido; se conserva tal cual vino, sin limpiar.