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
381 lines
26 KiB
Markdown
381 lines
26 KiB
Markdown
# 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:40–18: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 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.
|