Files
takana/docs/adr/0016-renombre-takana.md
SergioandClaude Opus 5 008dd3925e renombre: los instaladores pasan a takana-* — y el peligro no era el fichero, era el PROTOCOLO
ADR 0016 los listaba entre los CONGELADOS; el usuario pidió descongelarlos al abrir el SDD 28. La
enmienda queda escrita en el propio ADR, que si no la etiqueta deja de describir el hecho.

`hammer-install.sh` → `takana-install.sh`, `hammer-live-install.sh` → `takana-live-install.sh`,
`hammer-banner.txt` → `takana-banner.txt`, `BRIEFING-hammer.md` → `BRIEFING-takana.md`.

**Lo que hacía caro esto no es el nombre del fichero.** El instalador se inyecta en el ISO como
`/usr/bin/hammer-install` y su éxito se detecta con un `grep` de `HAMMER-INSTALL-OK` desde TRES
scripts de prueba. Renombrar un solo lado los deja casando NADA — sin fallar —, que es literalmente
el modo en que `atribuir-fallos.py` quedó mudo cuando el renombre movió el target de `tracing`.
Se renombraron las dos puntas en el mismo commit (`/usr/bin/takana-install`,
`TAKANA-INSTALL-OK/FAIL`, `TAKANA_INSTALL_*`, `work/takana-install.img`, `/run/takana-install`),
se comprobó por `grep` que no quede ningún token viejo fuera del ADR, y —lo que decide— se CORRIÓ
`install-tui-test.sh`: 4/4 casos verdes.

Las tres `TAKANA_INSTALL_*` caen al nombre viejo (`${TAKANA_INSTALL_X:-${HAMMER_INSTALL_X:-}}`):
el llamador puede ser un ISO anterior al renombre. Misma convención que `TAKANA_ROOT_PW` unas
líneas más arriba en ese mismo script.

NO se tocó `/usr/sbin/hammer-recover` ni su hook de arranque —renombrarlo rompe máquinas YA
INSTALADAS, no el repo—: sobrevive intacto dentro del script renombrado, verificado por conteo
antes y después (8 ocurrencias). Tampoco `hammerd`, `hammer-edit` (su `name` está en la ruta del
store), `/var/lib/hammer`, `HAMMER_LIVE` ni los siete literales de hash.

De paso: `scripts/.hammer-banner.txt.kate-swp` era un swap de editor commiteado por error. Fuera.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-09 22:54:13 +00:00

381 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
1. El sistema se llama **takana**. La marca vive en `docs/marca/`.
2. El renombre va **por etapas con alias**, nunca de un saque. `hammer` sigue funcionando hasta que
el último llamador migre.
3. 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
1. **Marca + este ADR.** Aditivo, no rompe nada. ← *hecho*
2. **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.
3. **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 `hammer-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:4018:45Z).** Reiniciado `hammer-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.
4. **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.
**Una consecuencia que se anotó y resultó FALSA — corregida el 2026-09-09.** Se afirmó que al
renombrar `hammer-core` los bytes de `hammerd` cambiaban —linkea contra un crate con otro nombre
y el nombre va en los símbolos— y que por eso **había que rehacer el baseline `of_tree` del
selfhost**. El razonamiento es correcto y la conclusión no, porque le faltaba un dato:
**`recipes/hammerd.toml` pinea su fuente por `commit`, no por el árbol de trabajo**
(`commit = "e9b0551213…"`). El renombre vive en el working tree; la receta construye desde un
commit congelado. El cambio no la alcanza.
Medido, receta por receta, con `takana hash` contra lo sellado:
| componente de Stage 1 | vigente | sellado |
|---|---|---|
| `musl` | `ce952f72` | `ce952f72` |
| `busybox` | `2a2b1280` | `2a2b1280` |
| `hammerd` | `48bbbe52` | `48bbbe52` |
| `arje-zero` | `b34c571b` | `b34c571b` |
⇒ **Los cuatro idénticos: la etapa 4 no movió el baseline y no hay nada que rehacer por su
causa.** El baseline se moverá el día que alguien suba el `commit` de `recipes/hammerd.toml` a
uno que contenga los crates renombrados — que es una decisión aparte, no un efecto colateral.
**Lo que sí apareció al ir a mirar, y no es de esta campaña:** el `of_tree` del `stage1-rootfs`
sellado hoy en `./store` es `b3:d152f060…`, que **no coincide con ninguna de las dos constantes
del script**: `EXPECT_REF` = `9adefb82…` (toolchain Alpine) ni `RUST_EXPECT_REF` = `7fa6cb4e…`
(takana-rust). Puede ser deriva vieja o corresponder a otra configuración de store —los variantes
rust usan `store-rust`/`store-rust2`—; **con los datos que hay no se puede afirmar cuál**, y
re-anclar un número sin correr el verify in-VM que pruebe que reproduce sería poner una constante
que nadie verificó. Esta máquina **no tiene `/dev/kvm`** (4 cores, 7 GiB), así que ese verify no
se corre acá. Queda como su propia tarea, con su propia máquina.
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.
5. **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, `hammer-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:
> **ENMIENDA 2026-09-09 — dos de esos congelados se descongelaron por decisión del usuario**
> («renombrá todos los `hammer` que vayas encontrando, como ese `hammer-install`»), al abrir el
> frente del [SDD 28](../28-servidor-de-produccion.md). Renombrados:
> `hammer-install.sh` → `takana-install.sh`, `hammer-live-install.sh` → `takana-live-install.sh`,
> `hammer-banner.txt` → `takana-banner.txt`, `BRIEFING-hammer.md` → `BRIEFING-takana.md`.
>
> **Lo que hacía peligroso este renombre no era el fichero: era el PROTOCOLO.** El instalador se
> inyecta en el ISO como `/usr/bin/hammer-install` y su éxito se detecta con un `grep` de
> `HAMMER-INSTALL-OK` desde **tres** scripts de prueba (`iso-install-test.sh`,
> `efi-install-test.sh`, `install-tui-test.sh`). Renombrar un solo lado deja los tests casando
> **nada** — sin fallar, exactamente el modo en que `atribuir-fallos.py` quedó mudo al renombrar
> el target de `tracing`. Se renombraron **las dos puntas** en el mismo commit
> (`/usr/bin/takana-install`, `TAKANA-INSTALL-OK/FAIL`, `TAKANA_INSTALL_*`), se comprobó por
> `grep` que no quedara ningún token viejo, y se **corrió** `install-tui-test.sh`: 4/4 verdes.
>
> **Lo que NO se tocó, y sigue congelado por la misma razón de antes**: el binario
> `/usr/sbin/hammer-recover` y su hook de arranque (renombrarlo rompe **máquinas ya instaladas**,
> no el repo) — sobrevive intacto dentro del script renombrado, verificado por conteo antes y
> después. Tampoco `hammerd`, `hammer-edit`, `/var/lib/hammer`, `HAMMER_LIVE` ni los siete
> literales de hash. Y las tres variables `TAKANA_INSTALL_*` **caen al nombre viejo**
> (`${TAKANA_INSTALL_X:-${HAMMER_INSTALL_X:-}}`), porque el llamador puede ser un ISO anterior al
> renombre — la misma convención que `TAKANA_ROOT_PW` unas líneas más arriba en ese script.
- `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.**
6. **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.
-**`/opt/takana` y `takana-farm.service`** (2026-09-09 ~20:15Z). Sin montajes adentro esta
vez, así que el `mv` fue directo.
**El orden no era cosmético:** el hub siembra por `rsync` a una ruta fija, así que el cron se
sacó ANTES de mover. Al revés, la siguiente cosecha habría recreado `/opt/hammer` y el worker
quedaba **partido en dos árboles** sin que nada fallara.
Un detalle que el primer `sed` no casó: `systemctl restart hammer-farm` y
`journalctl -u hammer-farm` nombran la unit **sin el sufijo `.service`**.
**Verificado:** el worker cerró un ciclo de build real desde `/opt/takana` (`dunst` sellado,
1/1) y una cosecha completa del hub contra la ruta nueva pasó siembra, manifiesto, los nueve
grafos, `static-audit` (691 estáticos, MIENTEN 0) y estado pusheado.
**Dos referencias a `/opt/hammer` quedan a propósito porque siguen siendo ciertas:**
`docs/23-plan-rehasheo.md` describe rutas **embebidas en artefactos ya construidos** —sus
secciones `.debug` literalmente dicen `/opt/hammer/work`— y el HANDOFF de la noche de KDE es
registro. Cambiarlas haría que los documentos mientan sobre lo que hay en el disco.
**Y una recaída, anotada porque es la misma trampa dos veces:** el barrido ad-hoc de este
mismo cambio (`git grep -l … | sed`) NO respetó el aviso de arriba y dio vuelta cuatro
afirmaciones **históricas** de este fichero, dejándolo diciendo que a las 18:40 se reinició
`takana-farm.service` cuando en ese momento se llamaba `hammer-farm.service`. Corregidas a
mano. La exclusión hay que ponerla en **cada** barrido, no una vez.
-**`/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 26 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.