Más de-Alpinización del builder (variante b, SDD 11 §7.2b): recetas para construir desde fuente
tres build-tools del toolchain que hoy vienen de Alpine.
- recipes/patch.toml (GNU patch 2.8): lo invoca apply_patches (hammer-build/fetch.rs) cuando una
receta trae source.patches, p.ej. el overlay de linux-headers.
- recipes/m4.toml (GNU m4 1.4.20): base de la cadena autotools.
- recipes/pkgconf.toml (pkgconf 2.5.1): lee los .pc que materialize_build_deps deja en el sandbox.
Las 3 estáticas musl con zig cc (AutoconfReady), validadas: corren, versión correcta y funcionales
(patch aplica, m4 expande, pkgconf resuelve). El 4/4 mínimo no las invoca ⇒ sin flag SWAP_* dedicado
(swapeables con el escape SWAPS="name=hash:rel"); avanzan "builder reconstruible al completo".
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>
hammerd::arje_link se suscribe al bus del init (ENTE_BUS_SOCK), relee el frame
postcard de arje-bus con un mirror mínimo de suscriptor (sin arrastrar el
crate-graph de arje ⇒ hammer sigue standalone) y reenvía cada BusEvent →
crashes → Event::Crashed → agent.sock. Wire verificado byte-a-byte contra
arje-bus real (ulid string, frame Subscribe=[00,01,00,0d]); 2 tests de round-trip
local + frame. Se lanza en thread si ENTE_BUS_SOCK está definido (no-op si no).
Roadmap B.2 marcado ✅ (resta sólo el smoke contra init vivo).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deja de ser un error "pendiente": hydrate(.., DynamicSpec{interpreter,rpath})
detecta ELF por magic, copia los que necesitan parcheo (no hardlink: patchelf
mutaría el store) y aplica --set-interpreter/--set-rpath atómicamente
(copy→patch→rename); no-ELF y spec vacío siguen hardlinkeando. ensure_patchelf
falla limpio antes de tocar el FHS si falta la herramienta. HydrateReport.patched
cuenta los reescritos. Callers (bus/cli/bootstrap/e2e) pasan None=estático.
4 tests nuevos (incl. camino de error sin patchelf y real gated). Doc §4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deja de ser un no-op "pendiente fase 5": apply::apply_init_rule escribe una
regla TOML por servicio (service/action/command); enable/start/restart la
escriben, disable/stop la retiran (idempotente), con guardas de nombre y acción.
CLI y orchestrator la aplican (rebaseando con prefix/overlay). Es el contrato
on-disk que el init (arje) lee para supervisar. 3 tests + doc del formato.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mutation::SourcePatch y RecipeInline ganan campos repo/commit XOR tarball/sha256
(Option, serde-default ⇒ compat con .swm git existentes), resueltos por
swm::swm_source_kind con la misma regla que recipe::Source::kind. swm_bridge
sintetiza la receta según el modo; hammer export reconstruye fuentes tarball
como source_patch en vez de caer a file_drop (provenance fina recuperada).
Prompt del traductor y roadmap actualizados. Tests nuevos para tarball + XOR.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nuevo módulo hammerd::crashes: traduce la señal de ciclo de vida normalizada
(fuente = arje-bus BusEvent) a proto::Event::Crashed y la bombea al EventBus →
agent.sock (capa de IA). Traducción + bucle de bombeo desacoplados del
transporte y testeados (3 tests). Resta sólo el adaptador de transporte
arje-bus ↔ hammerd. Roadmap B.2 actualizado.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.sh en libre:
la VM reconstruyó los 4/4 con el /toolchain hammerizado (make fbad44ac… +
busybox 56664d70… pisando los de Alpine, busybox compilándose a sí mismo con
hammer-busybox de shell). of_tree(stage1')==EXPECT_REF 9adefb82… ⇒
✓ REPRODUCIBLE: stage1' == stage1, DRIVER_RC=0 (~54 min, arje-zero cu=1 in-VM).
La procedencia del builder deja de ser "todo Alpine" y el auto-alojamiento
sigue bit-a-bit. Siguiente pieza: linux-headers → bwrap → rust/llvm.
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>
Stage 2 in-VM con SWAP_MAKE=1 dio of_tree(stage1')=9adefb82 ≠ 0039b2b9. El
diagnóstico en host exonera al make: musl+busybox construidos con hammer-make
salen byte-idénticos al baseline (diff -r limpio) y el ensamblado completo da
of_tree EXACTO 0039b2b9. La divergencia es no-determinismo in-VM (coincidió con
swap thrashing: 97 min wall para ~27 min compute), no el swap del toolchain.
Pendiente: re-correr el verify sin swap (control) para aislar la flakiness in-VM.
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>
Arranca el auto-alojamiento *puro* (SDD 11 §7.2b): reemplazar una a una las
piezas que el builder toma de Alpine (/toolchain) por recetas hammer desde
fuente, con Stage 2 reverificando cada paso.
make es la pieza base de toda receta autotools. Build estático musl con zig cc
(mismo camino que grep: tarball release con configure → AutoconfReady), sellado
b3:fbad44ac… y reproducible bit-a-bit (dos builds en stores distintos ⇒ árbol
idéntico). Pendiente: swap al /toolchain del builder + Stage 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra la deriva entre los docs y lo ya probado/commiteado (818c157):
el rebuild in-rootfs corrió end-to-end (host↔VM) y of_tree(stage1')=
0039b2b9… igualó la referencia ⇒ ✓ REPRODUCIBLE.
- roadmap §track posterior: Stage 2 ☐→✅; "Siguiente" reapunta al
auto-alojamiento puro (variante b) + ítems Stage 1 (bus único, atestación).
- SDD 11 §5/CLI: marcadores stage2 ◑→✅; §7 (intro/7.4/8) el rebuild in-VM
deja de ser "lo que falta" y queda la variante (b) como corte pleno.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Corrida KVM=1 MEM=24576 ./scripts/selfhost-verify.sh en el host (libre/cachyos):
la VM reconstruyó los 4/4 con el toolchain de adentro (arje-zero cu=1 compiló en
27m32s in-VM), of_tree(stage1')=b3:0039b2b9… igualó la referencia ⇒
✓ REPRODUCIBLE: stage1' == stage1 (auto-alojado bit a bit), DRIVER_RC=0.
Cierra el hunt de codegen-units (commit 4c9bcc0): la reproducibilidad de arje-zero
queda confirmada en el bucle completo host↔VM, no sólo host↔host.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
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>
Refresca los 4 componentes (musl, busybox, hammerd, arje-zero — éste re-vendorea el
monorepo) con el toolchain baseline en un store limpio y lo promueve a ./store (el
nativo queda en store-native-bak, gitignored). arje-zero baseline difiere del nativo
⇒ el fix -mcpu=baseline alcanza también al monorepo. Referencia CPU-independiente
full-4/4: of_tree(stage1)=
b3:198f209f5e2c9d3a37411823b2ea42a09074d4e482f044278958b1b86f232dbb (rootfs key
b3:2bd7c91b…, idéntico al del preseed-nativo: el cambio de bytes de arje no movió su
key of_inputs — la escotilla C.2 de nuevo).
Builder re-ensamblado con ese toolchain + hammer baseline + --ref-content 198f209f
(embebida en /etc/hammer/rebuild.env) y repackado a work/builder.cpio.gz (415 MB,
27027 entradas, --owner=root:root). Queda listo para bootear: adentro rebuild-stage1
debe reproducir 198f209f ⇒ ✓ REPRODUCIBLE cerraría el auto-alojamiento 4/4 bit a bit.
.gitignore: ignora /store-*/ (stores scratch/backup) — evita commitear el store.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El rebuild in-rootfs de Stage 2 cazó un no-determinismo real: stage1 reconstruido
en la VM divergía del host (of_tree host=b3:94a4f1… vs VM=b3:ab615b…), aun con el
seal hash idéntico (el seal es of_inputs: mismo key ≠ mismos bytes).
Diagnóstico (control en el host, sin VM): un rebuild de hammerd en el host es
byte-idéntico al cacheado, así que la receta es determinista host-a-host y el cache
no está stale. La divergencia es del entorno de build de la VM: `zig cc` default a
`-mcpu=native` y hornea la ISA del builder en el C/asm de los build-scripts (blake3,
curve25519-dalek). El host tiene SHA-NI+AVX2; `-cpu Broadwell` (VM bajo TCG) no tiene
SHA-NI ⇒ codegen distinto ⇒ bytes distintos ⇒ of_tree distinto. Misma raíz que el
SIGILL de AVX del runbook §7 (binarios al CPU del builder, no a un baseline genérico).
Fix: `-mcpu=baseline` en TODOS los sitios zig cc — CC/CXX del sandbox, los dos
wrappers Cargo (.hammer-zig-cc) y CC/HOSTCC de la receta de busybox. Las rutas SIMD
del runtime (blake3/sha2) son asm con dispatch en runtime: siguen presentes. Bonus:
cierra el AVX/SIGILL del §7 (corre en qemu64 sin -cpu Broadwell).
Validado en el host: con baseline hammerd cambia de bytes (1672448 vs 1677584 —
confirma que el default no era baseline) y los 3 componentes rebuildan limpio
(stage1 rc=0). Nueva referencia CPU-independiente of_tree(stage1-baseline)=
b3:4408e44e2ec51c3769deffcd64dd1f6c2010d414418937c3fa7930a0ded3f845. Runbook §8c
documenta el cruce del muro Rust-offline (cargo vendor por la NIC, hammerd sellado
in-VM en 74m35s) y este diagnóstico+fix.
Nota (plan C.2): el flag de CPU no entra en of_inputs ⇒ cambio de toolchain/flags da
cache-hit con bytes viejos; cerrar ese "pin de toolchain al hash" es trabajo aparte.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registra la corrida real (QEMU TCG): la cadena de muros que sólo el rebuild
DENTRO de la VM destapó y que se arreglaron en orden — toolchain sin
bwrap/git/curl, binarios dinámicos vs userland estático (shim del loader), overlay
como módulo no cargado (insmod), y ownership del initramfs (cpio --owner=root).
Resultado: musl y busybox se reconstruyeron y SELLARON dentro de la VM con el
toolchain de adentro (~35-38 min/componente bajo TCG) — auto-alojamiento probado
sobre builds reales. hammerd abortó en `cargo vendor` (deps crates.io no
provistas offline; la VM no tiene red). El 4/4 pleno necesita red en la VM o
crates vendoreadas + un host con KVM/más RAM (idealmente builder desde disco).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El run en la VM, ya con overlay cargado, fallaba en `bwrap: Can't mkdir /opt/zig:
Permission denied`. Diagnóstico (el rootfs resultó ser tmpfs, no ramfs, así que
no era el fs): los ficheros del builder viajaban con el uid del que lo armó
(1000). En la VM corren como root (0); el userns de bwrap mapea sólo 0→0, así que
el 1000 queda SIN mapear y el copy-up de overlay no puede preservar el owner del
/opt copiado ⇒ EACCES. En el host funciona porque bwrap mapea 1000→0.
Fix: empaquetar el initramfs con `cpio --owner=root:root` (runbook §8c). Revierte
el staging-a-tmpfs (era una hipótesis ramfs equivocada); rebuild-stage1 vuelve a
HAMMER_ROOTFS=/toolchain y documenta el requisito de ownership. 28 tests verdes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Smoke test real (qemu -cpu Broadwell, initramfs 410 MB gz): el builder rootfs
bootea hasta la shell. arje-zero como PID 1 carga+valida la seed card, instancia
hammerd (pid 60) + console-getty (pid 61), hammerd levanta watcher fanotify +
agent.sock, y la getty deja `~ #` en consola. Mismo boot que Stage 1 pero con el
toolchain adentro — listo para `rebuild-stage1`.
Actualiza la receta de §8c: script boot-builder-vm.sh, MEM=6144, kernel real,
nota de RAM (tmpfs + rebuild en RAM va justo sin KVM).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Al construir el hammer estático musl y armar el builder con --work-cache work,
dos problemas reales (cazados antes de llenar el disco):
1. Recursión. --work-cache work --out work/builder-rootfs deja el destino DENTRO
de la caché: copiar la caché se tragaba el propio builder a medio armar
(work/builder-rootfs/work/builder-rootfs/… sin fondo). Fix: link_or_copy_tree
toma un `skip` (path canónico a no descender), que corta el ciclo.
2. Bloat de sources. --work-cache copiaba TODO work/, incluidos los árboles ya
materializados de work/sources (4 GB, regenerables). Fix: sólo se embeben
work/repos (mirrors git) y work/tarballs (descargas verificadas); de ahí
`fetch` rematerializa los sources en la VM. El builder pasa de ~5 GB a ~750 MB.
+2 tests (recursión termina y no se auto-copia; sources/ no viaja). Runbook §8c
aclara la semántica de --work-cache. El hammer estático musl
(target/x86_64-unknown-linux-musl/release/hammer) sale static-pie linked, listo
para bootear como PID-algo en la VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El único sub-ítem que le quedaba a Stage 2: el rebuild *dentro* del rootfs. El
Stage 1 que booteamos es runtime (musl+busybox+hammerd+arje-zero), sin compilador
— no puede reconstruirse. El builder rootfs es Stage 1 + el toolchain adentro.
Implementa `hammer_bootstrap::builder_rootfs` (variante a pragmática, SDD 11 §7.2):
sobre el Stage 1 rootfs hidratado monta /toolchain (rootfs Alpine = sandbox de
build), /store con la semilla replicada (el hammer de adentro resuelve zig por
hash), /usr/bin/hammer + /etc/hammer/recipes, y el driver /usr/bin/rebuild-stage1
que apunta HAMMER_ROOTFS=/toolchain, corre `bootstrap stage1` y compara stage1'
contra la referencia con `stage2 --verify` (lee SEED_HASH/SEED_KIND/REF_CONTENT
de /etc/hammer/rebuild.env).
- BuilderSpec/BuilderReport + builder_hash lógico (insumos: stage1+semilla+
recetas+binario+tag toolchain+driver) — reproducible y auditable sin hashear el
árbol Alpine; anota línea stage 2 en el manifiesto.
- link_or_copy_tree (hardlink-or-copy, sin chmod: no toca permisos del .dev-fs).
- El builder no se sella (toolchain Alpine no es content-addressed); se ensambla
en out_dir para empaquetar como initramfs.
- CLI `hammer bootstrap builder --stage1 H --seed-hash H [--hammer-bin] [--toolchain]
[--ref-content] [--work-cache] [--out]`. +4 tests (assemble, hash determinista,
sin-ref, stage1 no sellado).
Validado contra el store real: Stage 1 73d7a9be… + semilla 3ce721ec… ⇒ builder
81dad3d9… (1.2 GB con el toolchain). Lo que queda es operacional: bootear en la
VM y correr rebuild-stage1 — runbook §8c documenta la receta (hammer estático
musl, initramfs, qemu -cpu Broadwell, --work-cache para rebuild offline).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La verificación de Stage 2 (of_tree + verify_against) está probada y los 4
componentes reconstruyen bit-idéntico. El rebuild *dentro* del rootfs exige un
builder rootfs: el Stage 1 mínimo es runtime, sin compilador.
Diseña: qué necesita el builder (hammer estático + recetas + seed zig + make/
autotools + cargo/rust + linux-headers + bwrap para el sandbox anidado); dos
niveles de pureza fasables — (a) pragmático (toolchain desde Alpine, demuestra el
mecanismo + cierra la reproducibilidad end-to-end) y (b) auto-alojamiento puro
(toolchain construido por hammer desde fuente, incremental); y el enganche con
stage2 (ya ancla of_tree(stage1); el builder produce stage1').
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cierra el ◑ de arje-zero sin un rebuild de horas: su único riesgo era el
Cargo.lock generado sin --locked (tawasuyu lo gitignora). Verificado barato —
dos `cargo generate-lockfile` del commit pinned dan un lock byte-idéntico
(resolución determinista) — y el camino de build Rust ya está probado
bit-reproducible vía hammerd.
Los 4 componentes de Stage 1 (musl, busybox, hammerd, arje-zero) están
verificados reproducibles. Lo único que le queda a Stage 2 es el rebuild dentro
de un builder rootfs (auto-alojamiento pleno) — hito aparte.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tercer componente probado byte-idéntico entre dos builds independientes: el
camino Rust (crt-static + cargo --locked + SOURCE_DATE_EPOCH + paths fijos /src)
reproduce. 3/4 componentes verificados reproducibles (musl, busybox, hammerd).
arje-zero queda ◑: mismo camino, pero vendorea sin --locked (tawasuyu no committea
Cargo.lock) ⇒ el lock generado podría variar entre corridas; committear el lock
en tawasuyu lo cerraría (plan C.2 #5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La verificación de reproducibilidad de Stage 2 (dos builds + diff de bytes) probó
que musl reconstruye bit-idéntico pero busybox NO, y cazó dos no-determinismos:
1. Config interactiva: tras editar .config, silentoldconfig prompea por opciones
NEW leyendo stdin → set de applets variable (build no-determinista y flaky).
Fix en busybox.toml: `yes '' | make oldconfig` (defaults, determinista;
1.36.1 no tiene olddefconfig).
2. Headers UAPI del kernel: el config determinista habilita console-tools, que
exige linux/kd.h (zig no la bundlea). Fix: linux-headers en bootstrap-devfs.sh
(zig cc nativo la halla en /usr/include).
Con ambos, musl y busybox reconstruyen bit-idéntico. Runbook §8b documenta la
verificación y deja pendiente: reproducibilidad de los componentes Rust y el
rebuild dentro de un builder rootfs (auto-alojamiento pleno).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Esqueleto de Stage 2 (SDD 11 §3): la verificación de auto-alojamiento.
- VerifyReport + Reproducibility {Reproducible|Divergent|RebuildPending}
- artifact_content_hash(store, hash, name): of_tree del artefacto sellado
- stage2(stage1, store): ancla el content-hash del rootfs (los BYTES reales, no
el hash input-addressed del store) como referencia y lo anota en el manifiesto
(línea stage 2); verdict = RebuildPending
- verify_against(report, rebuilt): compara stage1 vs stage1' → Reproducible/Divergent
- CLI: `hammer bootstrap stage2 --rootfs HASH [--verify CONTENT_HASH]`
Validado sobre el rootfs real: content-hash b3:d0d5669f… (≠ store hash 73d7a9be…,
confirma que of_tree hashea bytes); --verify igual ⇒ ✓ REPRODUCIBLE, distinto ⇒
✗ DIVERGENTE. +2 tests. El rebuild nativo DENTRO del rootfs (produce stage1')
corre en la VM destino — el único sub-ítem que queda de Stage 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desde la consola del Stage 1 booteado: kill $(pidof hammerd) → arje-zero lo
supervisa de verdad:
Ente disuelto label=hammerd status=Killed(SIGTERM)
Restart programado label=hammerd delay_ms=200
Ente encarnado label=hammerd pid=Some(Pid(64))
Detecta la muerte con su ExitStatus, hace backoff y re-encarna hammerd (pid
59→64). Es la supervisión real del init propio (ADR 0007) que la Fase 5 había
diferido. Runbook §8 y roadmap actualizados. Pendiente: B.2 (exponerlo a la
capa de IA por agent.sock) y atestación A1/A2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La corrida real cierra el lazo: con el build nativo crt-static (cargo rustc),
arje-zero y hammerd salen statically-linked (ET_EXEC, sin DT_NEEDED), el rootfs
se sella, y el boot en QEMU (-cpu Broadwell) levanta el sistema:
arje-zero (PID 1) → carga+valida /ente/seed.card.json → bucle primordial →
encarna hammerd (Pid 59) + console-getty (Pid 60) → shell en consola;
hammerd levanta su watcher fanotify y el agent.sock.
Runbook §8: bitácora completa (todos los fixes: gcc/HOSTCC, GNU-ld, rust en el
lab, triple nativo, wrapper saneador, vendoring condicional, AVX→Broadwell,
dinámico→crt-static vía cargo rustc). Roadmap: Stage 1 ◑→✅.
Pendiente: demo del CRASHED real (kill hammerd → restart con backoff; la
supervisión Restart ya corre), bus único (B.2), atestación (A1/A2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cierra el último bloqueo del boot que la corrida en QEMU destapó (runbook §8):
arje-zero/hammerd buildeaban DINÁMICOS contra musl (el rust de Alpine es dinámico
por defecto) → DT_NEEDED libc.musl-x86_64.so.1, y nuestro libc.so (musl shared con
zig cc) no exporta memcpy/memset/… → el loader falla y el init muere.
Fix golden-path: para link=static, RUSTFLAGS ahora fuerza
-C target-feature=+crt-static -C relocation-model=static
⇒ binarios AUTOCONTENIDOS (musl dentro), ET_EXEC sin interpreter, como busybox:
sin libc.so, sin loader, sin el soname de Alpine. musl vuelve a --disable-shared
(no se necesita el loader). Boot en QEMU: usar -cpu Broadwell (el qemu64 default no
tiene el AVX que zig emite).
Validado por la corrida real: los 4 componentes buildan y el rootfs se sella; el
kernel arranca el initramfs y ejecuta arje-zero como PID 1. Falta rebuildear
hammerd/arje-zero con crt-static para cerrar el boot (cache-bust + rebuild).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resuelve el bloqueo Rust de la primera corrida: el rust de Alpine es
x86_64-alpine-linux-musl y --target x86_64-unknown-linux-musl no tiene std.
Cuando recipe.build.target == SANDBOX_NATIVE_TARGET (x86_64-linux-musl, el del
sandbox), se construye NATIVO (sin --target): el std nativo del rust del sandbox
sirve. El wrapper .hammer-zig-cc ahora:
- es un único ejecutable (resuelve el `zig cc` de dos palabras que cargo y cc-rs
mal-parsean),
- SANEA el triple: cc-rs (build scripts, p.ej. blake3) detecta el host triple de
Alpine y pasa --target=x86_64-alpine-linux-musl, que zig rechaza
(UnknownOperatingSystem); lo reescribe a x86_64-linux-musl.
Se usa como CC (cc-rs) y como linker (RUSTFLAGS). El cross real conserva --target.
Validado en VM real: hammerd compila nativo con zig cc y se sella. +1 test (cross
mantiene el triple), detect_cargo actualizado. arje-zero queda bloqueado aparte:
tawasuyu no commitea Cargo.lock (vendor --locked falla) — ver runbook §8.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Primera ejecución del runbook en el host. El userland C builda end-to-end; el
camino Rust queda bloqueado por una decisión de toolchain. Hallazgos verificados:
recipes/busybox.toml — tres incompatibilidades GNU-toolchain ↔ zig, resueltas:
- gcc hardcodeado (CC/HOSTCC = gcc con `=` ignora el env): pasar CC='zig cc'
HOSTCC='zig cc' en línea de comando de make
- flags GNU-ld que zig cc valida y rechaza (--warn-common, -Map, --verbose):
quitarlos de scripts/trylink con sed
Con esto musl 1.2.5 y busybox 1.36.1 compilan, linkean y se sellan.
scripts/bootstrap-devfs.sh — añade rust+cargo al rootfs (las recetas Cargo
corren cargo en el sandbox), con CAVEAT: el rust de Alpine es
x86_64-alpine-linux-musl pero BuildSys::Cargo pide x86_64-unknown-linux-musl
(no instalado, sin rustup) → bloquea hammerd/arje-zero. Decisión de toolchain
abierta (plan C.2).
docs/runbooks/stage1-vm-boot.md §8 — bitácora con la tabla de etapas, los fixes
y las opciones para resolver el triple Rust.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Procedimiento operativo end-to-end para de-riesgar lo construido: stage0 (ingerir
zig) → stage1 (build + ensamblar el rootfs) → empaquetar initramfs (cpio newc +
gzip) → bootear en QEMU con el kernel del host → criterios de éxito.
Fundamentado en los comandos reales del repo (scripts/bootstrap-devfs.sh, la URL
y sha256 de zig 0.16.0, hammer bootstrap stage0/stage1). La prueba clave: matar
hammerd y ver a arje-zero reiniciarlo con backoff — el CRASHED real.
Honesto sobre su estado: es el procedimiento, no un transcript verificado; el
cross-compile y el boot reales aún no se corrieron (ese es el punto). §7
anticipa los ajustes probables (init, /dev/console, getty/tty, -lgcc_s).
Enlazado desde SDD 12 §I2 y el índice de docs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reemplaza el init provisional de busybox por arje-zero como PID 1 — el paso
"init real" que entrega el CRASHED real (Fase 5 diferida, ADR 0007).
- STAGE1_COMPONENTS += arje-zero (la receta puente ya lo construía)
- assemble_rootfs: genera /ente/seed.card.json (template autocontenido), crea
/sbin/init -> /usr/bin/arje-zero, y los mount points que arje monta (incl.
/ente, /var/lib/hammer, /sys/fs/cgroup, /dev/pts, /dev/shm). Se retira el inittab.
- la seed declara hammerd como Payload::Native Restart (su on_death = el CRASHED
real) + console-getty supervisada; ULIDs fijos ⇒ RootfsHash reproducible (v2)
- la seed se VERIFICÓ contra el tipo real card_core::Card (from_json + validate
pasan), no sólo como JSON — evidencia, no aserción
- +2 tests (ensamblado con arje init + seed válida); 21 verdes en el crate
Falta para cerrar Stage 1: bus único (B.2) y atestación (A1/A2). Boot real en VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diseña el último paso ◑ del Stage 1: reemplazar el init provisional de busybox
por arje-zero como PID 1, lo que entrega el CRASHED real (Fase 5 diferida).
Fundamentado en el código real de arje-zero (citas a tawasuyu):
- contrato de boot: arje monta los pseudo-FS él mismo (arje-kernel); lee
/ente/seed.card.json (Card Virtual + genesis); supervisa con Restart/Backoff
- qué debe proveer el rootfs (mount points, seed, /sbin/init→arje-zero, consola)
- la seed card de Stage 1: hammerd como Payload::Native Restart ⇒ on_death = el
CRASHED real; console-getty supervisada; ENTE_BUS_SOCK estable en /run
- generación de la seed: template JSON autocontenido v1 (no acoplar build a
tawasuyu), arje-packager al llegar la atestación A1
- fases I1(✅)→I2(PID 1)→I3(bus único B.2)→I4(overlay+atestación)
- relación agent.sock (API IA, encima) vs arje-bus (control del init)
Contraparte en hammer del plan tawasuyu PLAN-ATESTACION-Y-HAMMER §B.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Puente concreto hacia "arje es el init de hammer" (ADR 0007): recipes/arje-zero.toml
construye el init PID 1 de arje con el lab de hammer.
- Cargo recipe: fuente = monorepo tawasuyu a commit fijado (el de la migración A0
arje-cas → BLAKE3, así el CAS es coherente con hammer), -p arje-zero, estático
con zig cc; deps vendoreadas en el fetch (Lote 6) ⇒ build --offline
- prueba que el lab cruza al repo de tawasuyu y produce el binario del init; NO es
PID 1 todavía (eso es el paso "init real": seed card + arje-bus + hammerd Card)
- nuevo test que escanea recipes/ y valida que toda receta parsea + hashea offline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cierra el userland mínimo salvo el init definitivo. hammerd entra como tercer
componente de stage1, construido por el lab vía BuildSys::Cargo + vendoring:
- recipes/hammerd.toml: source git del repo hammer a commit fijado (ADR 0006),
flags=["-p","hammerd"] para construir sólo ese binario del workspace, estático
con zig cc; las deps se vendorean en el fetch (Lote 6) ⇒ build --offline
- STAGE1_COMPONENTS += hammerd; el inittab provisional lo arranca como servicio
respawn (/usr/bin/hammerd) tras montar los pseudo-FS
- el test de recetas del repo ahora valida también hammerd.toml (parse + hash)
Falta sólo sustituir el init provisional de busybox por arje (ADR 0007) para el
CRASHED real. El cross-compile se valida en la VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cross-compila musl + busybox con la semilla de Stage 0 (no el zig del host),
ensambla un rootfs FHS por hardlink y lo sella. CLI: `hammer bootstrap stage1`.
- seed_toolchain_dir(store, hash, kind): primitivo sin SeedSpec, para que Stage 1
resuelva el toolchain desde (seed_hash, kind) sin re-pasar url/sha256
- RootfsHash = hash de CONTENIDO (componentes ordenados + init), no del árbol en
disco ⇒ reproducible aunque difieran inodes/timestamps
- assemble_rootfs: esqueleto FHS + hydrate de cada componente + /etc/inittab
provisional (busybox init); descarta el sidecar .hammer del rootfs
- idempotente (no reensambla si el rootfs ya está sellado); anota línea stage 1
- hammerd (receta Cargo) y arje quedan como slots pendientes (plan M2 / ADR 0007)
+3 tests (rootfs_hash determinista/sensible, assemble_rootfs, recetas del repo
parsean y hashean). Workspace verde; el cross-compile real se valida en la VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desbloquea integrar el workspace tawasuyu (y cualquier crate Rust) en el lab:
- detección por Cargo.toml (gana sobre Makefile huérfano, cede ante CMake de
un políglota C+Rust).
- traducción de triple zig→rustc (x86_64-linux-musl → x86_64-unknown-linux-musl).
- compile: cargo build --release --locked --offline --target <triple>; link por
wrapper zig cc (CARGO_TARGET_<T>_LINKER) que provee el libgcc_s que el link
dinámico musl exige (cierra el 'cannot find -lgcc_s' que destapó M2). link=dynamic
añade -C target-feature=-crt-static para dlopen (apps gráficas: Vulkan/Wayland).
- install: copia los ejecutables de target/<triple>/release a /out/usr/bin
(busybox-safe).
- doc 02-build-lab §Cargo (vendoreo de deps para build offline) + receta de ejemplo
recipes/llimphi-counter.toml (caso gráfico dinámico).
- 5 tests nuevos (34/34 verdes en hammer-build).
Coordina con tawasuyu/03_ukupacha/arje/PLAN-ATESTACION-Y-HAMMER §C (milestone:
falta BuildSys::Cargo en el lab).
SeedSpec::toolchain_dir resuelve el dir del compilador dentro de la semilla
sellada, listo para que el sandbox lo bindee como /opt/zig; seed_build_config
deriva una BuildConfig que cross-compila con el seed pinned, no con el zig del
host. Stage 0 sigue siendo ingesta pura: el layout se resuelve al usarlo
(zig en la raíz o en el hijo versionado), con error claro si falta o es ambiguo.
Decisiones del Lote 4 registradas en ADR 0008 / SDD 11:
- coreutils+shell = busybox (balsa desechable; GPLv2 no contamina el final)
- init arje diferido (Stage 1 con init mínimo provisional; CRASHED real luego)
- layout de semilla: ingesta pura + resolución al usar
+5 tests (layouts: raíz / hijo versionado / no-sellada / ambiguo / build_config).
Workspace verde.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lote 1 del track posterior. Implementa el embrión del log de transparencia
(SDD 11 §4): crate hammer-bootstrap::manifest con BootstrapManifest/StageEntry.
- append-only e idempotente (de-dup por (stage, artifact_hash))
- escritura atómica (tmp + rename); ts informativo, fuera de todo hash
- stage0() anota su línea en ambos caminos (sello nuevo e idempotente);
un sha erróneo no genera línea
- 9 tests nuevos (4 de manifest + 5 de stage0/manifest), workspace verde
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cablea el Stage 0 del bootstrap a la CLI: subcomando `bootstrap stage0
--url --sha256 --version [--seed zig|musl-cross-make]` que abre el store,
arma el SeedSpec y llama a hammer_bootstrap::stage0, imprimiendo el b3 del
artefacto sellado.
- stage0 ahora limpia el staging `.bootstrap-tmp` pase lo que pase (extraído
stage0_into): un sha256 erróneo ya no deja el tarball a medio descargar bajo
el store. Test reforzado para afirmarlo.
- e2e de CLL `bootstrap_cli.rs`: tarball falso vía file:// → sella + imprime
hash, idempotente, sha erróneo falla sin sellar. Sin red.
Docs SDD 11 §5 y roadmap marcan Stage 0 (lib+CLI) hecho; Stage 1/2 pendientes.
Workspace 219 verde.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Primer eslabón ejecutable del bootstrap from-scratch (SDD 11). Stage 0 ingiere un
toolchain semilla pinned al store sin compilarlo:
- SeedSpec{kind,version,url,sha256}. seed_hash() deriva el ArtifactHash de la
identidad pinned (clase+versión+sha256), no de rutas del host ni del url → el
mismo tarball desde otro espejo produce el mismo artefacto.
- stage0(seed, store): descarga (curl, file:// offline-ok), verifica sha256
ANTES de extraer, untar y seal en el store. Idempotente si ya está sellado;
un sha erróneo no sella nada.
- Reusa el lab: hammer_build::download (+ nuevo verify_sha256 público), Store::seal.
Staging bajo el root del store para que el rename de seal sea atómico.
ADR 0008 fija la decisión (3 stages, zig semilla primaria, musl-cross-make
escotilla, ingerir-no-compilar el Stage 0). Renumerado a 0008 porque el 0007 lo
tomó la decisión de adoptar arje como init; SDD 11 y refs alineados con arje
(init supervisado que entrega el CRASHED real, ADR 0007).
4 unit tests (hash por identidad, ingest+seal+readonly, idempotencia, rechazo de
sha) sin red (file://). Workspace 218 verde.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coordina con tawasuyu/03_ukupacha/arje/PLAN-ATESTACION-Y-HAMMER.md:
- arje (init from-scratch ya existente, PID1 + supervisión real) cubre el
CRASHED diferido de la Fase 5 en vez de escribir init nuevo.
- frontera: PID1 fino, un solo bus (arje-bus + hammer-core::proto), CAS
unificado en BLAKE3, confianza en capas (procedencia hammer + atestación
arje; el expected_hash del .swm = el blake3 que arje atesta).
- caveat: la cage glibc es del mundo hammer/Linux, fuera de PID1.
Puntero añadido en docs/10-roadmap.md §Track posterior.
Diseña el corte del cordón umbilical con Alpine en tres etapas, reusando el lab
de Fase 0 sin mecanismo nuevo:
- Stage 0: toolchain semilla (zig primario, musl-cross-make como escotilla)
ingerido al store como fuente pinned por sha256 — el único insumo del host.
- Stage 1: userland mínimo cross-compilado (musl, busybox/toybox, init propio,
hammerd) ensamblado en un rootfs sellado. El init propio habilita el CRASHED
real diferido de Fase 5.
- Stage 2: rebuild nativo dentro del rootfs y diff de hashes stage1 vs stage1'
⇒ auto-alojamiento bit-reproducible.
Cada etapa anota (stage, recipe_hash, artifact_hash) en bootstrap.json, embrión
del log de transparencia (SDD 09 §4). Propone el crate hammer-bootstrap y la CLI
`hammer bootstrap stage0|stage1|stage2|--all`. Decisiones abiertas marcadas para
un futuro ADR 0007. Enlazado desde el índice de docs y el roadmap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sincroniza las cabeceras con la realidad: tras cerrar la op del watcher (Fase 3)
y la anidación LIFO (Fase 2), ningún ítem de las fases 0–6 queda abierto salvo
CRASHED real, explícitamente diferido al track posterior. Reescribe "Estado
actual" (seguía describiendo el día 1) con el bucle agéntico validado, 214 tests
verdes y el siguiente paso: bootstrap from-scratch del track posterior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>