Commit Graph
90 Commits
Author SHA1 Message Date
sergio efd670a666 Etapa G: git-town PROMOVIDO (go.work, Git Town 23.0.3) + kustomize flags=./kustomize + gate alias go-mockery→mockery 2026-06-29 10:13:16 -04:00
sergio 237f9796e7 Etapa G: gate alias soft-serve→soft (charmbracelet) + re-encola soft-serve (falso-rechazo) 2026-06-29 09:12:45 -04:00
sergio 254c5a9cc7 Etapa G: ronda 2 pre-siembra finde (+18 Go) + alias gate argo-rollouts/step-cli
terrascan/tfsec/tflint/tparse/richgo/curlie/dockle/vendir/kubefwd/kubeshark/ktunnel/duplicacy/
go-junit-report/argo-rollouts/gowitness/shuffledns/dnscrypt-proxy/step-cli. Dropeados ssh-vault
(sin marca Go) y skopeo (CGO gpgme). Gate: argo-rollouts→kubectl-argo-rollouts, step-cli→step.
2026-06-27 07:04:38 -04:00
sergio b1525ec203 Etapa G: harvest-go fija GIT_SSH_COMMAND (clave github5/tawasuyu) p/ push desde cron sin agente 2026-06-27 07:00:23 -04:00
sergio b544ad3d59 Etapa G: harvest gate exige binario == nombre esperado (no solo que 'corra')
La 1ª corrida promovió+FIRMÓ binarios equivocados que pasaban el smoke por solo 'correr':
katana/naabu→functional-test, gosec→gosecutil, buf→protoc-gen-buf-lint (detect_go_main eligió un
main helper). El gate ahora localiza el binario cuyo nombre == receta (o alias conocido tofu/mc/
nats/dlv/flux/ct/kubeseal/node_exporter/...); si instaló otro → a needs-review-weekend con el motivo,
NO se firma. Lección eksctl aplicada al gate.
2026-06-27 06:50:03 -04:00
sergio c381e1b0c7 Etapa G: modo finde desatendido — worker muele incoming-go/ + harvest-go.sh (cosecha-only gated)
- farm-worker-loop.sh: construye QUEUES='recipes/incoming recipes/incoming-go' (en serie por vuelta;
  la cola Go aislada se muele 24/7 sin pisar el incoming/ del otro agente).
- scripts/farm/harvest-go.sh: cosecha determinista sin IA (cron del laptop). Baja el store sellado,
  y por cada receta SELLADA (cache-hit bajo timeout, NO compila en el hub) hace SMOKE-TEST del binario
  (existe + corre version/--help sin panic/segfault) antes de promover+firmar. Lo que falla el smoke va
  a tandas/needs-review-weekend/ para el lunes. add explícito (nunca -A), push con reintento.
2026-06-27 06:41:28 -04:00
sergio c270edade7 Etapa G: watchdog no purga caches Go con CUALQUIER hammer build en vuelo (no solo vendor)
vault (go-15) falló en 'go mod vendor' con '~/.cache/go-build/...: no such file': durante el compile
de telegraf el disco cruzó DISK_HIGH, el watchdog corrió 'go clean -cache', y el vendor de vault (que
usa esa cache) arrancó en la ventana siguiente sin ficheros. El guard anterior solo miraba 'go mod
vendor' activo en ese instante; ahora difiere la purga si hay cualquier 'hammer build' en vuelo.
2026-06-27 03:30:50 -04:00
sergio ad7ec4aea4 Etapa G: watchdog protege árboles con 'hammer build' en vuelo (fase host vendor/resolve)
go-13 (dnscontrol/lazysql, árboles de deps Go enormes) destapó otra race del watchdog: durante el
fetch/'go mod vendor'/resolve_phases (host-side, antes del bwrap) el árbol no está bind-montado ⇒ el
watchdog lo borraba a mitad del vendor → go.mod desaparecía → 'receta sin compile, heurística no
encontró build system'. Ahora además protege work/sources/<name>-* si <name> tiene un proceso
'hammer build' activo (cubre toda la vida del build, no solo la fase sandbox).
2026-06-27 01:07:09 -04:00
sergio 370e7b7a20 Etapa G: limpia cola go-12 — parquea batch gráfico C a hold-nongo + difiere syncthing
- mesa/wayland/wayland-protocols/libdrm/seatd/samurai/meson + patches: batch C de gráficos previo
  que jamás se mandó al worker, arrastrado en incoming/. Parqueado en tandas/hold-nongo/.
- syncthing: build especial (genera la GUI embebida 'auto.Assets' vía go generate/build.go) ⇒
  diferida a staged-fallidos con el motivo. Quedan 18 go-12 limpias en la cola.
2026-06-26 23:51:05 -04:00
sergio 06ba417336 Etapa G: granja Go pesada — watchdog no purga modcache con vendor activo + syncthing main en cmd/
go-12 (terraform/k8s) destapó la race: con árboles de deps de varios GB en paralelo, el disco
cruza DISK_HIGH y el watchdog corría 'go clean -modcache' a mitad de los 'go mod vendor' en vuelo
(.partial: no such file) ⇒ 5 recetas no convergían. Ahora difiere la purga del modcache si hay un
vendor de host activo (la purga de work/sources ya libera lo grueso y es segura). syncthing: main
real en ./cmd/syncthing (la raíz tiene build-constraints que excluyen todo).
2026-06-26 23:32:12 -04:00
sergioandClaude Opus 4.8 64f4a20072 granja: watchdog purga caches Go cuando disco >=82%
El 'go mod vendor' acumula GOMODCACHE (~/go/pkg/mod) sin tope: una tanda Go
grande lo llevo a 31G y lleno el disco de 80G -> I/O-wait disparo el load a 23
(cuello real, no CPU/RAM). El watchdog ahora purga 'go clean -modcache/-cache'
cuando el disco supera DISK_HIGH% (def 82). El vendor/ local de cada receta ya
tiene lo necesario; si pisa un vendor en curso, esa receta reintenta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:36:05 -04:00
sergioandClaude Opus 4.8 5877363f5b Etapa G frente Go: AUTOMATIZADO end-to-end (BuildSys::Go + importador)
Cierra la fragilidad manual del patron Go (path del main por receta). Ahora
importar Go es tan automatico como Rust: import -> pin -> build, sin tocar nada.

- lib.rs: BuildSys::Go (go.mod, prioritario sobre configure/make auxiliares que
  traen muchos proyectos Go). resolve_phases deriva el compile generico:
  'go install -trimpath -ldflags=-buildid=' del paquete main. install='true'
  (go install ya deja en GOBIN=/out/usr/bin).
- detect_go_main(): detecta el dir del main por FILESYSTEM (no compila, evita
  contaminarse con mains de ejemplo rotos en docs/scripts que rompen go list).
  Heuristica raiz > cmd/<x> > menor profundidad; excluye vendor/docs/test/etc.
  go install nombra el binario solo (cmd/mlr->mlr, raiz->modulo).
- nix_import.rs + nix-import.sh: detecta is_go (vendorHash de buildGoModule),
  emite deps.build=['go'] sin phases (BuildSys::Go las deriva).
- tests: go_mod_wins_over_configure, detect_go_main_picks_cmd_over_docs.

Validado end-to-end: miller (cmd/mlr->mlr, esquiva docs rotos), amfora (raiz),
duf (import->pin->build 100% automatico, binario estatico que corre).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:34:27 -04:00
sergioandClaude Opus 4.8 4291b2adeb Etapa G frente Go: vendoring integrado en fetch — amfora construye, repro
Cierra el muro del frente Go (vendoring) replicando el patron de Rust:
vendorear in-situ en el fetch (host, con red), no tarball-al-mirror.

- fetch.rs: vendor_go_deps() analoga a vendor_cargo_deps — corre go mod vendor
  en el host si hay go.mod; el sandbox compila offline con -mod=vendor.
- lib.rs: engancha vendor_go_deps tras los patches cuando el arbol trae go.mod
  (sin BuildSys::Go; el compile lo fija la receta en build.phases).
- bootstrap-devfs.sh: instala el toolchain go del host (1.26.4, estatico) en
  .dev-fs/tools/go + symlink en ~/.cargo/bin (PATH del worker via .cargo/env).
- amfora: PRIMERA receta Go del corpus. Build end-to-end validado (repo+commit
  -> fetch vendorea -> sandbox offline -> binario estatico que corre, Amfora
  v1.11.0). REPRODUCIBLE bit-a-bit: dos stores independientes dan of_tree
  b3:9ec1d5fe (trimpath + buildid= en el compile). Promovida al repo (282).

Quedan 12 recetas Go en tandas/staged-2026-06-26/ (cheat/dnsx/croc/dyff/csvtk/
git-town/miller/jump/gron/qrcp/walk/gtrash): mecanico, copiar el patron de
amfora ajustando el nombre del binario.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:34:56 -04:00
sergioandClaude Opus 4.8 404bfc4970 imagen↔repo: end-to-end — producto booteable con userland del repo firmado
scripts/product-image-from-repo.sh cierra el lazo Etapa F+G entero:
base product-rootfs (arje-zero PID1 + musl/busybox) → hidrata userland rico vía
`hammer install --require-signed` del repo firmado → imagen GRUB auto-booteable →
boot en QEMU + handshake SSH que CORRE los tools del repo.

VALIDADO e2e: booteó, arje-zero PID1, hammerd+netup+sshd, y por SSH corrieron
bat 0.26.1 / fd 10.4.2 / jq 1.8.1 / eza — userland farmeado, servido desde el
repo firmado, no del bootstrap hardcodeado. 240 bins en /usr/bin (base+repo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 16:58:43 -04:00
sergioandClaude Opus 4.8 d41f1705e6 imagen↔repo: install hidrata userland del repo firmado + 2 fixes de path
Cierra el lazo Etapa F+G: el userland del producto se arma vía `hammer install
--repo --prefix --require-signed` desde el repo firmado, no del bootstrap
hardcodeado. scripts/product-userland-from-repo.sh hidrata un set curado (13+
tools validados estáticos del repo).

Dos bugs reales del path install/pack encontrados+arreglados:
1. pack derivaba target_bin=/usr/bin/{name}; para paquetes con binario≠nombre
   (ripgrep→rg, repgrep→rgr) quedaba mal y el sanity-check de install fallaba.
   Ahora pack lo deriva del flag `--bin <X>` de la receta Cargo.
2. La reproducción del source_patch derivaba el NOMBRE de la receta del
   target_bin ⇒ "rg" ≠ "ripgrep" del corpus ⇒ no cache-hit ⇒ rebuild + dup en
   el store (find_by_hash ambiguo). build_source_patch/run_apply ahora reciben
   el nombre del paquete (install/bootstrap lo pasan; apply=None) ⇒ cache-hit
   del artefacto del corpus, sin duplicar.

(El "EACCES" inicial era sólo --store ausente: DEFAULT_STORE=/store root-only.)
hammer-build 49/49 tests ok; ripgrep install validado e2e (rg hidratado).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 16:54:39 -04:00
sergioandClaude Opus 4.8 b61815a602 Etapa G: worker auto-escala JOBS a nproc (resize del VPS sin tocar config)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 06:11:30 -04:00
sergioandClaude Opus 4.8 f9a60ed469 Etapa G: fixes del kit de granja distribuida (validados en VPS Hetzner real)
Tres frentes que aparecieron al provisionar un CCX13 (Ubuntu 24.04) de verdad:
- bootstrap-devfs §3b: el loader musl-256-keys ahora DETECTA la versión del
  rootfs (apk db) en vez de hardcodear 1.2.5. Alpine edge avanzó a musl 1.2.6 ⇒
  un loader 1.2.5 vs coreutils 1.2.6 da `renameat2: symbol not found` → chmod
  roto en el sandbox → el wrapper zig-cc no queda +x → EACCES. case con sha de
  1.2.5 y 1.2.6, die si aparece una nueva.
- vps-setup §2b: compila bubblewrap 0.11.2 si el de la distro no soporta
  --overlay-src (Ubuntu 24.04 trae 0.9.0 sin overlayfs; el sandbox lo necesita).
- farm-sync: excluye /.scratch (44G de fuentes mrustc/rustc) + *.png/content*
  del rsync al worker (casi copia 44G de más).

Smoke test en el VPS: builds compilan código real (pasan cc/chmod), rustc 2
cores al 93%, RAM holgada. Worker funcional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 04:35:57 -04:00
sergioandClaude Opus 4.8 d949d10dcd Etapa G: kit de granja distribuida (worker VPS hub-and-spoke)
Paraleliza el build a un/varios VPS sin exponer gitea ni la clave de firma.
Modelo: el laptop es el HUB (firma + gitea); el worker sólo construye la cola
incoming/ y sella al store local (PROMOTE=0). Reproducibilidad bit-a-bit +
content-addressing ⇒ el store del worker es byte-idéntico, se rsync-ea de vuelta
y el hash valida solo (no hay que confiar en el VPS).

- vps-setup.sh: provisiona Debian/Ubuntu (deps, userns p/bwrap, swap 16G,
  rustup, build hammer, bootstrap-devfs rootfs Alpine edge, systemd unit).
- farm-worker-loop.sh: loop autónomo 24/7, PROMOTE=0, watchdog de disco
  integrado; muele lo que haya sin esperar al hub.
- hammer-farm.service: systemd, JOBS=2 (CCX13 = 2 vCPU dedicado), Nice/idle-io.
- farm-sync.sh (hub): rsync código+cola arriba, store sellado abajo,
  promote+firma+commit+push. Acceso único laptop->VPS por SSH.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:50:43 -04:00
sergioandClaude Opus 4.8 ff17454a47 Etapa metal: habilita DRM_I915 + firmware (panel TigerLake se apaga sin él)
Diagnóstico cerrado: el reboot-probe probó que el kernel está VIVO en el metal del
usuario (se reinicia solo); la pantalla negra es porque el panel TigerLake se apaga al
pasar el kernel y solo i915 (driver Intel real) lo reenciende — efifb/simpledrm/earlyprintk
escriben a un fb muerto (validado: nomodeset tampoco). Era un recorte de más (i915 venía
-d del kernel QEMU). Fix = lo que toda distro normal trae:
- linux-metal.toml: -e DRM_I915 -e DRM_FBDEV_EMULATION (saca -d DRM_I915).
- metal-firmware.sh: inyecta i915/tgl_* (DMC/GuC/HuC, 1.3M) en /lib/firmware/i915.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:54:49 -04:00
sergioandClaude Opus 4.8 5012549f85 Etapa metal: USB de bisección (initramfs tiny) + fix piso FAT32 de la ESP
- Diagnóstico metal: con systemd-boot, earlyprintk=efi NO imprime NADA del kernel
  (solo 'Loaded initrd MEDIA_GUID' + 'Measured initrd PCR 9' y cuelga) ⇒ el kernel
  no llega ni a su consola temprana: hang en ExitBootServices o el decompresor.
- metal-usb-sdboot.sh: piso de 48MiB a la ESP (FAT32 -F exige ~33MiB; con initramfs
  chico caía debajo y la firmware no leía el BOOTX64.EFI → 'Not Found').
- USB de bisección (work/hammer-tiny-usb.img, initramfs busybox 1.3MB): ✓ OVMF llega a
  /init. Si en el metal del usuario ARRANCA a shell ⇒ el cuelgue era el initrd 100MB;
  si TAMBIÉN cuelga ⇒ es el kernel/EBS (probar nokaslr/no5lvl).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:26:29 -04:00
sergioandClaude Opus 4.8 1dc5c16fbd Etapa metal: USB systemd-boot + kernel diagnóstico (esquiva quirk LoadOptions del firmware)
El boot por EFI-stub DIRECTO se cuelga en el firmware del usuario tras 'Measured initrd
PCR 9' (quirk EFI_LOAD_OPTION del firmware + initrd 100MB por la firmware). Pivote:
- scripts/metal-usb-sdboot.sh: arma work/hammer-metal-usb.img (disco GPT+ESP FAT32) con
  systemd-boot (binario del host, crutch de bringup) como BOOTX64.EFI + loader entry.
  systemd-boot provee el initrd por LoadFile2 (esquiva el parseo de cmdline del firmware)
  y permite EDITAR la cmdline en vivo (tecla 'e') ⇒ iterar sin recompilar 45min.
  ✓ VALIDADO OVMF: menú → 'Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID' → arje-zero PID1.
- linux-metal.toml: kernel diagnóstico — cmdline +earlyprintk=efi,keep +ignore_loglevel
  +efi=novamap (ve el boot en pantalla post-ExitBootServices) y -DEBUG_WX (saca el trace).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:33:46 -04:00
sergioandClaude Opus 4.8 30d8c95577 Etapa metal: ✓ VALIDADO boot UEFI por EFI-stub (ISO como disco USB) + nota Secure Boot
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>
2026-06-22 22:31:41 -04:00
sergioandClaude Opus 4.8 63d0871664 Etapa metal: banner de marca para el motd del live (icono martillo+# + wordmark)
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>
2026-06-22 22:09:40 -04:00
sergioandClaude Opus 4.8 b1e2eb95ce Etapa metal: arranque UEFI por EFI-stub directo (esquiva GRUB 2.14 roto)
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>
2026-06-22 21:57:46 -04:00
sergioandClaude Opus 4.8 487d741f08 Etapa metal: ensamblador del ISO EFI (pieza 4) + hallazgo regresión EFI
- 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>
2026-06-22 21:45:28 -04:00
sergioandClaude Opus 4.8 ac2835f4e7 Etapa metal: kernel-metal (drivers reales =y) + firmware AX201 + ESTADO.md
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>
2026-06-22 20:56:19 -04:00
sergioandClaude Opus 4.8 2032df4085 Etapa G: MITIGA scudo (alias libscudo→musl) → destraba trippy/jnv/presenterm (119→122)
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>
2026-06-22 16:01:37 -04:00
sergioandClaude Opus 4.8 e393c6bd4d Etapa G: BUMP de toolchain del catálogo 1.91.1→1.96 (destraba MSRV) + oxlint/ruff (101→103)
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>
2026-06-22 12:48:24 -04:00
sergio 70be258ccd Etapa G: GRANJA de build paralela — drena la cola, 11/22 al corpus (71→82)
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.
2026-06-21 13:36:21 -04:00
sergioandClaude Opus 4.8 db84ee6647 Etapa G: tanda C tier-2 — import-batch PREFER=alpine + importador separa runtime depends
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>
2026-06-21 10:00:35 -04:00
sergioandClaude Opus 4.8 66dbc3a561 Etapa G: orquestador de tandas import-batch.sh (escalera nix→Alpine, yield por tier)
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>
2026-06-21 09:01:24 -04:00
sergioandClaude Opus 4.8 1180bd9154 Etapa G Fase 2: tag→SHA — hammer pin (sello inmutable, ADR 0006)
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>
2026-06-21 08:50:14 -04:00
sergioandClaude Opus 4.8 c3f890c7d1 Etapa G capa-de-adaptación #4: plantilla Cargo para imports Rust de nix
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>
2026-06-21 08:21:47 -04:00
sergioandClaude Opus 4.8 8e6c878d3a Etapa G capa-de-adaptación #2: Alpine import build-ready (sha256 auto + traducción abuild)
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>
2026-06-21 08:18:46 -04:00
sergioandClaude Opus 4.8 630cde6551 Etapa G: importador Alpine APKBUILD→receta (carga los parches de musl)
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>
2026-06-21 08:00:56 -04:00
sergioandClaude Opus 4.8 b979d9e550 Etapa G: importador nix→receta (hammer import-nix + scripts/nix-import.sh)
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>
2026-06-21 07:41:50 -04:00
sergioandClaude Opus 4.8 cb8c49736c Etapa F dogfood: scripts/build-repo.sh — poblar el corpus como repo de release firmado
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>
2026-06-21 07:21:44 -04:00
sergioandClaude Opus 4.8 be881ad099 auto-recover al arranque: crate hammer-recover + hook /sbin/init (E4 #4b)
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>
2026-06-21 05:57:59 -04:00
sergioandClaude Opus 4.8 01b3a225b2 hammer-upgrade: journal de intención + recover idempotente (E4 endurecimiento #4a)
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>
2026-06-21 04:45:27 -04:00
sergioandClaude Opus 4.8 fd09e842b4 arranque UEFI: ISO híbrido BIOS+EFI con grubx64.efi + ESP de mtools (refinamiento #3)
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>
2026-06-21 04:39:13 -04:00
sergioandClaude Opus 4.8 d73e20a394 instalar DESDE el live: hammer-install in-live + payload de disco (refinamiento #1)
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>
2026-06-21 01:50:07 -04:00
sergioandClaude Opus 4.8 2976f3e5fd E5 medio live: ISO El Torito booteable con xorriso de hammer (primer corte)
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>
2026-06-21 01:25:34 -04:00
sergioandClaude Opus 4.8 a22552e28f hammer-upgrade: GC de generaciones huérfanas — 'hammer upgrade prune' (E4 hardening)
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>
2026-06-20 21:49:18 -04:00
sergioandClaude Opus 4.8 d23946fa3d hammer-upgrade: upgrades atómicos con generaciones y rollback (Etapa E4, primer corte)
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>
2026-06-20 21:46:14 -04:00
sergioandClaude Opus 4.8 4e7272730f hammer-bootstrap: ancla soberana externa /etc/arje/rootkey.pub (I4 / Etapa D, hardening #3)
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>
2026-06-20 20:23:59 -04:00
sergioandClaude Opus 4.8 83d589a952 hammer-bootstrap: producto atestado cableado en product() (I4 / Etapa D, estructurar bien #1)
`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>
2026-06-20 20:01:41 -04:00
sergioandClaude Opus 4.8 a44d05e7e2 atestación firmada END-TO-END: hammer firma, arje-zero-attest verifica (I4/D)
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>
2026-06-20 19:38:01 -04:00
sergioandClaude Opus 4.8 2d18ed6dcd scripts/hammer-install: instalador a disco in-place (Etapa E2)
`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>
2026-06-20 18:32:35 -04:00
sergioandClaude Opus 4.8 8ba571aebc scripts/product-image: imagen de disco auto-booteable del producto (Etapa E)
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>
2026-06-20 18:09:09 -04:00
sergioandClaude Opus 4.8 20936e1f96 hammer-bootstrap: userland Rust en el producto + adelgazar busybox (Etapa C)
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>
2026-06-20 17:47:20 -04:00