work/hammer-metal.iso (257M) bootea en OVMF por las dos vías como DISCO:
- virtio-disk y USB-storage(xhci): EFI-stub carga kernel+initrd de la ESP (GPT
isohybrid), cmdline horneado aplica, usbhid/cfg80211 init, arje-zero PID1, SIN panic.
- El '-cdrom' falla (El Torito EFI se trunca >32MB) pero es IRRELEVANTE: un USB es disco
⇒ la firmware lee la ESP por GPT. Confirmado el escenario real del pendrive.
Falta solo el hardware: quemar y bootear (Secure Boot OFF).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/hammer-banner.txt: icono ASCII cabeza-de-martillo rellena de '#' (el hash
content-addressed) + handle, wordmark 'hammer' (toilet pagga, estilo pixel-block que
rima con la cabeza de hashes) + acrónimo HAMMER + uso WiFi. metal-iso.sh lo copia a
/etc/motd ⇒ es lo primero que ves al loguear en consola. Reutilizable como base de
logo/wallpaper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La rama GRUB-EFI cuelga (bug GRUB 2.14 del host rolling, afecta a cualquier kernel).
Solución: bootear el kernel por su EFI-STUB directo, sin GRUB-EFI.
- linux-metal.toml: CONFIG_CMDLINE_BOOL=y + CMDLINE horneado
('console=tty0 ... initrd=/initramfs.cpio.gz rdinit=/init'). Sin FORCE ⇒ en BIOS
GRUB sigue mandando su cmdline. Confirmado en fuente (libstub/file.c:50): el
EFI-stub convierte '/'→'\' y efi_load_initrd cae a la carga por cmdline sin loader.
- iso-image.sh: modo EFI_STUB=1 ⇒ la ESP lleva el bzImage como \EFI\BOOT\BOOTX64.EFI
+ initramfs.cpio.gz en su raíz (FAT32). La firmware lanza el kernel directo.
- metal-iso.sh: pasa EFI=1 EFI_STUB=1 (híbrido: BIOS GRUB + UEFI EFI-stub).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/metal-iso.sh: arma work/hammer-metal.iso (kernel metal + firmware AX201
+ wpa_supplicant + wifi-connect), consola en pantalla (console=tty0).
- VALIDADO BIOS (QEMU): el kernel metal bootea, inicializan usbhid/i8042 (teclado),
e1000/e1000e/igb, cfg80211+regulatory (stack WiFi), simpledrm/VGA console, y
arje-zero arranca PID1. iwlwifi/nvme presentes (no bindean en QEMU sin hw real).
- HALLAZGO: la rama UEFI (OVMF) NO bootea el kernel — regresión pre-existente de
GRUB 2.14 (host rolling): GRUB-EFI dice 'Booting' y el kernel queda mudo, con
CUALQUIER kernel (el QEMU tmb falla; efi-boot-test FALLA UEFI / PASA BIOS).
Fix propuesto: boot por EFI-stub directo (sin GRUB-EFI) — requiere kernel con
CONFIG_CMDLINE embebido + ESP con bzImage como BOOTX64.EFI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pieza 1+2 de 'hammer en el metal' (laptop TigerLake, WiFi AX201):
- recipes/linux-metal.toml: variante del kernel con hardware real built-in
(efifb/simpledrm, USB+HID, AHCI/NVMe, cfg80211+mac80211+iwlwifi/iwlmvm,
e1000e/r8169, USB-CDC). Conserva los bits hammer (overlay/userns/fanotify/
virtio) ⇒ sigue booteando en QEMU. MODULES=off (monolítico, sin modprobe).
Deja linux.toml INTOCADO (su of_tree es load-bearing del selfhost).
- scripts/metal-firmware.sh: inyecta iwlwifi-QuZ-a0-hr-b0/cc-a0 + regulatory.db
en /lib/firmware de un rootfs (descomprime .zst → .ucode plano). Blobs fijos
(TODO pin a linux-firmware).
- ESTADO.md: resumen humano del proyecto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El edge rust depende de scudo-malloc y los shims rustc/cargo lo linkean como primer NEEDED ⇒
scudo interpone malloc en todo el proceso y CRASHEA a rustc en ciertos proc-macros (corrupted
chunk header / SIGSEGV). No se puede apk-del (rust depende). Mitigación: aliasar
/usr/lib/libscudo.so → la libc musl ⇒ el NEEDED resuelve pero malloc cae a musl, sin scudo.
Los shims no usan símbolos scudo-específicos (sólo querían el override), seguro y reversible
(.orig-scudo). bootstrap-devfs.sh lo reproduce (paso 3a-bis).
Destrabados+promovidos: trippy (trip 0.13.0), jnv (0.7.1), presenterm (0.16.1) — antes
crasheaban rustc, ahora construyen+corren static-musl.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El usuario aprobó subir el rustc del sandbox. El rootfs de build (.dev-fs/alpine) migró a
repos Alpine edge: rust/cargo 1.96.0 (+ gcc 15.2/musl 1.2.6/llvm22) + clang-dev/clang-libs
(libclang p/bindgen de los *-sys). bootstrap-devfs.sh actualizado para reproducirlo. El 4/4
NO se afecta (usa SWAP_RUST con el rust hammer-built 1.91.1); esto es el toolchain del CATÁLOGO.
Destrabados+promovidos: oxlint (1.94) y ruff (1.94) construyen+sellan static-musl. ouch lleva
gueto gcc pero queda staged: sus deps C++ (unrar-ng-sys/libbzip3-sys) chocan en el link final
(-lstdc++ vs la libc++ de zig). Frontera C++ = ortogonal al MSRV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La pieza que escala a miles: scripts/build-farm.sh construye N recetas a la vez
(worker pool xargs -P) sobre recipes/incoming/, y lo que CONSTRUYE+sella lo promueve
incoming/->recipes/ + publica al repo firmado (en serie: el index.json es mutable
compartido); lo que falla queda staged con su motivo. Idempotente (cache-hit del store).
Corrida sobre las 22 del lote Rust #2, JOBS=4 en la laptop libre (8 cores/19GB):
BUILD-YIELD 11/22 promovidos, los 11 corren static-musl (bandwhich/difftastic/fclones/
gping/grex/mcfly/miniserve/navi/onefetch/pastel/viu). Corpus 71->82, repo 82.
Los 11 fallos, FLAGEADOS por la granja (esto es el valor: triage automatico, no
adivinanza) y staged en incoming/ para revision:
- crate+dep-C bajo zig-cc (frente conocido, mismo family que el gueto gcc): delta
(libgit2-sys), gitui (openssl-sys), jless (libc-stdhandle).
- MSRV > 1.91.1 (techo del sandbox): ouch (1.93). watchexec: feature nightly E0658.
- LINK colgado con zig-cc en binarios Rust grandes (deadlock, hubo que matarlos):
broot, pueue, starship. Patron a investigar (zig cc + lld en binarios pesados).
- import roto (no build): xsv/zellij 'No such file' (la fuente no clasifico),
dog (importo un dog.c ajeno, no el cliente DNS Rust).
GOTCHA infra: builds Rust grandes pueden COLGARSE en el link final con zig-cc
(broot 7min frozen, pueue/starship idem); la granja deja matar el slot
(pkill -f 'build recipes/incoming/<n>.toml') y sigue con los demas.
Arranca el tier-2 de la escalera (C clasico via Alpine, que trae los parches musl):
- import-batch.sh: env PREFER=nix|alpine. Para tandas C, PREFER=alpine antepone Alpine —
el import de nix de un C tiene exito (tarball) pero SIN parches musl => romperia al
construir. Refactor a 'tiers' ordenados (misma escalera, dos sentidos).
- tandas/cli-c.txt: primera tanda C (tree/which/sed/gawk/gzip/tar/jq/file).
- importador Alpine: build-deps = SOLO makedepends. El depends de abuild es RUNTIME
(gzip depends=less para zless) — no hace falta para compilar y rompia hammer build
(buscaba recipes/less.toml). Ahora va como comentario de provenance. Mismo patron que
el fix de buildInputs-de-nix en recetas Rust.
VALIDADO: import C 8/8 desde Alpine/main con parches musl bajados; gzip pierde el less
espurio. test extracts_source_patches_deps_phases actualizado (depends != build-dep).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Faltaba el orquestador de "importar tandas a escala": las piezas per-paquete ya
existían (nix-import.sh, alpine-import.sh) pero sin un driver que aplicara la ESCALERA
DE FALLBACK en lote.
- import-batch.sh <pkg...> | -f <listfile>: por paquete intenta nix (Rust/Go/CLI,
~100% musl) y cae a Alpine main→community si el import falla (Alpine trae los parches
de musl). Mapeo opcional nixattr=alpinepkg. Escribe OUTDIR/<pkg>.toml.
- Mide el YIELD POR TIER (nix / alpine / fallaron) — el dato real al escalar, sin
especular. stdout limpio = nombres importados (encadenable con xargs/pin-recipes).
- Encadena con las piezas existentes: pin-recipes.sh (Fase 2 tag→SHA) + build-repo.sh
(Etapa F pack --build + repo firmado).
- tandas/cli-rust.txt: primera tanda curada (tier 1 Rust) para el modo -f.
VALIDADO REAL en vivo (nix 2.34.7): tanda {fd, hyperfine, jq} → yield 3/3, nix=2
(fd/hyperfine), alpine=1 (jq cae a main + baja test-portable.patch). pin en lote sobre
la tanda: fd v10.4.2 → SHA 7027d453, jq (tarball) salteado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los imports github salen con commit=tag flotante (v1.1.0); el pin lo resuelve al SHA inmutable
⇒ el laboratorio se vuelve determinista al estilo Nix (origen anclado a un punto fijo).
- hammer-cli: `hammer pin <recipe>` (in-place o --out). Usa `git ls-remote` (host-agnóstico, sin
API ni tokens ni rate-limits), prefiere el commit dereferenciado `^{}` para tags anotados.
Reescritura DIRIGIDA de la línea `commit = "<tag>"` (preserva comentarios/formato; no toca
version u otras que casen). No-op si ya es SHA (idempotente) o tarball (ya anclado por sha256).
is_git_sha (40 hex sha1 / 64 hex sha256). +1 test.
- scripts/pin-recipes.sh: ancla en lote (recipes/*.toml).
- Validado real: sd v1.1.0 → 4a7b216552d6… (git ls-remote), idempotente. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El tier de mayor yield (Rust) salía como github sin --bin ni install ⇒ no buildeable. Ahora un
paquete buildRustPackage sale build-ready, con el patrón de la receta ripgrep.
- nix_import.rs: NixPkg gana is_rust + main_program. Si is_rust ⇒ flags=["--bin", <bin>] (bin =
meta.mainProgram, ripgrep→rg) + install "cp target/release/<bin> /out/usr/bin/<bin>". +1 test.
- nix-import.sh: detecta Rust por `hasAttr "cargoDeps" p`; main_program = meta.mainProgram or pname.
- Validado real: import fd → repo+commit, flags=["--bin","fd"], install template. Build-ready
(sólo el commit es tag, no SHA — refinamiento aparte). 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mueve las recetas de Alpine de "importan" a "casi buildean":
- alpine_import.rs: translate_abuild() en las fases — substituye $pkgdir→/out (el DESTDIR del lab),
$pkgname→nombre, $pkgver→versión. NO toca $CBUILD/$CHOST/--shared (juicio por-paquete, marcado).
+1 test.
- scripts/alpine-import.sh: baja el tarball UNA vez y calcula el sha256 (Alpine publica sha512,
hammer pide sha256), reemplazando el FIXME ⇒ receta lista sin tocar el hash a mano.
- VALIDADO real: import bzip2 → sha256 ab5a0317… resuelto, 5 parches musl bajados, install
traducido a /out. Recipe build-ready (sin $pkgdir ni FIXME en código). 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segunda fuente del catálogo, y la RESPUESTA a "¿qué si el build falla en musl?": Alpine ya
porta miles de paquetes a musl CON los parches; su APKBUILD los trae. Un import de nix los pierde.
- crates/hammer-cli/alpine_import.rs: PARSEA el APKBUILD (no lo ejecuta) → receta hammer.
Extrae pkgname/pkgver (expande $var), la URL del tarball, LOS .patch (→ source.patches, lo
central), makedepends+depends → deps (filtra -dev, !negados, pins versionados, auto-refs),
build()/package() → fases. sha256 queda FIXME (Alpine publica sha512; el wrapper lo calcula). 3 tests.
- `hammer import-alpine [FILE|-]`; scripts/alpine-import.sh <pkg> [main|community] baja el
APKBUILD + sus .patch de aports.
- VALIDADO contra aports REAL: import coreutils 9.11 → patches renameat2-fakeroot.patch +
coreutils-9.10-dash-tests.patch BAJADOS a disco; deps limpias (acl/attr/bash/openssl/perl/utmps);
fases build/package capturadas. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Poblar el catálogo no es opcional: 34 recetas a mano = userland desierto. nixpkgs es el mayor set
de recetas DESDE FUENTE ⇒ semilla natural. Importamos la RECETA (source+hash+deps), nunca el
binario del cache de nix — hammer reconstruye desde fuente ("verificar, no confiar").
- crates/hammer-cli/nix_import.rs: consume el JSON normalizado de nix y emite una receta hammer.
Clasifica el origen: fetchurl flat → tarball+sha256 (convierte el hash nix SRI/base32/hex → hex);
fetchFromGitHub → repo+commit (hammer pinea por commit, no necesita el hash NAR). Filtra el ruido
de stdenv (setup-hooks, wrappers). nix_base32 decode portado. 11 tests.
- `hammer import-nix [FILE|-]` (stdin) → receta .toml; valida que parsee como Recipe.
- scripts/nix-import.sh <attr>: `nix eval --apply` produce el JSON normalizado y lo pipea al
importador. NIX_STORE= para store local si /nix/store no es escribible.
- VALIDADO contra nixpkgs REAL (nix 2.34): import hello (tarball, sha256→hex) + ripgrep (github→
repo+commit); pipeline completo nix→import→pack→.swm probado con hello. 31 suites verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convierte la maquinaria de paquetería en la cadena de suministro real: cada receta del corpus
se vuelve un .swm, el repo es el catálogo firmado, install lo reproduce localmente.
- scripts/build-repo.sh: packea recipes/*.toml al repo, anclando expected_hash con `pack --build`
(cache-hit si el artefacto ya está sellado; fallback a pack sin ancla por timeout/fallo), y
firma el release (clave efímera o KEY=). Env: REPO/STORE/KEY/DISTRO/BUILD_TIMEOUT.
- /dist/ gitignoreado (el repo es artefacto reproducible desde el script).
- Validado: poblado de 34 paquetes (31 con expected_hash anclado), release firmado. install
ripgrep desde el repo FIRMADO en modo --require-signed → release trusted → reproduce
b3:9e6079fd (casa el ancla del catálogo) → hidrata → rg 14.1.1 corre. Cadena completa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El modelo de generaciones es in-place (sin menu NixOS que ofrecer en GRUB); lo
que encaja es auto-sanar un upgrade interrumpido al boot. crates/hammer-recover
= mini-binario static-musl (lo unico que el producto necesita, sin el CLI hammer
completo): sin pending.json es no-op; con uno completa (roll-forward) o deshace
(roll-back) -> FHS siempre consistente; nunca aborta el boot. iso-image.sh
INSTALLER=1 lo compila (target musl) y bundlea al payload; hammer-live-install.sh
lo copia a /usr/sbin/hammer-recover y el wrapper /sbin/init instalado lo corre
tras montar /store y /var/lib/hammer, antes de incarnar arje-zero. iso-install-
test.sh valida el hook al boot (marker 'hammer-recover: sin upgrade interrumpido').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El apply escribe el plan completo a pending.json ANTES de proyectar y lo limpia
al commitear; un corte a media proyección deja pending.json + FHS a medias. La
proyección se hizo re-entrante (project_plan, backups idempotentes via
backup_existing_once que nunca pisa el original) ⇒ recover COMPLETA (roll-forward,
re-verifica of_tree) o DESHACE (roll-back: restaura backups, borra la gen a
medias, current->padre). apply se niega con PendingExists si hay intento; status
lo avisa. CLI: hammer upgrade recover [--rollback]. 5 tests (corte a media
proyeccion -> ambos modos) + ejercicio en upgrade-e2e-test.sh. 14 tests verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iso-image.sh EFI=1 añade un 2º El Torito EFI: ESP FAT (poblada con el mtools de
hammer, recipes/mtools.toml) con EFI/BOOT/BOOTX64.EFI = grubx64.efi
(grub-mkimage -O x86_64-efi). GRUB EFI lee el mismo grub.cfg y carga el kernel
EFI_STUB (ya en el kernel, sin rebuild) + initrd vía protocolo EFI. El mismo ISO
sigue booteando por BIOS (El Torito i386-pc) ⇒ híbrido. efi-boot-test.sh valida
ambas firmwares E2E (OVMF→grubx64.efi→sshd, y SeaBIOS→sshd).
GOTCHA: mformat -F fuerza FAT32 (min ~33MiB); sobre ESP chica deja FAT inválido
que la firmware no lee -> sin -F, auto FAT12/16.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el lazo ISO live -> disco instalado -> bootea solo. iso-image.sh
INSTALLER=1 bundlea kernel + GRUB MBR (boot/core.img + modulos) en el initramfs
bajo /usr/lib/hammer/install/ + inyecta /usr/bin/hammer-install. hammer-install
corre dentro del live como root real (busybox fdisk/mke2fs/mount/dd + uutils cp):
particiona MBR (/,/store,/var/lib/hammer), formatea, copia la propia raiz del
live (autoinstalador), escribe /boot+wrapper, instala GRUB con 2 dd (boot->MBR,
core->hueco post-MBR; punteros default 1/2 ya valen en MBR contiguo, sin parcheo).
AUTO_INSTALL=<dev> = /init desatendido (install+poweroff). iso-install-test.sh
valida E2E: ISO+disco blanco -> HAMMER-INSTALL-OK -> boot del disco solo ->
GRUB->kernel->arje-zero->sshd, particiones dedicadas montadas.
GOTCHAS: busybox fdisk CHS-alinea a 63 (hueco 62 < core 281 sectores) -> pisa el
FS de p1 -> grub>; fix sectores explicitos (p1@2048). Copiar top-level de /
enumerado, no lista fija, o se escapa /ente/seed.card.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/iso-image.sh arma un ISO 9660/El Torito isohybrid usando el xorriso
construido por hammer (recipes/xorriso.toml, dogfooding): GRUB core El Torito
(grub-mkimage -O i386-pc-eltorito + iso9660) carga kernel + initramfs del ISO;
el rootfs del producto va como cpio.gz (live de RAM). scripts/iso-boot-test.sh
valida E2E in-VM: SeaBIOS->GRUB 2.14->Linux 6.16.12->/init (marcador
HAMMER-ISO-LIVE-OK)->arje-zero PID1->hammerd->netup(DHCP)->sshd escuchando.
El producto entero corre desde el medio live, sin tocar disco. ISO 113M.
SDD 13 E5 marcado primer corte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tras un rollback las generaciones más nuevas quedan inalcanzables (no hay redo).
prune(state, keep) borra esas huérfanas (la cadena viva = current+ancestros vía
parent siempre se conserva); --keep N recorta además la profundidad de rollback.
live_chain() expuesto. 2 tests nuevos + paso prune en upgrade-e2e-test.sh (3->1
gens, árbol vivo intacto).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Crate hammer-upgrade + CLI 'hammer upgrade apply|rollback|status'. Aplica un
árbol Stage1/producto del store sobre el FHS vivo dejando una generación
(manifest + backup de bytes previos), atómico fichero-a-fichero con 'current'
como commit-point. Rollback restaura EXACTO al árbol anterior (o pre-upgrades).
Cada cambio al journal (HammerHydrate, replay-able). Verificación opcional del
of_tree esperado (índice de mirror E3 / release firmada). Maneja ficheros +
symlinks. 7 tests unitarios + scripts/upgrade-e2e-test.sh (apply v1->v2->
rollback->rollback contra el binario real). SDD 13 actualizado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El producto atestado escribe la pubkey de la rootkey en /etc/arje/rootkey.pub (32 bytes
raw), leyéndola del attest_rootkey del seed ya firmado (lo que arje-packager computó de
nuestra rootkey privada) ⇒ cero criptografía nueva en hammer. El gate de arje
(attest_gate.rs::ancla_externa) la PREFIERE sobre la rootkey auto-declarada del seed
(trust = ancla.or(seed.attest_rootkey)): cierra el ataque "seed reescrito por completo"
que el WARN "sin ancla soberana externa" señalaba (hueco A2). product_attested_hash v2.
- assemble_attested_product_rootfs: tras firmar, extrae attest_rootkey (array 32B) y lo
escribe en etc/arje/rootkey.pub; test hermético verifica el ancla.
- scripts/attest-boot-test.sh: nuevo modo SEED_REWRITE=1 (re-firma el seed con rootkey
ATACANTE, deja la rootkey.pub legítima intacta ⇒ debe HALT).
Validado E2E en QEMU sobre el artefacto REAL v2 (hammer bootstrap product --attest):
· ÍNTEGRO → WARN desaparece, "anclada a rootkey soberana externa", 3 ✓, SSH OK.
· SEED_REWRITE → gate: ancla ≠ attest_rootkey, 3× "autor no confiable" → Halt → sin SSH.
Nota: el ancla en fichero cierra el seed-rewrite; el ancla compilada ARJE_ATTEST_ROOTKEY
(dentro del binario atestado) sería más fuerte pero exige rebuild por-rootkey (documentada
en attest_gate.rs, no hecha). Queda sólo el pendiente #2 (Cargo.lock en tawasuyu).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`product_attested()` + CLI `hammer bootstrap product --attest [--policy] [--rootkey]`:
estructura el spike attest-boot-test.sh dentro de hammer-bootstrap. Hidrata el init
CON gate (arje-zero-attest) sobre /usr/bin/arje-zero, fija attest_policy, deriva los
--bin label=path de la PROPIA seed (PID1 + execs Native, DFS) y FIRMA con arje-packager
(host, static musl) → seed firmada en /ente/seed.card.json; regenera /ente/attest.json
coherente con el init nuevo y sella product-attested-rootfs APARTE (núcleo+product base
intactos, Separación Mecanismo/Política).
- AttestConfig{policy, rootkey:[u8;32]}; DEV_ATTEST_ROOTKEY determinista ⇒ firmas
Ed25519 reproducibles ⇒ árbol sellado reproducible. product_attested_hash v1.
- GOTCHA hardlinks read-only del store: romper /ente/seed.card.json antes de que el
packager escriba (EACCES) y /ente/attest.json antes de reescribir; tmp de firma en
staging/.attest-build se limpia antes de sellar.
- critical_bins_from_seed: getty+sshd comparten /bin/busybox ⇒ 3 concesiones por hash.
- Validado en host contra los sellos reales (3 concesiones, init 13MB, hammer attest ✓).
- Test hermético assemble_attested_swaps_gate_signs_seed_and_regenerates_attest (packager
sintético). 40 tests verde.
- scripts/attest-boot-test.sh: MODO A REAL (PRODUCT_ATTESTED=<hash> bootea el artefacto
real) + MODO B SPIKE fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra la integración canónica firmada de I4 (decisión del usuario): la mitad de
arje (gate) + la mitad de hammer (firma) acopladas con la cripto real de
arje-attest/agora — NO el manifiesto plano de /ente/attest.json.
tawasuyu (pusheado, commit c78a0ada en main): `arje-packager --seed-out` emite el
seed FIRMADO standalone (el gate attest_gate.rs ya estaba en main).
hammer:
- recipes/arje-packager.toml: el firmador (build-time tool, static musl, corre en
el host). GOTCHA: lib+bin + monorepo virtual ⇒ flags `-p ... --bin ...`.
- recipes/arje-zero-attest.toml: arje-zero CON gate, variante SÓLO de producto
(commit c78a0ada). El arje-zero del núcleo (9967b02c) NO se toca ⇒ of_tree
baseline (9adefb82/7fa6cb4e) BLINDADO. Separación Mecanismo/Política.
- scripts/attest-boot-test.sh: valida E2E sobre una copia del product-rootfs
(override del PID1 por el gated + seed firmado por arje-packager + boot QEMU).
Validado in-VM (rootkey fija ⇒ firmas Ed25519 deterministas):
- ÍNTEGRO: gate atesta arje-zero+hammerd+busybox ✓ (politica=Halt) → servicios
arriba → SSH OK.
- TAMPER (1 byte en hammerd tras firmar): "atestación ✗ binario no atestado" →
"ARRANQUE FALLIDO ... Halt — abortando antes de incarnar" → shell de rescate,
SIN SSH. La integridad comprometida NO levanta el entorno.
Pendiente (estructurar bien): cablear esto en hammer-bootstrap product() (firmar
+ hidratar el gated automáticamente) y reproducibilidad (Cargo.lock committeado
en tawasuyu para el gated). El mecanismo ya está probado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`hammer install <device>`: vuelca el product-rootfs lean sobre un disco
destino (block device real /dev/sdX, o un fichero pre-dimensionado para
pruebas) con el mismo layout GPT + GRUB BIOS de E1 pero IN-PLACE. Tras
instalar, el disco arranca solo (SeaBIOS → GRUB → arje-zero → hammerd+
getty+sshd). Contraparte "a disco real" de product-image.sh (imagen portátil).
- install-image.sh generalizado: si IMG es block device (o PREALLOC=1 sobre
fichero pre-dimensionado) NO trunca ni borra el nodo — valida capacidad e
instala in-place. sfdisk/mke2fs -d/dd-splice/GRUB son idénticos (dd a un
offset de device = a un offset de fichero). El camino rootless de E1
(unshare -r + dd conv=sparse,notrunc + patch GRUB) sirve igual al device.
- hammer-install.sh: resuelve el product-rootfs, provisiona authorized_keys
(AUTHKEYS=<file> o TESTKEY=1 efímera), guardas de seguridad (FORCE=1 para
un block device, rechazo si está montado), y delega a install-image.sh.
Con BOOT=1+TESTKEY=1 arranca el target y valida por SSH.
Validado: instalación IN-PLACE sobre un fichero pre-dimensionado de 2200M
(ejercita el camino exacto del device, sin hardware real) → el disco
instalado auto-bootea (sin -kernel) y sirve SSH: ls = "uutils coreutils
0.9.0", /dev/vda3→/store y /dev/vda4→/var/lib/hammer montadas. El device
real sólo añade el guard FORCE + autodetección [-b].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el pipeline de release engineering para el product-rootfs: del
artefacto sellado por `hammer bootstrap product` a un disco GRUB-booteable
que arranca solo en QEMU (`-drive file=img`, SIN -kernel) y sirve SSH.
- product-image.sh: hidrata el product-rootfs sellado a un dir escribible,
provisiona authorized_keys de prueba y delega el armado del disco a
install-image.sh (GRUB BIOS, particiones dedicadas vda2=/ vda3=/store
vda4=/var/lib/hammer). A diferencia de install-image por defecto (que
empaqueta el BUILDER con todo el toolchain), la root es el product-rootfs
LEAN ⇒ imagen de PRODUCTO. Con BOOT=1 auto-bootea + handshake SSH (slirp
hostfwd) como validación.
GOTCHA: BOOT=1 (para el boot propio) se hereda por entorno a
install-image.sh, que haría su PROPIO `exec qemu` foreground y bloquearía
⇒ se pasa BOOT=0 explícito al delegar.
- hammer-bootstrap: el producto crea los mountpoints /store y /var/lib/hammer
(disk-ready) para el wrapper /sbin/init de la imagen. Test actualizado.
Validado in-VM: la imagen auto-bootea por GRUB (sin -kernel) → arje PID1 →
hammerd+getty+sshd; SSH OK, ls = "uutils coreutils 0.9.0" (userland Rust del
disco), /dev/vda3→/store y /dev/vda4→/var/lib/hammer montadas. 36 tests verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extiende la capa de producto con el userland Rust-nativo ADOPTADO, fuera del
núcleo blindado (sigue sin tocar STAGE1_COMPONENTS ni el of_tree).
- USERLAND_COMPONENTS = [uutils, findutils, findutils-xargs, diffutils,
ripgrep]: se hidratan sobre el 4/4 DESPUÉS de busybox ⇒ sus symlinks en
/usr/bin ensombrecen los applets busybox.
- ADELGAZAR BUSYBOX (determinista, sin depender del PATH del shell): por cada
nombre que el userland Rust provee en /usr/bin, se RETIRA el symlink
homónimo de busybox en /bin, /sbin, /usr/sbin (busybox suele dejar `ls` en
/bin, fuera del /usr/bin ya ensombrecido). La tool Rust queda ÚNICA en PATH.
El binario busybox y lo no reemplazado (sh/ash, tar, mount) se preservan.
- product_rootfs_hash v2 (incluye userland); build_components() helper;
assemble_product_rootfs hidrata base→userland→servicios.
- 36 tests verde (nuevo: ensombrecido + retiro de applet + sh/busybox quedan).
Validado in-VM (product-boot-test.sh sobre el product-rootfs de la ruta real):
ls = "uutils coreutils 0.9.0" (/usr/bin/ls), find = "find (Rust) 0.9.1",
rg = "ripgrep 14.1.1"; /bin/ls y /bin/cat retirados, /bin/sh + busybox
preservados. SSH sigue verde. "Adelgazar busybox" cerrado en el producto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidación arquitectónica de sshd-como-servicio (Separación Mecanismo/
Política, decisión del usuario). El núcleo NO se toca: STAGE1_COMPONENTS +
STAGE1_SEED_CARD siguen siendo el mecanismo base atómico que el
selfhost-verify reconstruye bit a bit (of_tree 9adefb82/7fa6cb4e blindado).
Nuevo en hammer-bootstrap:
- SERVICE_COMPONENTS = [netup, openssh] (política de producto).
- SSHD_SERVICE_CARD: card genesis Native/Restart (netup + ssh-keygen -A +
exec sshd -D), validado E2E en QEMU.
- product_seed_card(): compone la seed de producto = seed base + cards de
servicio apendados al genesis vía serde_json (hammer sigue autocontenido,
sin dep de card-core). El núcleo (hammerd+getty) se preserva.
- assemble_product_rootfs() + product(): HIDRATACIÓN TARDÍA — hidrata el
stage1-rootfs ya sellado (4/4 verificado) + inyecta openssh/netup encima
+ escribe configs (passwd con sshd, sshd_config con PidFile /run, /var/empty
0711, /root/.ssh) y sella un `product-rootfs` APARTE. of_tree del núcleo
intacto. Idempotente, reproducible.
GOTCHA: los ficheros hidratados son hardlinks read-only al store ⇒ romper
el hardlink (remove+write) en vez de chmod (mutaría el inodo del store).
- CLI: `hammer bootstrap product --rootfs <base> --recipes recipes`.
- 4 tests nuevos (seed compone, hash determinista, assemble inyecta, recetas
de servicio parsean). 36/36 verde.
scripts/product-boot-test.sh: valida que el product-rootfs de la RUTA REAL
bootea en QEMU y sirve SSH (sólo provisiona authorized_keys de prueba, no
ensambla nada). VERDE: arje levanta hammerd+getty+sshd, handshake real
"Accepted publickey for root", guest responde (seed=hammer-product con 3
cards, Linux 6.16.12). Cierra el agujero de verificación con la arquitectura
final, no con el spike sucio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spike de validación (sucio, temporal) que cierra el agujero que el bwrap
anidado dejó: bootea un work/rootfs-ssh-test desechable (4/4 limpio del
store + openssh + netup PRECOMPILADOS) como initramfs, con sshd como
genesis card supervisado por arje-zero, red slirp con hostfwd :2222->:22,
y hace el handshake SSH real desde el host.
VERDE: arje valida la seed e instancia el card sshd (Native/Restart);
netup toma lease 10.0.2.15 (DHCP slirp); ssh-keygen -A genera host keys;
sshd escucha :22; "Accepted publickey for root" y el guest ejecuta el
comando (HANDSHAKE_OK uid=0, Linux 6.16.12). Quema las 3 incógnitas:
(1) handshake post-chroot/setuid de privsep como root REAL del guest
(lo que el bwrap no pudo), (2) arje gobierna el lifecycle del daemon,
(3) los fds de red no segfaultean contra musl.
Gotchas: cpio -R 0:0 (sin root-owned /var/empty sshd lo rechaza);
chmod u+w sobre el staging (el store está sellado read-only).
NO es plomería de producto: la consolidación (SERVICE_COMPONENTS +
hidratacion tardia sobre el 4/4 verificado, of_tree blindado) es el Paso 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
netup = binario hammer-propio (sync, sólo libc, sin tokio) que configura la red:
levanta el link (netlink RTM_NEWLINK), negocia un lease DHCPv4 a mano sobre UDP
(DISCOVER/OFFER/REQUEST/ACK con flag broadcast), y aplica IP + ruta default +
/etc/resolv.conf (RTM_NEWADDR/RTM_NEWROUTE). Hand-roll de netlink y DHCP porque
no hay cliente DHCP Rust "maduro" para adoptar al estilo ripgrep, y el workspace
es 100% sync (rtnetlink arrastraría tokio). Reemplaza el `ip` estático de busybox.
Validado in-VM contra el DHCP de QEMU slirp: ✓ lease 10.0.2.15 + ruta + DNS +
egress TCP. Autodetecta la NIC (primera no-loopback) y espera carrier por sysfs.
Infra de prueba:
- scripts/drive-netup.py: bootea la VM, corre netup y verifica lease/NAT/DNS.
SLIRP=1 ⇒ red user-mode (cero setup de host); si no, tap del lab.
- scripts/labnet.sh: lab de LAN real opcional (tap directo + dnsmasq + NAT) para
fidelidad de hardware; no requerido para validar netup.
- scripts/netdiag.sh: diagnóstico del camino DHCP del lab (tcpdump+nft+dnsmasq).
Lección: para validar el CÓDIGO conviene slirp primero (cero plomería de host);
la LAN real (firewall INPUT, sutilezas del bridge, perms) es fidelidad posterior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primer entregable de release engineering (SDD 13 nuevo): scripts/install-image.sh
produce una imagen GPT que se BOOTEA SOLA en QEMU (`-drive file=img`, sin
-kernel ni firmware extra) — SeaBIOS → MBR → GRUB → kernel del propio disco →
arje-zero PID 1. Capaz de arrancar en hardware real.
Layout: vda1=BIOS boot (ef02), vda2=/ (con /boot/bzImage + /boot/grub),
vda3=/store, vda4=/var/lib/hammer. Reusa el particionado sin-root de B3
(sfdisk + mke2fs -d bajo unshare -r + dd conv=sparse) y el wrapper /sbin/init.
GRUB instalado SIN root ni loop: grub-bios-setup sondea el disco físico del
host (/dev/nvme…, 660 root:disk) para adivinar el root device del dir -d y falla
sin privilegios. Lo reemplaza un patch binario determinista sobre la ABI estable
de GRUB i386-pc: grub-mkimage arma core.img; un script Python escribe core.img
en la BIOS boot partition y parchea los punteros (boot.img off 0x5c kernel_sector
→ LBA de core.img; core.img off 0x1F4 blocklist.start → resto de core.img),
con asserts de los valores por defecto (1 y 2) para fallar ruidoso si la ABI
cambia. boot.img va al MBR sin pisar la GPT protective (sólo 440 B).
Verificado in-VM (KVM, sin -kernel): SeaBIOS → GRUB 2.14 → Linux 6.16.12 →
vda1..4 detectadas, vda2 root + vda3/vda4 montadas por el wrapper (0 errores) →
arje-zero PID 1 + hammerd (store=/store journal=/var/lib/hammer/journal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/disk-image.sh ahora arma una imagen GPT de 3 particiones ext4 en vez
de una sola: vda1=/ , vda2=/store (CAS inmutable), vda3=/var/lib/hammer (estado
mutable). Construcción sin root ni loopback: cp -al stagea el rootfs por
hardlinks (vacía store/ y var/lib/hammer/, instala el wrapper /sbin/init),
mke2fs -d puebla cada ext4 bajo unshare -r (root-owned), sfdisk escribe la GPT
y dd conv=sparse,notrunc empalma cada fs en su offset (imagen sparse, ~2G
reales). El wrapper /sbin/init monta vda2/vda3 y hace exec de arje-zero (el
kernel sólo monta vda1). drive-rebuild.py: root=/dev/vda1 en modo DISK.
Consecuencia resuelta: con /store en su propia partición el sellado cruza
filesystems y rename(2) da EXDEV. Store::seal cae a copia recursiva a un
staging dentro del store (preserva symlinks+modos) + rename store-interno
(atómico, mismo FS) + borrado del origen. Test copy_tree añadido.
Verificado in-VM (kernel hammer, KVM): vda{1,2,3} montados dedicados, 0
errores Cross-device, stage1' == stage1 ✓ REPRODUCIBLE. Cierra el ☐ de
SDD 11 §6 (particionado/montaje en la imagen destino).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B1 — kernel con disco+FS: linux.toml añade VIRTIO_BLK/VIRTIO_PCI/VIRTIO_NET/EXT4_FS
built-in (=y, sin módulos), aditivo al path initramfs. B2 — infra de disco:
- scripts/disk-image.sh: empaqueta un rootfs como imagen ext4 booteable con
`mke2fs -d` bajo `unshare -r` (archivos root-owned sin sudo, como el cpio
--owner=root:root; evita EACCES en el copy-up de overlay del rebuild).
- scripts/drive-rebuild.py: modo DISK= ⇒ QEMU monta la imagen como virtio /dev/vda
y el kernel arranca con `root=/dev/vda rw rdinit=/sbin/init` — sin initramfs.
Verificado en QEMU/KVM: el kernel monta /dev/vda ext4 como root REAL y arje-zero
arranca como PID 1 DIRECTO, sin el hack /init+switch_root (el muro pivot_root del
path initramfs desaparece por construcción: / ya es un mount pivotable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flex/bison estaban en el devfs base (apk) Y como recipes/{flex,bison}.toml en
deps.build del kernel — pero las capas de deps van BAJO el rootfs, así que el
Alpine flex/bison eclipsaba a los hammer y las recetas eran decorativas. Quito
flex/bison del devfs/bootstrap-devfs.sh; ahora el kernel los toma de deps.build
(hammer). Validado: rebuild en store fresco → kconfig compila con flex/bison
hammer (lexer.lex.o/parser.tab.o), HAMMER_KERNEL=1 verify → ✓ REPRODUCIBLE.
(El bzImage difiere en 1 byte del Alpine-flex/bison build: el timestamp embebido
UTS_VERSION, no flex/bison; el kernel no es input del of_tree del 4/4.)
m4 (que bison invoca) se deja en el base como tool autotools general. Con esto
los build-deps ESPECIALIZADOS del kernel (flex/bison/openssl/elfutils) son todos
hammer-from-source; sólo quedan las libs base/runtime (m4, libz/libzstd de libelf,
gcc) como capa de arranque, igual status que libc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tools/objtool del kernel enlaza -lelf (libelf+gelf.h); venía de `apk add
elfutils-dev`. Ahora recipes/elfutils.toml construye SÓLO libelf (0.194, lo que
objtool necesita — no libdw/libdwfl/src) desde fuente, wired como deps.build del
kernel. Con esto NINGÚN build-dep del kernel viene de Alpine: el camino del
kernel es 100% hammer-from-source.
elfutils es glibc-céntrico; musl no trae <error.h>/<argp.h>/<libintl.h>/fts/
obstack/rawmemchr. En vez del parche completo de Alpine, shims mínimos en compat/
(vía CPPFLAGS/-include): error.h y argp.h (sólo los usan color.c/printversion.c,
cuyos .o van en libeu.a pero objtool NO referencia ⇒ basta que compilen); libintl
no-op (+ --disable-nls); rawmemchr inline; y libargp/libfts/libobstack.a stubs
para pasar los AC_SEARCH_LIBS del configure (símbolos inertes para libelf). CC=gcc.
bootstrap-devfs.sh: elfutils-dev fuera del NEEDED. Validado: purgué elfutils-dev
del devfs, rebuild → objtool linkea el libelf hammer (sin gelf.h Alpine), y
HAMMER_KERNEL=1 verify → ✓ REPRODUCIBLE bit a bit, DRIVER_RC=0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
certs/extract-cert (host-tool del kernel, lo arrastra CONFIG_SYSTEM_DATA_
VERIFICATION=y) enlaza -lcrypto; venía de `apk add openssl-dev`. Ahora
recipes/openssl.toml (OpenSSL 3.5.4, CC=gcc, libcrypto/libssl ESTÁTICAS vía
no-shared) lo construye desde fuente, wired como deps.build del kernel
(materializado como capa overlay en /usr: libcrypto.a + headers + pkgconfig).
bootstrap-devfs.sh: openssl-dev fuera del NEEDED. El runtime libcrypto3/libssl3
(que curl/git necesitan) lo sigue trayendo Alpine aparte — no es openssl-dev.
Validado: purgué openssl-dev del devfs, rebuild del kernel → certs/extract-cert
compila con el libcrypto hammer (sin Alpine openssl), y HAMMER_KERNEL=1 verify →
✓ REPRODUCIBLE bit a bit, DRIVER_RC=0. libelf (objtool) sigue como bootstrap-lib
apk (elfutils-en-musl es el difícil; de-Alpinizable luego).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Generaliza el -append hardcodeado a una env var APPEND (default igual). Útil para
el frente kernel-from-source: experimentar con params del kernel sin tocar el
script. Documenta el hallazgo del muro pivot_root del bwrap (el bzImage hammer
bootea pero bwrap falla porque / es el rootfs absoluto, sin mount padre;
rootfstype=tmpfs no lo arregla).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avance del frente kernel: el build pasó objtool y certs.
- bootstrap-devfs.sh: +flex +bison +elfutils-dev +openssl-dev al NEEDED.
* elfutils-dev (libelf/gelf.h): objtool lo exige — en x86_64 las mitigaciones
seleccionan CONFIG_OBJTOOL aunque se use frame-pointer unwinder.
* openssl-dev: el host-tool certs/extract-cert lo #incluye — el defconfig
fuerza CONFIG_SYSTEM_DATA_VERIFICATION=y vía KEYS←NFS/integrity/dns_resolver
(no apagable sin desarmar media config).
Ambas son build-libs de arranque (status g++/zlib-dev), SEGURAS para el of_tree
del self-host: hammerd no usa openssl (Cargo.lock limpio) y arje-zero va
preseeded/cacheado (no se reconstruye). De-Alpinizables luego con
recipes/{elfutils,openssl}.toml.
- linux.toml: quité los -d de keyring que no pegan (olddefconfig/syncconfig los
revierten por el select-chain); openssl resuelve extract-cert. Comentario
actualizado con el diagnóstico.
Estado: build compila el kernel completo (objtool+certs OK); defconfig es grande
(GPU/wireless), pendiente el bzImage + boot-test + optimización de config.
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>
Ambos hops del climb construidos con el bootstrap real (x.py) en el sandbox devfs,
reusando LLVM 20.1.8 externo en todo (gate >=19) — sin rebuild de LLVM:
mrustc → rustc 1.90.0 → rustc 1.91.0 → rustc 1.91.1
- hop 1 (1.90.0→1.91.0): bootstrap.toml, stage0 = prefix run_rustc musl-host
1.90.0. stage-2 + install → rust-1.91.0-prefix relocatable (rpath).
- hop 2 (1.91.0→1.91.1): bootstrap-1.91.1.toml, stage0 rustc = el 1.91.0 instalado
(bind ro /stage0 vía STAGE0), stage0 cargo = el 1.90.0 (pasa el check minor-1).
extended+cargo. FIX cargo-native-static=true → feature all-static (openssl/curl/
libgit2/libz vendored desde fuente vía gcc), el devfs hermético no tiene OpenSSL.
- run-xpy.sh: añade bind ro opcional /stage0 (env STAGE0) para el stage0 del hop.
VERIFICADO: rust-1.91.1-prefix/bin/{rustc,cargo} corren standalone (solo prefix
montado, rpath). rustc 1.91.1 (ed61e7d7e) host x86_64-unknown-linux-musl LLVM
20.1.8; cargo 1.91.1. Smoke-test compila+corre. Es la versión EXACTA de Alpine,
auto-alojada desde la cadena mrustc. Cada hop ~45min (LLVM reusado).
NEXT: swap rust 1.91.1 en /toolchain + criterio de auto-consistencia (EXPECT_REF).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frente rust, fase climb: la toolchain mrustc-bootstrapped 1.90.0 (host musl) ya
sirve de stage0 para construir rust 1.91.0 con el bootstrap real (x.py).
- bootstrap.toml: build/host/target=x86_64-unknown-linux-musl, stage0 rustc/cargo
= prefix run_rustc 1.90.0 host-musl, LLVM 20.1.8 reusado vía llvm-config (pasa
el gate >=19, sin rebuild de LLVM), crt-static=false (link dinámico contra el
musl del devfs, como Alpine y la toolchain de run_rustc), rpath=true.
- run-xpy.sh: corre x.py en el sandbox devfs (bind /src, /out, /mrustc) para que
resuelvan los paths de stage0 + llvm-config del bootstrap.toml.
- README: documenta ambos.
dry-run de x.py VALIDADO (stage0 pasa el version-check minor+1, LLVM hallado,
target ok). Build stage-2 de 1.91.0 EN MARCHA en background.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Con el stage0 musl-host, run_rustc all terminó: rustc stage-3 (host=musl, LLVM 20.1.8,
1.90.0-stable-mrustc) + cargo 1.90.0 + sysroot (294 rlibs) construidos y verificados
corriendo. El muro de proc-macros quedó roto (tinystr/displaydoc/icu nativos).
Dos snags finales arreglados (en run_rustc.patch, vs be69c74):
- Makefile:262 (hello_world FINAL): mismos flags CRT que la 172 (--target +
-C target-feature=-crt-static -C link-self-contained=no); solo aflora al completar
stage-2/3.
- Makefile:235 (cargo FINAL): --features vendored-openssl (openssl-sys no halla openssl
del sistema en el devfs hermético; openssl-src lo compila desde fuente). README
documenta ambos como snags #4 y #2(262).
NEXT: climb 1.90 -> 1.91.0 -> 1.91.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Con el muro de proc-macros roto (stage0 musl-host), run_rustc avanzó por primera vez
hasta el smoke-test hello_world del prefix FINAL (Makefile:262) y falló igual que el
de stage-1: usa el default musl -static-pie + CRT self-contained ausente
(rcrt1.o/crti.o/crtbeginS.o/crtendS.o/crtn.o + -lunwind estático) -> ld: cannot find.
Fix idéntico al de la línea 172: añade --target $(RUSTC_TARGET) -C target-feature=
-crt-static -C link-self-contained=no a la regla del hello final. run_rustc.patch
regenerado (vs be69c74) e incluye ahora ambas reglas; README snag #2 documenta las dos.
Verificado parcialmente: stage-2 rustc, stage-3 rustc y cargo se construyeron OK antes
del fallo (proc-macros nativos: tinystr/displaydoc/icu compilan). Rebuild en curso.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Al construir el cargo del stage0 musl-host, minicargo (con RUSTC_TARGET seteado,
aunque host==target==musl) corre los build-scripts bajo cargo-build/host/build_X/
pero el crate target hace include!/include_bytes!(OUT_DIR/..) apuntando a
cargo-build/build_X/ (vacío) -> mrustc aborta con signal 6. Rompió en libsqlite3-sys
(bindgen.rs) y en el binario cargo (man.tgz).
Fix: symlink_outdirs() enlaza cada dir target -> host (salidas bit-idénticas porque
host==target), aplicado en un bucle de reintento alrededor del build incremental de
cargo (los sys-crates tardíos -cargo/curl/openssl/libgit2-sys/libssh2-sys- corren sus
scripts al final, así que un solo pase no basta). Documentado como snag #4 en el README.
Verificado: stage0 musl-host rustc 1.90.0 + cargo 1.90.0 construyen y corren con
host=x86_64-unknown-linux-musl; desbloquea proc-macros nativos en run_rustc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>