Era la ultima superficie de CLI que quedaba con el nombre viejo. Mismo patron
que forja: el canonico es el nuevo, el viejo sigue aceptandose como alias.
El default pasa a .../release/takana.
El DESTINO no se toca: el binario se sigue instalando en /usr/bin/hammer dentro
del rootfs del producto, porque ese rootfs se hashea. Moverlo cambia el hash del
producto y obliga a rehacer el baseline del selfhost — es etapa 6 y decision
aparte, no efecto colateral de renombrar un flag.
Verificado en el subcomando correcto (bootstrap builder, no product): las dos
formas se aceptan. 605 tests en verde.
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.
El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.
Y el hallazgo caro: casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a 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 porque los logs viejos en disco dicen la vieja.
Además 14 rutas de módulo en docs, que el barrido anterior no tocó
porque no es frontera de palabra.
TAKANA_X con caída a HAMMER_X (ADR 0016). No es un sed: estas variables son
contrato de usuario —knobs del instalador, el entorno del worker, mirror-env.sh—
y viven en perfiles de shell y units FUERA del repo, así que un renombre duro no
falla ruidosamente: la variable no aparece, se toma el default y el build se
comporta distinto sin que nada lo diga.
Rust: takana_core::env::{var,var_os} toma el nombre canónico y deriva el viejo
cambiando el prefijo — se le pasa TAKANA_* 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 no inventa una caída.
Migrados los 17 sitios directos y los indirectos que el grep no mostraba
(bases_de_mirror, las constantes de kernel_cmd, ROOT_ENV de qorpa, 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 default, manteniendo el
nombre INTERNO de la variable para no tocar sus 190 usos.
Y donde el script EXPORTA en vez de leer, se ponen LAS DOS (mirror-env.sh y el
fragmento in-VM de bootstrap): ahí el lector puede ser un binario viejo —worker
sin recompilar, el /usr/bin/hammer pinado del baseline— que sólo conoce HAMMER_*.
La caída sirve para lectores nuevos; los viejos necesitan que la vieja siga puesta.
Verificado de punta a punta con el binario, no sólo con unit tests: HAMMER_LAB
sigue surtiendo efecto, TAKANA_LAB hace lo mismo, y con las dos gana TAKANA_LAB.
605 tests en verde y el hash de zlib sigue en b3:dc363f26… , intacto.
Cambio de comportamiento que va aparte y hay que decir: 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 instalados, el volid
HAMMER_LIVE del ISO, ni el namespace HARKAQ_*, que es de otro subsistema.
hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
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 rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
CIERRA el frente kernel-from-source: el bzImage hammer-built (Linux 6.16.12,
recipes/linux.toml) bootea la VM del selfhost-verify y el rebuild in-VM reproduce
el of_tree bit a bit (✓ REPRODUCIBLE, DRIVER_RC=0, ~3min).
El muro era bwrap `pivot_root: Invalid argument`: con un kernel hammer el / del
initramfs es el rootfs absoluto del mount-namespace (sin mount padre movible), y
bwrap del sandbox no puede pivotar de ahí (el kernel host lo permitía — quirk
suyo; diagnosticado con un debug-loop de initramfs mínimo, boot ~10s).
Fix portable (HAMMER_KERNEL=1): un /init wrapper PID1 que copia el rootfs a un
tmpfs y hace switch_root, dejando / como mount tmpfs real (pivotable). bwrap
pivota en CUALQUIER kernel. Condicional ⇒ el camino del kernel host (rdinit=
/sbin/init directo) queda intacto. drive-rebuild.py: APPEND env override para
pasar rdinit=/init.
Uso: HAMMER_KERNEL=1 KERNEL=<bzImage> KVM=1 MEM=16384 PRESEED=hammerd \
./scripts/selfhost-verify.sh
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✓ REPRODUCIBLE in-VM (2026-06-18): SWAP_RUST=1 KVM=1 PRESEED=hammerd selfhost-verify.sh
→ el host construye stage1 con hammer-rust (of_tree b3:7fa6cb4e), la VM lo reconstruye con
hammer-rust y reproduce 7fa6cb4e BIT A BIT (stage1' == stage1, DRIVER_RC=0). hammerd
reconstruido in-VM = byte-idéntico al host (d08fd273). Cierra el frente rust del
auto-alojamiento: el compilador (rustc 1.91.1 hammer-built, mrustc→1.90→1.91.0→1.91.1) se
auto-aloja bit a bit, con su propio EXPECT_REF (≠ 9adefb82 de Alpine; rustc emite los bytes).
PRESEED=hammerd es OBLIGATORIO con SWAP_RUST: arje-zero (monorepo tawasuyu) vendorea ~1973
crates; un rebuild in-VM completo (PRESEED=all) desborda el rootfs en RAM (ENOSPC). Con
PRESEED=hammerd se preseedea el arje-zero host-built (hammer-rust, locked desde 9967b02c) y
sólo hammerd se reconstruye in-VM. Documentado en el bloque SWAP_RUST y en rust-frontier/README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto-consistencia de hammer-rust VERIFICADA en host (2026-06-17): of_tree(stage1)
construido con hammer-rust 1.91.1 = b3:7fa6cb4e… reproducido 2× (store-rust y store-rust2,
builds frescos independientes). Es el nuevo EXPECT_REF para SWAP_RUST (criterio elegido:
auto-consistencia, ≠ el 9adefb82 de Alpine). Cableado como default de RUST_EXPECT_REF.
swap-rust-into-toolchain.sh --restore: ahora también elimina lo AÑADIDO por el swap
(rustlib x86_64-unknown-linux-musl + librustc_driver de hammer), no sólo restaura
rustc/cargo. Round-trip apply→restore deja el devfs prístino (verificado: sólo
x86_64-alpine-linux-musl + driver de Alpine). Antes dejaba ~350 MB de cruft.
Nota: los leftovers NO afectaban el of_tree (un build Alpine con/ sin ellos diverge igual
del baseline Jun13 — ver hallazgo de drift abajo); la limpieza es higiene/reversibilidad.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
recipe.hash_inputs = source+compiler+target+link+patches+flags+phases+deps; NO incluye
el binario rustc del toolchain. En el ./store compartido, arje-zero/hammerd quedarían
cacheados con los bytes de Alpine ⇒ el swap de rust sería un no-op (REF seguiría 9adefb82).
SWAP_RUST=1 ahora usa un store dedicado (store-rust, override RUST_STORE) para forzar el
rebuild de los 4/4 con hammer-rust, sin tocar el ./store baseline. Así of_tree refleja el
compilador nuevo (auto-consistencia).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pieza rust del auto-alojamiento "variante b" (la última del frente). A DIFERENCIA de
make/busybox/bwrap/coreutils/linux-headers (herramientas que orquestan/copian ⇒ bytes
idénticos ⇒ of_tree 9adefb82 por construcción), **rustc EMITE los binarios del 4/4**
(arje-zero, hammerd) ⇒ un rustc distinto diverge of_tree. Criterio elegido: AUTO-
CONSISTENCIA (REF recomputado con hammer-rust = nuevo EXPECT_REF; la VM reproduce ESE),
no igualdad byte-a-byte con el baseline Alpine.
- swap-rust-into-toolchain.sh: overlay aditivo y reversible de .scratch/rust-1.91.1-prefix
sobre /toolchain. El rustlib x86_64-unknown-linux-musl de hammer y sus .so con hash propio
NO chocan con el x86_64-alpine-linux-musl de Alpine ⇒ sólo se reemplazan /usr/bin/{rustc,
cargo} (Alpine guardado en *.alpine; --restore deshace). Los 4/4 compilan NATIVO (target
x86_64-linux-musl == SANDBOX_NATIVE_TARGET, sin --target) ⇒ hammer-rustc emite para su
triple nativo x86_64-unknown-linux-musl.
- selfhost-verify.sh: SWAP_RUST=1 aplica el swap en $TOOLCHAIN antes del build de stage1
(así el REF host se computa con hammer-rust) y restaura al salir (trap). Sirve TANTO al
REF host COMO al toolchain in-VM (--toolchain $TOOLCHAIN). EXPECT_REF pasa a RUST_EXPECT_REF
(no el 9adefb82 de Alpine). Vars: RUST_PREFIX, RUST_EXPECT_REF.
VALIDADO: el swap aplica/restaura limpio; hammer-rust 1.91.1 (host x86_64-unknown-linux-musl)
compila+corre un crate NATIVO (sin --target) en el devfs — el camino exacto de los 4/4.
NEXT: computar el nuevo RUST_EXPECT_REF (bootstrap stage1+stage2 con SWAP_RUST) y verify in-VM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quinta pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): GNU coreutils 9.8
(cp/mkdir/ln/chmod/mv/install…), que el `make install` de musl y busybox invocan.
- recipes/coreutils.toml: empaquetada multicall (--enable-single-binary), mismo layout que el
paquete de Alpine (un /bin/coreutils + ~100 symlinks que despachan por argv[0]). Estática musl
con zig cc, vainilla sin patches (es tool del toolchain, no input compilado del 4/4).
- scripts/selfhost-verify.sh: SWAP_COREUTILS=1 construye la receta y monta el único binario sobre
/toolchain/bin/coreutils — los symlinks del toolchain (cp/mkdir/install/…) lo siguen, un solo --swap.
Bisección host fuerte: con hammer-coreutils pisando el de Alpine (trap-restore en .dev-fs), musl Y
busybox rebuildearon BYTE-IDÉNTICO (57b66a2e, 56664d70). Pendiente: corrida in-VM acumulando swaps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cuarta pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): bubblewrap, el
sandbox del propio lab. Binario estático ⇒ swap de archivo sobre /toolchain/usr/bin/bwrap.
Como herramienta del toolchain (no input del 4/4) no necesita casar byte-a-byte con Alpine,
sólo aislar igual.
Tres piezas:
- recipes/libcap.toml (2.78): dep obligatoria de bwrap; el toolchain Alpine no trae el -dev
(libcap.a / sys/capability.h). Build estático musl con zig cc, sin patches (es tool, no input).
- materialización de build-deps (hammer-build): deps.build ahora se CONSTRUYE recursivamente
(build() llama build() por cada dep) y cada artefacto sellado se apila como capa --overlay-src
bajo el rootfs del sandbox, dejando usr/{include,lib,lib/pkgconfig} en /usr. pkgconf y zig cc
las hallan sin plumbing de flags. Recetas sin deps: sandbox byte-igual (baseline intacto).
Tests nuevos: no_deps_emits_single_overlay_src, deps_stack_as_overlay_layers_under_rootfs.
- recipes/bwrap.toml (0.11.0): el toolchain no trae meson/ninja/python, así que bypaseamos meson
compilando los 4 .c de bubblewrap directo con zig cc (+config.h trivial). deps.build=["libcap"].
Validación host fuerte: hammer-bwrap es estático, corre --version y sandboxea, y musl rebuildeó
BYTE-IDÉNTICO usándolo de sandbox (bisección). Expuesto con SWAP_BWRAP=1. Tests verdes.
Pendiente: corrida in-VM acumulando swaps para el sello ✓ REPRODUCIBLE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tercera pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): los headers
UAPI del kernel 6.16.12 que musl/busybox #include, hoy tomados del paquete linux-headers
de Alpine.
- recipes/linux-headers.toml: `make headers` (no `headers_install` — su rsync final falta
en el toolchain hermético) + unifdef vía HOSTCC=zig cc; .tar.gz (busybox-tar lo
descomprime sin xz). Sella los 13 subdirs kernel-owned de usr/include.
- assemble_builder: el swap ahora soporta rel_path de DIRECTORIO (reemplaza el árbol
entero: remove + copy), no sólo binarios. Cubierto por test nuevo
(builder_rootfs_swaps_toolchain_header_tree_from_source, incl. borrado de huérfanos).
- recipes/linux-headers-alpine-compat.patch: el paquete de Alpine no es el `make headers`
crudo. Aporta content-pinned 5 archivos que difieren de la salida vainilla; 3 son
REQUERIDOS (scsi/{scsi,scsi_ioctl,sg}.h — legacy userspace, NO UAPI) porque el applet
`eject` de busybox los incluye y sin ellos no compila. install los pisa sobre /out y
limpia el junk (.cmd/Makefile/headers_check.pl/drm) que `cp -a` arrastra.
- scripts/selfhost-verify.sh: SWAP_LINUX_HEADERS=1 construye la receta y emite un --swap
por subdir kernel-owned (auto-derivado del artefacto sellado).
Criterio de éxito fuerte: `diff -r` del header-tree de hammer contra el de Alpine = VACÍO
⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ reproducibilidad por construcción.
Ensamblado end-to-end en host OK (13 swaps, musl bits/sys intactos). 133 tests verdes.
Pendiente: corrida in-VM para el sello ✓ REPRODUCIBLE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Variante (b) pieza 2: el sh+coreutils que el build usa del /toolchain. El /toolchain
de Alpine es mixto — busybox para sh/sed/grep/awk/tar/find (symlinks → /bin/busybox),
GNU coreutils para cp/mkdir/install. El swap monta el busybox estático de hammer
sobre /toolchain/bin/busybox: los applets busybox-backed pasan a usarlo, coreutils
GNU queda intacto. Sin código nuevo — el mecanismo --swap ya soporta rel_path
(--swap busybox=<hash>:bin/busybox).
Validado en host (sin VM): con make+busybox de hammer swapeados, los 4/4 componentes
construyen — incl. busybox compilándose a sí mismo con hammer-busybox de shell — y el
of_tree del rootfs ensamblado da 9adefb82 (el baseline determinista). Las piezas 1 y 2
son reproducibilidad-neutrales: el toolchain es intercambiable sin perturbar el byte-output.
selfhost-verify.sh: SWAP_BUSYBOX=1 (paralelo a SWAP_MAKE). Docs 10/11 actualizadas.
Pendiente: verify in-VM con los swaps para el ✓ REPRODUCIBLE end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verificado: arje-zero con CARGO_BUILD_JOBS=1 y TODAS las CPUs del host sale
byte-idéntico al build serial ⇒ el jobserver serializa el backend paralelo de
rustc/LLVM (ThinLTO) independientemente del nº de CPUs. La pieza que faltaba para
la reproducibilidad host↔VM (codegen-units=1 no bastaba).
- EXPECT_REF re-baseado: 0039b2b9 (build paralelo no-fiable) → 9adefb82 (serial,
determinista, = lo que produce la VM con 1 vCPU).
- Baseline del host refrescado: store/ re-sellado con el arje-zero determinista.
- docs/10-roadmap.md: causa raíz documentada (make inocente; arje-zero/ThinLTO).
Pendiente: re-correr el verify in-VM con el fix para cerrar el ✓ REPRODUCIBLE y
validar de paso la pieza 1 (swap del make).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segundo paso del auto-alojamiento *puro* (SDD 11 §7.2b): tras sellar GNU make
4.4.1 desde fuente (commit 749ea9e), ahora el builder puede *usarlo*. El swap
reemplaza una a una las piezas que el builder toma de Alpine por recetas hammer,
con Stage 2 reverificando que el byte-output no cambia.
- `BuilderSpec.swaps: Vec<ToolchainSwap{name,artifact,rel_path}>`: monta el binario
sellado sobre el path Alpine en /toolchain (estático musl ⇒ sin shim del loader).
- `swaps_digest` (ordenado por nombre) entra al hash lógico del builder ⇒ la
procedencia deja de ser "todo Alpine" y se vuelve auditable para el log de
transparencia. Vacío ⇒ digest "" (compat hacia atrás: builder pura-Alpine
conserva su hash previo).
- CLI: `hammer bootstrap builder --swap make=<hash>[:rel_path]` (repetible;
rel_path por defecto usr/bin/<name>).
- selfhost-verify.sh: opt-in `SWAP_MAKE=1` (construye recipes/make.toml y lo
swapea) + `SWAPS="name=hash …"` para swaps extra. Default off ⇒ corrida
pura-Alpine idéntica a la baseline conocida-buena.
Validado en el host contra el store real: pura `b3:8a370f5d…` vs swapped
`b3:e7e2282c…`, y /toolchain/usr/bin/make queda hardlinkeado al artefacto
fbad44ac… (ELF estático, no el dinámico de Alpine). 3 tests nuevos
(swap aplica, hash cambia, error si falta el binario). 37 tests verdes.
Pendiente: correr Stage 2 in-VM con el swap y confirmar que of_tree(stage1') == ref
(el make hammer compila los 4/4 idéntico al de Alpine ⇒ toolchain intercambiable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El rebuild in-VM daba ✗ DIVERGENTE (of_tree host 984e002f ≠ VM 94b93585) pese a
que los 4 componentes sellaban bajo el mismo key of_inputs. Bisección por componente
en el host: musl, busybox y hammerd reconstruyen byte-idéntico, y la ensambladura del
rootfs (of_tree) es determinista e independiente del entorno. El culpable era arje-zero:
dos builds del MISMO host daban binarios distintos (Δ ~9.6 KB por .text/.rodata/.eh_frame/
.gcc_except_table) — firma del codegen paralelo de rustc.
Raíz: el workspace de hammer pinea [profile.release] codegen-units=1 (por eso hammerd
reproducía), pero el monorepo tawasuyu no declara perfil → cargo default codegen-units=16,
cuyo reparto del crate en N objetos varía build-a-build.
Fix: el sandbox impone codegen-units=1 para TODAS las crates Cargo (el var de cargo gana
sobre el profile del repo fuente). El lab elimina el no-determinismo en vez de confiar en
upstream (SDD 09 §2). Verificado: dos builds cu=1 de arje-zero → byte-idénticos.
Nueva referencia reproducible 4/4: of_tree(stage1)=b3:0039b2b9… (reemplaza 198f209f… de
cu=16). Actualizados EXPECT_REF (selfhost-verify.sh) y runbook §8b/§8c.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dos bugs latentes que sólo aparecían en un build real (in-VM); en el host
quedan tapados porque todo sale de caché.
1. scripts/selfhost-verify.sh: los módulos se sacaban de /lib/modules/$(uname -r)
pero la VM bootea $KERNEL (/boot/vmlinuz-linux), que puede ser otra versión.
Mismatch de version-magic ⇒ overlay.ko no carga ⇒ bwrap muere "No such device".
Ahora la versión se deriva del bzImage booteado (file -bL "$KERNEL"; fallback uname -r).
2. crates/hammer-build/src/sandbox.rs: spawn_pump teeaba el stdout del build al
stdout del padre. rebuild-stage1 hace PRIME=$(hammer ... bootstrap stage1); en
un build real son miles de líneas de configure/make capturadas en $PRIME ⇒ el
paso siguiente --rootfs "$PRIME" exec con un arg gigante ⇒ E2BIG (Argument list
too long). Ahora el tee va a stderr; stdout queda para el hash legible-por-máquina.
Con esto el verify corre end-to-end in-VM: los 4 componentes reproducen bit a bit,
pero of_tree(stage1) diverge (host vs VM vs baseline) — no-determinismo del ensamblado
a cazar (SDD 09 §2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El veredicto pleno 4/4 in-VM pide KVM + RAM holgada; el host de dev (7.6 GB, sin KVM)
colgó por presión de RAM bajo TCG a los ~17 min del make de musl. Esta tarea traslada
la verificación a una máquina capaz (laptop) en un solo comando.
- scripts/selfhost-verify.sh: pipeline completo — stage0 → stage1 baseline → stage2
(ancla la ref of_tree) → inyecta overlay.ko/e1000.ko del kernel local al toolchain →
ensambla el builder con la ref embebida → empaqueta (cpio --owner=root:root) →
bootea + driver no-interactivo → veredicto. KVM auto-detect; PRESEED=hammerd para el
camino barato (preseed C+arje, sólo hammerd in-VM). Cross-check entre máquinas:
compara su of_tree(stage1) contra EXPECT_REF (198f209f… conocida-buena del dev).
- scripts/drive-rebuild.py: driver no-interactivo parametrizado (env: BUILDER_CPIO,
KERNEL, MEM, CPU, KVM, NET, DEADLINE, LOG). Bootea, espera la shell de arje-zero,
manda rebuild-stage1 y sale 0 si REPRODUCIBLE / 1 si DIVERGENTE. (Versión de repo del
driver ad-hoc que vivía en work/.)
- scripts/boot-builder-vm.sh: añade la NIC e1000 (NET=1 por defecto) — el vendoring Rust
necesita red.
- runbook §8c: documenta la tarea y el muro de RAM del host de dev.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>