Commit Graph
100 Commits
Author SHA1 Message Date
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 367b167991 Etapa G: cosecha cli-rust-6 (parcial) — 8 al corpus (137→145)
Granja cortada a tiempo (devuelvo la CPU); promoví manualmente los 8 sellados antes del corte:
cargo-binstall/cargo-bloat/cargo-deny/cargo-expand/cargo-machete/cobalt/fnm/ripsecrets. zola
quedó a medio compilar (no sellado) + el resto de cargo-* fallan por dep-C (openssl/git2) —
quedan staged para la próxima. build-yield parcial 8/22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 17:53:58 -04:00
sergioandClaude Opus 4.8 d109af9022 Etapa G: pre-carga la cola — tanda cli-rust-6 importada (15/15, lista para la próxima granja)
Sin construir (no peleamos CPU ahora). import-batch 15/15 (nix=14, alpine=1) + pin 14 tag→SHA.
Herramientas cargo + dev CLIs: cargo-edit/deny/audit/expand/machete/bloat/outdated/generate/
binstall + mdcat/zola/cobalt/mise/fnm/ripsecrets. La próxima sesión arranca con build-farm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 17:06:39 -04:00
sergioandClaude Opus 4.8 6bdbb17d48 Etapa G: cosecha cli-rust-5 — 15 al corpus (122→137) + tanda + cola
Granja JOBS=4 con scudo ya mitigado + toolchain 1.96: gitu/hgrep/igrep/kmon/kondo/mask/nomino/
oha/pik/ripgrep-all/rustscan/sad/serie/stylua/systeroid construyen+sellan+corren static-musl
(build-yield 15/23). pik confirma que sysinfo (antes MSRV 1.95) ya entra con 1.96. Incluye
tandas/cli-rust-5.txt (import 17/19) + dura/xcp staged (nuevos fallos: E0308/edición, triage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:42:18 -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 5468010a08 Etapa G: b3sum reescrita Cargo-limpia (WIP, queda staged)
Quita el over-build de Alpine (cmake/ninja+TBB de la libblake3 C → deps samurai/onetbb
espurias). El bin b3sum vive en el miembro b3sum/ del workspace blake3; el lab inyecta
[workspace] vacío en el root ⇒ -p b3sum no resuelve + vendorea las deps del root. Queda
staged; re-importar de nix lo daría rooteado en el crate b3sum directamente.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:28:45 -04:00
sergioandClaude Opus 4.8 f7669073d3 Etapa G: cosecha cli-rust-4 — 11 al corpus (108→119)
Granja JOBS=4 sobre la cola recargada, toolchain 1.96: atuin/csvlens/dua/erdtree/fselect/
git-cliff/rip2/rnr/skim/television/vivid construyen+sellan static-musl y se promueven al repo
firmado. build-yield 11/20. Quedan staged 9: b3sum (recipe arreglada, retry), dprint (clang-sys
pide libclang.a estática), hck/nu (deferidos), jnv/monolith/mprocs/presenterm/trippy (triage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:24:30 -04:00
sergioandClaude Opus 4.8 81ad23e8bb Etapa G: recarga la cola — tanda cli-rust-4 importada (18 staged para la granja)
import-batch sobre tandas/cli-rust-4.txt (20 entradas) → 18/20 (nix=17, alpine=1; fallaron
huniq/dua-cli por src no-clasificable). pin ancló 17 tag→SHA. Cola lista: atuin/television/
git-cliff/skim/dprint/trippy/mprocs/dua/erdtree/b3sum/vivid/monolith/presenterm/rip2/jnv/
csvlens/fselect/rnr (+ hck/nu staged previos). Techo MSRV ahora 1.96 ⇒ candidatos modernos entran.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:38:36 -04:00
sergioandClaude Opus 4.8 ae4daa44a5 Etapa G: ouch al corpus (107→108) — esquiva el frente C++ dropeando rar/bzip3
ouch construye+corre (0.8.0) con --no-default-features --features use_zlib,use_zstd_thin +
gueto gcc: mantiene gzip/zstd/bzip2/xz/zip/tar (deps C, gcc las compila), descarta unrar/bzip3
(deps C++: el link estático pide -lstdc++ y la libc++ de zig choca con los objetos g++). El
frente C++ general queda pendiente; ouch no lo necesita para los formatos comunes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:38:27 -04:00
sergioandClaude Opus 4.8 1868a2e303 Etapa G: biome al corpus (106→107) — toolchain JS/TS, cfg_select estabilizado en 1.96
biome (antes E0658 'cfg_select', macro nightly) construye+sella con 1.96. Cierra la tanda
desbloqueada por el bump: oxlint/ruff/zellij/uv/watchexec/biome = 6 al corpus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:19:25 -04:00
sergioandClaude Opus 4.8 b73fcc2b70 Etapa G: watchexec al corpus (105→106) — el feature nightly se estabilizó en 1.96
watchexec (antes E0658 'debug_closure_helpers', feature nightly) construye+sella con 1.96:
la feature se estabilizó. b3:557bff16. Confirma que el bump destraba no sólo MSRV de deps
sino features de std antes nightly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:44:50 -04:00
sergioandClaude Opus 4.8 565c5435c1 Etapa G: uv al corpus (104→105) — gestor de paquetes Python (astral), destrabado por 1.96
uv 0.8.x (MSRV 1.93, antes bloqueado) construye+sella static-musl con el toolchain 1.96
(zig-cc puro, sin gueto; sus deps C compilaron limpias). b3:7af4e822.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:31:44 -04:00
sergioandClaude Opus 4.8 c8aecf2b2f Etapa G: zellij al corpus (103→104) — multiplexor de terminal, destrabado por el bump 1.96
zellij 0.44.3 (MSRV 1.92, antes bloqueado) construye+sella con el toolchain 1.96: gueto gcc
(openssl-sys/aws-lc-sys/libgit2 en C) + deps.build=[zlib] resuelto desde recipes/ (dir corpus,
donde zlib.toml es hermano). b3:5e06634f.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:26:35 -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
sergioandClaude Opus 4.8 420cb822df Etapa G: helix al corpus (100→101) — el editor modal construye headless
Validado el fix de gramáticas: con HELIX_DISABLE_AUTO_GRAMMAR_BUILD=1 (+ gueto gcc por
tree-house-bindings/tree-sitter + de-Alpinizado cargo-auditable→cargo build) helix 25.07.1
construye+sella estático (b3:034e12cf) y se promueve al repo firmado. Las gramáticas
tree-sitter NO se compilan en build (el sandbox es hermético, sin red); van por runtime/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:32:49 -04:00
sergioandClaude Opus 4.8 c02c4fa0e0 Etapa G: xplr al corpus (99→100) + helix destraba el fetch de gramáticas
xplr construye con el gueto gcc (mlua-sys/Lua linkeaba mal _Unwind_* bajo zig-cc) → promovido.
CORPUS LLEGA A 100 recetas. helix: añade HELIX_DISABLE_AUTO_GRAMMAR_BUILD=1 — su build.rs
intentaba git-clonear 245 gramáticas tree-sitter (panic en el sandbox hermético); pendiente
de validar en la próxima construcción.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:15:53 -04:00
sergioandClaude Opus 4.8 a8684c98f4 Etapa G: fixes para la 3ra pasada — hck/helix/xplr (frente C-bajo-zig)
Triage de la 2da pasada, todos del mismo frente (crate Rust con dep-C que zig-cc rompe):
- helix: gueto gcc (tree-house-bindings/tree-sitter; cc-rs detecta familia con `zig -E` y falla)
- xplr: gueto gcc (mlua-sys/Lua; link suelta _Unwind_* de libgcc bajo zig-cc)
- hck: + deps.build=[zlib] (libdeflate ya pasó con gcc, ahora libz-sys pide -lz; gitui precedente)

NOTA: hck y zellij declaran zlib (corpus), pero resolve_build_dep busca $base_dir/zlib.toml y
en incoming/ no está ⇒ se construyen aparte desde recipes/ (donde zlib.toml es hermano).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:49:00 -04:00
sergioandClaude Opus 4.8 ff2aa48dbe Etapa G: 2da pasada de la granja — 4 al corpus (95→99) + jless diferido
Los 5 fixes de la pasada anterior validados al construir: gitoxide (max-pure mató
openssl-sys), sccache (--no-default-features), taplo y typos (eran víctimas del disco lleno,
no fallos reales) construyen+sellan y se promueven. jless DIFERIDO: la crate clipboard 0.5
es X11-only (libxcb) y removerla exige cirugía del Cargo.lock bajo --locked (clipboard-win lo
comparte rustyline) — no vale para un visor JSON teniendo jq/jaq.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:48:51 -04:00
sergioandClaude Opus 4.8 0aec90392a Etapa G: fixes de recetas para la 2da pasada de la granja (5 frentes)
Triage de los fallos de la 1ra pasada, adaptados por su causa raíz:
- gitoxide: feature `max-pure` (rustls+zlib-rs) en vez de `max` → mata openssl-sys
- sccache: --no-default-features (default=all arrastra backends cloud→reqwest→openssl)
- hck: gueto gcc (libdeflate-sys rompe en intrínsecos AVX512/evex512 bajo zig-cc)
- helix: de-Alpinizado `cargo auditable`→`cargo build` (auditable no está en el lab)
- jless: patch de-X11 que stubea la crate `clipboard` (arrastra libxcb, sin X11 headless)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:09:31 -04:00
sergioandClaude Opus 4.8 00b2cb584b Etapa G: cosecha de la granja — 7 al corpus (88→95) + descarta 4 misimports
build-farm JOBS=4 sobre la cola: bacon/cargo-nextest/cargo-watch/htmlq/jaq/joshuto/
mdbook construyen+sellan y se promueven incoming→corpus. joshuto valida que el frente
crate+dep-C (git2/libgit2-sys vendored) compila bajo el lab. Descarta 4 misimports por
colisión de nombre: dog (cat-en-C de Debian, no el cliente DNS Rust), felix (apache-felix
Java, no el file-manager), fx (json-viewer en Go, sin toolchain go), tre (libtre regex C,
no el árbol Rust). Re-pin de la próxima tanda los traerá por su crate real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:09:21 -04:00
sergio 9d29e3cf40 Etapa G: starship al corpus (87->88) — zig puro, confirma que no había deadlock
starship 1.25.1 construye+corre estático con zig-cc PURO (sin gcc). Tercer binario
Rust grande (gix, árbol gigante) que completa con paciencia tras el 'deadlock' = mito:
broot/pueue/starship los tres construyen, sólo eran lentos por codegen. Publicado al
repo firmado (88). Cola: 27 staged para la próxima granja.
2026-06-21 17:29:18 -04:00
sergio 078a4a0d7d Etapa G: recarga la cola — tanda Rust #3 importada (28 staged para la próxima granja)
import-batch sobre tandas/cli-rust-3.txt (30 entradas) -> 27/30 importadas en ~2min
(nix=25, alpine=2; fallaron yazi/rip/lln por src no-clasificable). pin ancló 24
tag->SHA. Dedup de 5 ya-en-corpus. Cola = 28 recetas listas (incl. helix/nushell/
gitoxide/ruff/uv/mdbook/jaq/bacon/... + los 6 staged previos). Aplica la lección:
el import recarga en minutos; el build es trabajo de granja (build-farm.sh, paciencia
con los binarios grandes — no son deadlocks).
2026-06-21 17:16:03 -04:00
sergio edda3a742a Etapa G: broot + pueue al corpus (85->87) — NO había deadlock de link, era lento + impaciencia
Investigué la hipótesis del "deadlock de link de zig-cc" en binarios Rust grandes
(broot/pueue/starship). RESULTADO: NO HAY DEADLOCK. La evidencia:

- pueue construye+corre con zig-cc PURO (sin gcc): selló dc735889, pueue 4.0.4. En la
  granja lo había matado a los 192s creyéndolo colgado — era rustc al 99% en codegen
  del crate final (cargo no imprime durante el codegen de una sola crate grande), +
  contención de 4 workers en 8 cores. Con paciencia y sin contención, completa.
- broot construye+corre (broot 1.57.0, static). Necesita compiler=gcc SÓLO por su
  libgit2-sys (cc-rs compila C que zig rechaza — el gueto gcc ya commiteado); el LINKER
  zig anda bien, sólo es lento por el codegen del binario grande.

Probé además linker=gcc (la hipótesis) y SÍ funcionaba, pero lo REVERTÍ: (1) estaba
basado en un diagnóstico equivocado (no hay deadlock); (2) era DAÑINO para la
reproducibilidad — delta/gitui se publicaron con linker=zig, y el linker NO entra al
artifact_hash (es código del lab, no de la receta) => un binario distinto bajo el mismo
hash => cache-hit que enmascara la divergencia. Borré el broot construido con
gcc-linker del store y lo reconstruí con el código commiteado (linker=zig) => el broot
publicado (257415a8) ahora ES reproducible con el lab tal como está en git.

recetas: broot (compiler=gcc+zlib por libgit2-sys, linker zig) y pueue (zig puro)
promovidas incoming/->recipes/. Publicadas al repo firmado (87). El lab queda INTACTO.

LECCIÓN para la granja: subir el timeout/no matar builds de binarios Rust grandes
(>192s de codegen es NORMAL, no deadlock); distinguir colgado de lento por CPU del
rustc (0% = problema, 99% = trabajando), no por silencio del log de cargo.
2026-06-21 17:12:49 -04:00
sergio 640198dab4 Etapa G: cosecha de la cola tras el gueto gcc — xsv al corpus, triage final de los 8 staged
Opcion 1 (cosechar lo que el fix ya destraba). De los 9 staged:
- xsv  PROMOVIDO (85): receta Cargo limpia + el fix del importador Alpine (filtra
  el toolchain cargo/cargo-auditable de las deps). Construye+corre estatico.
- broot: el gueto gcc SI resolvio su dep-C (libgit2-sys compilo con gcc, paso las 133
  deps y llego al crate broot), PERO se cuelga en el LINK final (192s frozen, mismo
  patron que antes) -> bloqueo ortogonal = DEADLOCK DE LINK de zig-cc (frente opcion 3).
- zellij: reescrito limpio (gcc+zlib), openssl compilo, pero MSRV rustc 1.92 > 1.91.1.
- ouch (rustc 1.93), watchexec (feature nightly E0658): MSRV/nightly, bloqueo real.
- jless: libxcb/X11 (lib faltante, no toolchain).
- dog: import del paquete equivocado (un dog.c, no el cliente DNS Rust).
- pueue, starship: deadlock de link (como broot).

CONCLUSION del triage: el gueto gcc (commit previo) resolvio el frente crate+dep-C
por completo (delta/gitui , y broot/zellij pasan su C). Los 8 que quedan staged NO
son dep-C: son MSRV>1.91.1 (zellij/ouch/watchexec), DEADLOCK de link de zig-cc en
binarios grandes (broot/pueue/starship), lib faltante (jless=X11) o import roto (dog).
El proximo leverage real = atacar el deadlock de link (recuperaria broot/pueue/starship
+ vale para cualquier binario Rust grande).
2026-06-21 16:03:02 -04:00
sergio bd0b9db11f Etapa G: importador Alpine filtra el toolchain de deps + xsv limpio (84→85)
Bug que la granja destapó: el import de Alpine de crates Rust emitía cargo/cargo-
auditable como [deps] build (el path nix ya los filtra, el Alpine no) -> la receta
abortaba buscando cargo.toml. Fix raíz en alpine_import.rs: normalize() filtra el
TOOLCHAIN (cargo/cargo-auditable/rust/rustc/go/make/cmake/meson/ninja) — lo provee
el lab (BuildSys::Cargo + detectores), no es un paquete a materializar.

xsv 0.13.0 reescrito como receta Cargo LIMPIA (sin las fases 'cargo auditable build'
del abuild que pelean con el flujo del lab, sin deps de toolchain): el lab autodetecta
Cargo. Construye+corre estático (computa stats CSV). Publicado al repo (85).
2026-06-21 15:39:01 -04:00
sergio 7eef332a9d Etapa G: gitui via gueto gcc + zlib — 2da prueba del frente crate+dep-C (83→84)
gitui 0.28.1 construye+corre estatico. openssl-src (vendorea+compila openssl 3.4
desde C) compilo con gcc (CC=gcc del fix anterior) + dep zlib para -lz. Segunda
validacion del gueto gcc para build-scripts: generaliza de libgit2-sys (delta) a
openssl-sys (gitui) — el frente crate+dep-C esta resuelto con el patron
compiler=gcc + deps.build=[libs externas]. Publicado al repo firmado (84 paquetes).
2026-06-21 15:13:51 -04:00
sergio 7c1a2ac8d5 Etapa G: gueto gcc para build-scripts de crates Rust con dep-C (frente sistematico)
La granja revelo una clase entera de fallos Rust: crates con dep-C cuyo build-script
(cc-rs) compila C con zig-cc, que lo RECHAZA/miscompila (delta=libgit2-sys,
gitui=openssl-sys, jless=libc-stdhandle). Mismo frente que file/jq en C puro -> misma
solucion: el GUETO gcc.

FIX (hammer-build/lib.rs, camino Cargo nativo): cuando la receta declara compiler="gcc",
cc-rs usa CC=gcc (el Alpine musl gcc del sandbox, probado en C) para compilar el C de
las deps; el LINKER sigue siendo el wrapper zig-cc. Los .o/.a de gcc-musl y el Rust
zig-musl son ABI-compatibles (ambos musl, mismo ELF x86-64). ADITIVO: una receta Rust
sin compiler= queda en ZigCc -> baseline (ripgrep/fd/...) intacto. 2 tests nuevos.

PROBADO END-TO-END: delta 0.19.2 construye+corre ELF estatico (libgit2-sys vendorea y
compila libgit2 desde decenas de .c con gcc; + dep zlib para -lz que libgit2/libz-sys
enlazan). Publicado al repo firmado (83 paquetes). Corpus 82->83.

LEVERAGE: libgit2-sys/openssl-sys/onig_sys/libc-stdhandle aparecen en CIENTOS de crates
-> el gueto gcc (+ resolver libs externas via deps.build) destraba esa clase entera, no
solo delta. jless queda staged (su feature de clipboard pide libxcb/X11, fuera de
alcance, NO toolchain). gitui (openssl-src vendoreado) deberia construir con el mismo
patron (compiler=gcc), pendiente de correr.
2026-06-21 14:27:16 -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
sergio 777724c036 Etapa G: promueve tealdeer/choose; muestra build-yield 5/6 (69→71)
Muestra del lote Rust #2 cerrada: eza/lsd/just/tealdeer/choose construyen+corren
(static-musl) -> 5 al corpus validado + repo firmado (71 paquetes). ouch FALLA por
MSRV (pide rustc 1.93.0; sandbox topa en 1.91.1, techo conocido) -> queda staged.
build-yield muestra = 5/6 = 83% (1 solo fallo, y por MSRV, no por toolchain musl).
2026-06-21 12:34:55 -04:00
sergio d77e629935 Etapa G: promueve eza/lsd/just del lote staging al corpus validado (66→69)
Flujo staging->corpus en accion: del lote Rust #2, los 3 primeros de la muestra
construyen+corren (ELF static-musl) -> promovidos incoming/->recipes/ y publicados
al repo firmado (69 paquetes). eza (ls moderno), lsd (ls+iconos), just (task runner).
Quedan 24 staged; se promueven a medida que la granja los construye.
2026-06-21 12:30:43 -04:00
sergio 630260f121 Etapa G: tanda Rust #2 a ESCALA — 27 recetas importadas a la cola de staging
Respuesta a "asi de a poquitos no cubrimos miles": el grueso va por LOTE, no a mano.
import-batch sobre tandas/cli-rust-2.txt (30 CLIs) -> yield import 29/30 en ~2 min
(nix=27, alpine=2; solo rargs fallo). pin-recipes anclo 26 tag->SHA. A esta tasa,
1000 paquetes ~ 1-2h de import desatendido.

ESTRUCTURA: recipes/incoming/ = COLA DE STAGING (el import vuelca en masa); recipes/
= corpus VALIDADO (construye+corre) del que build-repo.sh arma el repo firmado. La
granja/CI promueve incoming->recipes a medida que cada uno construye. Asi el corpus
mantiene su invariante sin frenar la importacion masiva.

27 recetas staged (todas parsean): eza lsd zellij starship just delta difftastic
gitui broot choose dog gping grex jless miniserve navi onefetch pastel pueue tealdeer
watchexec xsv bandwhich fclones mcfly viu ouch.

Build-yield (muestra en curso): eza OK, lsd OK (static-musl ELF). El resto se ancla
progresivamente. Las recetas a mano de las tandas previas (libs/gueto/lib+bin) fueron
para endurecer el importador en los casos duros; el largo tail navega por este lote.
2026-06-21 12:27:30 -04:00
sergio 8458602687 Etapa G: nano — editor de texto al userland (65→66)
nano 9.0: editor de terminal real, segundo consumidor de ncurses tras htop/less
(API curses widec). deps.build=[ncurses, linux-headers]; gueto gcc; sale static-pie
y corre. Redondea el userland con un editor de verdad. --disable-libmagic/-nls
(libmagic/gettext no en corpus, sólo autodetección/i18n). Publicado al repo (66).
2026-06-21 12:07:50 -04:00
sergio f73e226836 Etapa G: 4 init helpers de arje — userland de arranque (61→65)
Pueblo el userland de ARRANQUE real del distro con los helpers del init arje
(hermanos de arje-zero PID 1), patrón Cargo commit 9967b02c --locked estático:
- arje-getty-stub: agetty mínimo (ciclo de vida del login).
- arje-net-bring-up: oneshot que sube el enlace de la primera interfaz (corre: eth0 up).
- arje-installer: copia kernel+initramfs+seed a una ESP / arma USB GPT booteable.
- arje-absorb: traduce la config de otro init a una Semilla brahman (migración a arje).

Los 4 construyen+corren estáticos, publicados al repo firmado (65 paquetes).

GOTCHA recetas Cargo con lib+bin: arje-installer tiene [lib]+[[bin]]; el lab hace
'cargo rustc -p X -- <crt-static>' y cargo exige UN solo target tras '--' => agregar
'--bin <name>' a flags. arje-loader DESCARTADO: bootloader EFI (no_std, target uefi),
no static-musl userland.
2026-06-21 12:06:07 -04:00
sergio 3ec01bf788 Etapa G: tinkuy-sim + mirada-ctl — más apps tawasuyu (59→61)
Sigo poblando con apps propias de tawasuyu (patrón Cargo, commit 9967b02c --locked,
estático zig-cc, bin==package, reusan el árbol fuente):
- tinkuy-sim: simulador de dinámica molecular Lennard-Jones; corre 200 steps de 343
  partículas con reporte BLAKE3 por step (cómputo puro determinista).
- mirada-ctl: control CLI del compositor mirada (estilo swaymsg/hyprctl, cliente IPC).

Ambos construyen+corren estáticos, publicados al repo firmado (61 paquetes).

uya-cli DESCARTADO: requiere la lib de audio alsa (alsa-sys/libasound C), no en el
corpus -> fuera de alcance hasta portar esa lib. Texture honesta: no toda app
tawasuyu es static-musl pura; las que tocan audio/GPU/p2p necesitan libs o no aplican.
2026-06-21 11:54:27 -04:00
sergioandClaude Opus 4.8 a14d888321 Etapa F: cierra el hilo userland — el producto arma su userland desde el repo FIRMADO
Hasta hoy el bootstrap del producto hidrataba el userland desde recetas locales
hardcodeadas (build_components sobre USERLAND_COMPONENTS/SERVICE_COMPONENTS). Cierre
del dogfood: la IMAGEN ahora puede armar su userland por la CADENA DE SUMINISTRO de
paquetes — verifica la firma del release y REPRODUCE cada componente desde su .swm.

hammer-bootstrap:
- product_from_repo(base, repo, trust, ...): igual que product() pero el userland +
  servicios salen de install_components_from_repo en vez de build_components.
- install_components_from_repo: verifica firma del release (exige TRUSTED), por cada
  componente resuelve el cierre de deps + puebla el catalogo dep.toml + reproduce el
  source_patch con build_source_patch (chequea el expected_hash anclado). Devuelve los
  mismos (nombre,hash) que build_components.
- seal_product_rootfs: pasos 2-3 comunes extraidos (seed+hash+ensamblado+sellado).

hammer-cli: bootstrap product --from-repo DIR --trust DIR.

PROPIEDAD CLAVE VERIFICADA E2E: como el hash es por-CONTENIDO, reproducir desde el
.swm da los mismos (nombre,hash) que construir la receta -> el product-rootfs via
repo firmado es BIT-IDENTICO al hardcodeado (ambas vias -> ba351f1b). Firma del
release verificada (TRUSTED by release) antes de tocar nada. "Verificar, no confiar"
aplicado al propio ensamblado de la imagen. 40 tests bootstrap verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:46:11 -04:00
sergio 3429bcf1d4 Etapa G: agora-cli + cosmos-cli — más apps tawasuyu al catálogo (57→59)
Sigo poblando con apps propias de tawasuyu (mismo patrón Cargo, commit 9967b02c
--locked, estático zig-cc, bin==package, reusan el árbol fuente fetcheado):
- agora-cli: shell del ágora (identidad/atestaciones/grafo firmado), deps livianas.
- cosmos-cli: cliente del servicio de cómputo astronómico (usa tokio, build sano musl).

Ambos construyen+corren estáticos y publicados al repo firmado (59 paquetes).
2026-06-21 11:37:51 -04:00
sergio 4eea475bab Etapa G: dominium-cli — primera app propia de tawasuyu poblada (corpus 56→57)
Poblar el catálogo no es sólo CLI de terceros (Rust/C): tambien las APPS PROPIAS
del monorepo tawasuyu. dominium-cli es el runner headless del simulador físico de
dominium — patrón Cargo igual que arje-zero/llimphi-counter: source = tawasuyu a
commit fijado 9967b02c (con Cargo.lock committeado -> build --locked reproducible),
-p dominium-cli, estático zig-cc. bin==package (sin mismatch de workspace). Cómputo
puro (clap+serde+physics), sin GPU/red. Comparte commit con arje-zero -> reusa el
árbol fuente fetcheado.

Construye+corre: simulación de 100 ticks a 62k tps con métricas Gini/Moran.
Dogfood e2e: publicado al repo firmado (57 paq) -> install --require-signed ->
trusted -> reproduce desde fuente (hash casa) -> hidrata -> corre.
2026-06-21 11:24:01 -04:00
sergio a9cba5002e Etapa F dogfood: fix bug de patch en install (.swm con patch + build-dep)
Dogfoodear el corpus entero (build-repo.sh: 55/56 con expected_hash anclado, repo
firmado Ed25519) destapó un bug latente en la reconstruccion desde .swm: el path
del patch se prefijaba con el catalogo (swm-recipes/) TRES veces, y el install
abortaba con 'No such file'. jq es el PRIMER paquete con patch + build-dep que se
instala (fd/bwrap no tienen patch) -> nunca se habia ejercido ese camino.

Causa: recipe_from_source_patch metia catalog_dir en el path Y synthesize_recipe lo
re-unia a base_dir, redundante con el base_dir.join(p) que el lab hace al construir
(fetch::apply_patches / Recipe::hash). Fix: source.patches guarda solo el NOMBRE
relativo a base_dir; un unico join lo resuelve. El hash usa el CONTENIDO del patch,
no el string del path -> el expected_hash anclado sigue casando.

Verificado e2e: install jq desde el repo firmado -> trusted -> reproduce desde
fuente (hash e770e04d = el anclado) -> patch aplicado -> 18 ficheros hidratados ->
registrado en DB -> jq corre. + test de regresion inline_patch_resuelve_con_un_solo_join.
2026-06-21 11:12:52 -04:00
sergio 371d2f7cce Etapa G: htop — consumidor TUI canónico de ncurses (55→56)
htop 3.5.1 ejerce la API curses real de ncurses (ventanas/teclado/colores widec),
no sólo el lookup de terminfo de less. deps.build=[ncurses, linux-headers]; sale
static-pie y corre (--version). Valida ncurses como lib TUI completa del corpus.

Ajustes sobre el import: quito lm-sensors (opcional, no en corpus) con
--disable-sensors; gueto gcc (zig-cc miscompila); auto-detecta ncursesw widec.
2026-06-21 10:59:32 -04:00
sergio cbca6143f0 Etapa G: ncurses + less — lib TUI/terminfo al corpus (53→55)
ncurses es la lib fundacional del userland TUI (destraba less/htop/top/nano/ncdu).
La importo de Alpine pero RE-ANCLADA al release ESTABLE 6.5 de GNU (pineable; el
import traia el snapshot semanal de invisible-mirror, no reproducible) y
SIMPLIFICADA a estatico widec (--without-shared --enable-widec), sin el binding
C++ (rompe contra el libstdc++ del host) ni la maraña .so del APKBUILD. El install
crea los symlinks libncurses/libtinfo/libcurses -> libncursesw para que un
consumidor que enlaza -lncurses/-ltinfo resuelva.

less 704 es el primer CONSUMIDOR que lo valida: deps.build=[ncurses], enlaza
-ltinfo, sale static-pie y pagina. Confirma ncurses usable como lib del corpus.

Notas: gcc del lab = Alpine musl gcc (x86_64-alpine-linux-musl, no glibc) -> mezcla
limpia con objetos zig-cc/musl. gueto gcc en ambos (el build de ncurses corre tic
para generar terminfo; zig-cc lo miscompila). Corpus 53->55.
2026-06-21 10:57:59 -04:00
sergio 74baaf3130 Etapa G: oniguruma + jq destrabados — lib de regex al corpus (51→53)
jq estaba bloqueado por su lib de regex faltante. Importo oniguruma de Alpine
(lib build-dep como zlib/libcap: el lab apila libonig.a + oniguruma.pc en /usr)
y cableo jq con deps.build=[oniguruma].

Fricciones C resueltas, documentadas en las recetas:
- oniguruma: el archive/ de GitHub no trae configure (Alpine corre autoreconf,
  pide autoconf/automake/libtool ausentes del corpus); uso el tarball de RELEASE
  que sí trae configure pregenerado -> sin autoreconf.
- jq: gueto gcc (zig-cc lo miscompila -> segfault, igual que file). Estático vía
  libtool requiere -all-static en make Y en make install (libtool relinkea al
  instalar y descartaba el flag -> binario dinámico). Ahora jq sale static-pie.
- el import emitia sed como build-dep; es herramienta del sandbox, no lib -> la quito.

Ambos construyen+corren: jq-1.8.1 static-pie evalua JSON. Corpus 51->53.
2026-06-21 10:52:45 -04:00
sergioandClaude Opus 4.8 89061cac03 Etapa G: tanda C tier-2 construye+corre — 6 al corpus (45->51) + wrap abuild local
BUILD-YIELD C MEDIDO (no especulacion): 6/8 de la tanda construyen+corren como ELF
estatico musl. Promovidos: file 5.47, gawk 5.3.2, gzip 1.14, tar 1.35, tree 2.3.2,
which 2.23 (+ sus parches musl de Alpine).

Texture honesta del tier-2 C (la friccion que el tier-1 Rust no tiene):
- gawk/tar/tree/which: zig-cc directo, sin tocar nada.
- file: zig-cc MISCOMPILA -> el `file` recien hecho segfaultea generando magic.mgc
  (mismo sintoma que binutils). Escape compiler="gcc" -> construye.
- gzip: (1) configure "C compiler cannot create executables" con zig-cc -> compiler="gcc";
  (2) luego el install fallaba por `local i;` de la package() de Alpine.
- jq (oniguruma), sed (perl): NO build-friction sino dep faltante en el corpus
  (completitud) -> quedan pendientes hasta importar esas libs.

Fix generico que destrabo gzip (y futuros): el importador Alpine ENVUELVE el cuerpo de
build()/package() en una funcion shell. abuild los corre COMO funciones (donde `local`
es valido); el lab corre la fase plana bajo sh -c, donde `local` fuera de funcion es
error. Envolver restaura el contexto de abuild sin tocar el sandbox ni las fases planas
del corpus (solo lo importado). test translate_wraps_body_in_function_for_local.

Confirmado: los 6 binarios corren (--version). file/gzip llevan compiler="gcc" en su
receta (escape declarativo, gueto conocido).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:07:08 -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 e83542a4a9 Etapa G: primera tanda a escala — 11 CLI Rust construyen+corren (corpus 34→45)
Arranca el escalado del catálogo con la tanda tier-1 (tandas/cli-rust.txt) por el
pipeline completo: import-batch (escalera) → pin (tag→SHA) → build (medir) → promover.

BUILD-YIELD MEDIDO (no especulación): 11/12 de la tanda construyen+corren como ELF
estático musl en el lab. Los 11 promovidos al corpus:
  bat 0.26.1, bottom 0.12.3, dust, fd 10.4.2, hexyl, hyperfine, procs, sd, tokei,
  xh 0.25.3 (rustls, no openssl), zoxide.
(ripgrep ya estaba en el corpus con su patch jemalloc; no se duplica.)

El primer corte dio 8/12: los 4 con deps fallaban por buildInputs espurias de nix
(bat→zlib, fd→jemalloc, ripgrep→pcre2, xh→openssl) — backends C que el build Rust por
defecto NO usa. Arreglado en el importador (commit anterior): re-importados bat/fd/xh
SIN [deps] → los 3 construyen (bat 372s, fd 251s, xh 413s). Sube 8/12 → 11/12.

Verificado: los binarios CORREN (fd/bat/xh --version), estáticos musl stripped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:47:52 -04:00
sergioandClaude Opus 4.8 8913eae26b Etapa G: el importador nix NO emite buildInputs como [deps] en recetas Rust
Hallazgo al escalar la primera tanda (12 CLI Rust): `hammer build` resuelve las deps
relativas al dir de la receta y aborta si falta `recipes/<dep>.toml`. Las recetas Rust
importadas arrastraban los buildInputs de nix como `[deps]` activas — pero nix lista el
closure MAXIMAL (todos los backends C opcionales: zlib/pcre2/openssl/jemalloc), mientras
el build Rust del lab usa las features DEFAULT de cargo (backend Rust puro: miniz_oxide
vs zlib, rustls vs openssl) o las deja opt-in (pcre2). bat→zlib, fd→jemalloc,
ripgrep→pcre2, xh→openssl fallaban al instante por deps espurias.

Las recetas Rust validadas del corpus (ripgrep/uutils) NO declaran `[deps]`: cargo
resuelve el grafo por vendoring; un sys-lib C que SÍ haga falta es adaptación per-paquete
(patch/feature, p.ej. ripgrep-no-jemalloc), no una dep de corpus.

- is_rust ⇒ los buildInputs quedan como COMENTARIO de provenance (no se pierden: señalan
  qué C podría necesitarse), no como `[deps]`. Imports C (no-Rust) intactos.
- test rust_buildinputs_are_not_active_deps; filters_nix_stdenv_noise (C) sigue válido.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:09:19 -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 41aec17048 Etapa G Fase 3: flags autotools $CBUILD/$CHOST — el lab provee el triple nativo
El residuo autotools de los imports de Alpine (configure --build=$CBUILD --host=$CHOST
del abuild) ya no es trabajo a mano:

- El lab exporta CBUILD/CHOST con el triple nativo SANEADO (x86_64-linux-musl, el
  mismo que el wrapper zig-cc emite) ⇒ las fases traducidas de Alpine que referencian
  $CBUILD/$CHOST literal resuelven en runtime en vez de quedar vacías (config.guess
  detectaría x86_64-alpine-linux-musl, vendor que zig rechaza).
- La heurística autotools inyecta --build/--host al triple saneado cuando la receta no
  los puso ya (juicio per-paquete gana). build==host ⇒ NATIVO: autotools sigue corriendo
  sus AC_RUN tests; sólo normaliza el triple.
- Inerte para Cargo/CMake/Meson (no leen esas envs ni el triple).

VALIDADO REAL: e2e autotools BUILDEA (configure 'cross compiling... no', sella+corre).
3 tests nuevos de heurística + import comment actualizado.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:57:05 -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 a157b8c455 Etapa G Fase 1: extractor de workspaces Rust — -p <pkg> auto en workspaces virtuales
Sube el yield Rust a casi-todo el ecosistema CLI moderno (sd/fd/… usan workspace virtual: root
sólo agrupa, el bin vive en un sub-paquete ≠ pname). Antes exigía `-p` manual; ahora el lab lo
resuelve solo.

- hammer-build/lib.rs: cargo_root_is_virtual (root con [workspace] sin [package]) +
  resolve_cargo_bin_package (lee los members vía `cargo metadata` —fuente autoritativa: globs,
  [[bin]], src/bin/*, nested— y devuelve el paquete que expone el bin) + inject_cargo_package_selector
  (si virtual y flags piden --bin X sin -p, antepone `-p <pkg>`). Determinista ⇒ reproducible; el
  hash usa los flags ORIGINALES, el -p es resolución interna. Crate suelto / ripgrep: intactos
  (no virtual). serde_json a deps de hammer-build.
- VALIDADO REAL: sd (workspace virtual, bin en `sd-cli`≠pname) ahora BUILDEA SOLO (sd 1.0.0),
  sin tocar la receta (flags quedan --bin sd, el lab resuelve -p sd-cli). 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:45:04 -04:00
sergioandClaude Opus 4.8 6e4d8da779 Etapa G build-yield: imports Rust de nix BUILDEAN (aislamiento [workspace] genérico + filtro toolchain)
Midiendo build-yield real con la capa puesta: hyperfine (nix) construye end-to-end → ELF estático
musl que corre. Dos fixes que lo desbloquean genéricamente (sin patch por receta):

- nix_import.rs: rustc/cargo/rust se filtran de deps (son el LAB, no paquetes) — sin esto el build
  abortaba buscando rustc.toml. Default de flags Rust vuelve a `--bin <bin>` (el `-p <pname>` no
  generaliza: el paquete cargo del bin puede ≠ pname, p.ej. sd→sd-cli).
- hammer-build/lib.rs: `ensure_cargo_workspace_isolation` inyecta `[workspace]` vacío al Cargo.toml
  de la fuente si no lo tiene, ANTES de vendor. Idempotente ⇒ no choca con las recetas del corpus
  que lo parchean a mano. Resuelve el gotcha "fuente dentro del workspace hammer ⇒ cargo vendor
  aborta" para CUALQUIER import Rust.

BUILD-YIELD medido (real, con la capa): lz4 (C/Alpine, escape gcc) ✓ · hyperfine (Rust/nix) ✓ ·
sd (Rust) ✗ workspace-virtual con bin en paquete ≠pname (necesita `-p` manual). Texture honesta:
los bien-estructurados buildean solos; los con quirks de workspace necesitan toque per-paquete.
31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:31:56 -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 b66cae91ae Etapa G capa-de-adaptación #3: traducir mirror:// de nix a URLs concretas
nix resuelve `mirror://<sitio>/...` en eval; un import los deja literales y el curl de hammer no
los entiende. expand_nix_mirror() mapea los comunes (gnu/savannah/kernel/sourceforge/gnome/
apache/xorg/pypi/cpan/debian) a un espejo real; lo no mapeado se deja igual. +1 test.
Validado: import hello → tarball https://ftp.gnu.org/gnu/hello/... (antes mirror://gnu/...).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:20:08 -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 2d81bc6eaa Etapa G capa-de-adaptación #1: el lab honra build.compiler (escape de linker declarativo)
El muro del piloto: zig-cc filtra los flags de lld (rechaza -Wl,--allow-multiple-definition y
-z muldefs) ⇒ paquetes C con símbolos duplicados (lz4) no linkean. El escape es usar un toolchain
real (gcc de Alpine = musl + GNU ld, que SÍ acepta -z muldefs), pero el sandbox FIJABA CC="zig cc"
ignorando el campo `compiler` de la receta.

- hammer-build/lib.rs: `build.compiler` ahora setea CC/CXX/AR reales en el env (gcc→gcc/g++/ar,
  clang→clang/clang++/llvm-ar; zig-cc = default sin cambio). `self.env` pisa los defaults del
  sandbox. gcc/clang default a x86-64 genérico ⇒ reproducible, sin -mcpu=baseline.
- PURAMENTE ADITIVO: ningún recipe del corpus declara compiler=gcc (el gueto usa CC=gcc en fases),
  y el 4/4 núcleo es zig-cc ⇒ baseline of_tree intacto.
- VALIDADO REAL: lz4 (importado de Alpine) con `compiler = "gcc"` declarativo (fase SIN CC=gcc)
  → construye + corre (lz4 v1.10.0). El paquete que moría en el lld de zig ahora sella. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:14:34 -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 f94ef01139 Etapa F refinamientos: uninstall poda dirs vacíos + install --require-signed
Dos bordes ásperos de la paquetería:
- uninstall ahora retira los directorios que quedaron VACÍOS por el borrado (rmdir de abajo
  arriba; remove_dir sólo borra dirs vacíos ⇒ se detiene solo al toparse con contenido de otro
  paquete). Antes dejaba /usr/bin, etc. huérfanos.
- `install --require-signed`: modo estricto que ABORTA si el release no está firmado por una
  clave confiada (Unsigned o UnknownKey ⇒ error). No basta con que el .swm reproduzca: exige
  autoría verificada del catálogo. Default off (no rompe flujos sin firma).

Validado E2E: uninstall bwrap poda 4 dirs; --require-signed aborta sin firma y procede con
release trusted. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:12:58 -04:00
sergioandClaude Opus 4.8 910db267ef Etapa F paquetería #6: repo sobre red — install --repo URL (HTTP/HTTPS)
El repo son ficheros estáticos (index.json + .swm) ⇒ cualquier servidor estático lo sirve.

- hammer-cli: `RepoSource` {Local(path) | Http(url)}. `install --repo` ahora acepta path o URL.
  Para HTTP: lee index.json por GET, materializa un repo LOCAL temporal bajando el índice + los
  .swm del cierre de deps (curl, vía download::fetch_url_bytes), y de ahí el flujo es IDÉNTICO al
  local (resolución de deps, verificación de release/firma/base, reproduce + hidrata). tempfile
  pasa a dep normal de hammer-cli.
- Validado E2E: server HTTP estático + install openssh vía http:// → "release: trusted" (índice
  firmado bajado por red) → "repo: bajados 3 .swm" (cierre openssh+zlib+openssl) → resuelve del
  temporal. (El proxy del sandbox exige NO_PROXY para localhost; el código es correcto.) 31 verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:07:32 -04:00
sergioandClaude Opus 4.8 9d1b2d4a76 Etapa F paquetería #5: DB de instalados + hammer uninstall/installed
Funcionalidad de gestor de paquetes: rastrear qué hay instalado y poder quitarlo.

- hammer-core/installed.rs: `InstalledDb` (name→{version,hash,files}) load/save JSON; record
  (upsert), remove, `owned_by_others` (refcount por ruta). Rutas absolutas ⇒ uninstall no
  necesita el root. 4 tests.
- hammer-cli: run_apply ahora DEVUELVE los ficheros que CREA (hidratados + file_drop + init_rule;
  config_edit modifica, no crea ⇒ no se registra ni se deshace). install los registra en la DB
  (--db, default /var/lib/hammer/installed.json). `uninstall <nombre>` borra esos ficheros salvo
  los que otro paquete instalado aporta (refcount) y quita la entrada. `installed` lista.
- Validado E2E REAL: install bwrap (con dep libcap) → registra 2 ficheros → `installed` los
  lista → `uninstall bwrap` los borra (prefix vacío, DB vacía). 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:01:46 -04:00
sergioandClaude Opus 4.8 977f07ca6c Etapa F paquetería #4: firma del release (índice del repo firmado)
Cierra el hueco de seguridad: hoy se firmaba cada .swm (autoría del paquete) pero NO el
catálogo ⇒ un atacante podía añadir/quitar/intercambiar entradas del index.json. Firmar el
release ancla qué paquetes/versiones/hashes existen.

- hammer-core/sign.rs: extraídos `KeyPair::sign_raw` + `verify_raw` genéricos (bytes canónicos
  arbitrarios); Swm::{sign,verify_signature} ahora los reusan (DRY, sin cambio de comportamiento).
- hammer-core/repo.rs: `RepoIndex.signature` (Ed25519 sobre la lista de paquetes canónica,
  excluye la propia firma) + `sign`/`verify_signature`. `upsert` INVALIDA la firma (cualquier
  cambio al catálogo ⇒ re-firmar). 4 tests (sign→verify, survive save/load, upsert-invalida,
  tamper→BadSig).
- hammer-cli: `repo sign --key` / `repo verify`; `install` VERIFICA el release antes de resolver
  (BadSig ⇒ aborta "el índice fue manipulado"); `repo list` muestra si está firmado.
- Validado E2E host: sin firmar→firmar→trusted→install lo ve; MANIPULAR el índice sin re-firmar
  ⇒ install ABORTA; re-publicar invalida la firma. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:53:31 -04:00
sergioandClaude Opus 4.8 b89e2dba51 Etapa F paquetería #3: deps entre paquetes — install resuelve el cierre y reconstruye el catálogo
Cierra el hueco que pack/install advertían: un paquete con build-deps (bwrap→libcap,
openssh→zlib,openssl) ahora se instala por nombre reproduciéndose desde fuente CON sus deps.

Modelo: las build-deps viajan por NOMBRE en el source_patch y en la PackageEntry; install
resuelve el cierre transitivo desde el índice y reconstruye un catálogo de recetas que el lab
consulta al materializar deps en el sandbox.

- hammer-core: `Mutation::SourcePatch.deps` (Deps, serde-skip si vacío) + `from_recipe` lo
  carga. `Deps::is_empty`. `RepoIndex`/`PackageEntry.deps` + `resolve_closure(name)` (DFS
  topológico, deps antes que dependientes, detecta dep faltante y ciclo). 8 tests nuevos.
- hammer-build/swm_bridge: refactor — `recipe_from_source_patch` (síntesis pública, setea deps
  + base_dir=catálogo) + `catalog_dir_for` (dir determinista compartido). build_source_patch
  lo reusa. synthesize_recipe ahora setea recipe.deps + base_dir al catálogo (no "/").
- hammer-cli: pack puebla PackageEntry.deps; install resuelve el cierre y escribe un {dep}.toml
  por dep en el catalog_dir ANTES de aplicar el target (mismo dir determinista que usa
  build_source_patch ⇒ el lab resuelve {dep}.toml por nombre). Warning de pack actualizado.
- VALIDADO E2E REAL contra ./store: `install bwrap` resuelve libcap del catálogo y reproduce
  el artefacto CACHEADO EXACTO (b3:f89e716…) → hidrata bwrap (1.8MB ELF). El paquete con dep
  hashea bit-idéntico al original. Resolución/diamante/faltante/ciclo unit-tested. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:43:30 -04:00
sergioandClaude Opus 4.8 e867bac388 Etapa F paquetería #2: repositorio + hammer install <nombre> / repo list
Cierra el lazo "packié un .swm → lo instalo por nombre". El repo es el namespace que
le da identidad a los .swm (que en sí no la llevan).

- hammer-core/repo.rs: `RepoIndex` + `PackageEntry` (load/save index.json, find, upsert
  idempotente por nombre que reporta el .swm huérfano). Índice JSON plano, ordenado,
  diffeable, firmable a futuro como release. 4 tests.
- hammer-cli:
  * `pack --repo DIR` PUBLICA (escribe <repo>/<name>-<version>.swm + upsert al índice con
    distro_version/expected_hash/signed_by; retira el huérfano de una versión vieja).
  * `install <nombre> [--repo] [--trust] [--base-ref] [--prefix] [--skip-source-patch]`
    CONSUME: resuelve nombre→.swm, verifica firma (con --trust) ANTES de reproducir, delega
    en el camino de apply (reproduce source_patch + hidrata). Nunca corre binario ajeno.
    Nombre inexistente → error legible con los disponibles.
  * `repo list` imprime el catálogo.
- Validado E2E en host: publicar ripgrep (firmado) + findutils, repo list, index.json limpio,
  install ripgrep --trust → "firma: trusted (by alice)" → apply OK. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:28:16 -04:00
sergioandClaude Opus 4.8 339a7b07ef Etapa F paquetería #1: hammer pack — receta del corpus → paquete .swm (source_patch)
Cierra la dirección forward que faltaba (SDD 06 §6 la marcaba "para más adelante"):
una receta que el sistema ya sabe construir se vuelve un paquete distribuible y
reproducible-desde-fuente, inversa de `hammer apply`.

- hammer-core: `Swm::from_recipe(recipe, target_bin, patch_text, expected, distro)`
  (constructor puro: el caller lee los patches). `SwmBuild` gana `phases`+`zig_version`
  y `SourcePatch` gana `strip_components` (Option/skip ⇒ .swm viejos parsean igual) para
  reproducir con fidelidad el corpus real (22/34 recetas usan phases, 8 usan zig 0.13).
- hammer-build/swm_bridge: la dirección inversa (source_patch→Recipe→build) ahora traslada
  phases/zig_version/strip_components a la receta efímera ⇒ apply rehace idéntico.
- hammer-cli: `hammer pack <recipe> [--target-bin] [--out] [--expected|--build] [--sign]`.
  Concatena los patches inline; avisa si la receta declara deps (el source_patch aún no
  las modela = pieza posterior). `export` también enriquece su source_patch.
- Validado en host: ripgrep (git+patch+install custom), openssl (tarball+zig 0.13+phases),
  coreutils (multicall), findutils firmado → swm-verify "trusted". Tests core+bridge verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:20: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 3225fb10b8 recipes/mtools: GNU mtools 4.0.49 from-source estático (E5/#3 EFI, herramental)
Poblar una ESP FAT sin privilegios (ni loop-mount root) para el arranque UEFI
exige mtools (mformat/mcopy/mmd), ausente en el host. Pieza simétrica a xorriso
para la rama EFI. zig 0.13.0, estático musl, --without-x, sin deps (iconv
built-in de musl). Sella d7892990.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:22:27 -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 a1b8b57676 recipes/xorriso: GNU xorriso 1.5.8 from-source estático (Etapa E5, herramental)
El host no trae xorriso (y grub-mkrescue lo exige) ⇒ se construye desde fuente
con hammer para fabricar el medio ISO. Tarball GNU (libburn+libisofs+libisoburn
en un árbol), zig 0.13.0, 100% estático musl, todas las libs opcionales
desactivadas (readline/edit/acl/xattr/zlib/bz2/cdio) ⇒ binario autocontenido sin
deps. Sella b63d1a64, corre standalone (xorriso 1.5.8 + personalidad xorrisofs
mkisofs-compatible). Mismo patrón -static/-target musl de openssh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:15:53 -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 30daab35e6 Cargo.lock: registrar hammer-mirror (cierre E3 primer corte)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:37:25 -04:00
sergioandClaude Opus 4.8 b9d3170859 hammer-mirror: mirror del store content-addressed (Etapa E3, primer corte)
Nuevo crate hammer-mirror + CLI `hammer mirror push|pull|status <remote>`. El /store es un
CAS (dir `<hash>-<name>`, el hash ES la dirección); un mirror = ese CAS replicado entre
máquinas + resolución por hash. La integridad se ancla en of_tree (content-hash BLAKE3 del
árbol): el receptor RECOMPUTA of_tree sobre lo copiado y exige que case el del índice ANTES
de sellar (Store::seal, atómico + read-only en un staging del mismo FS) ⇒ una transferencia
corrupta/manipulada se RECHAZA, no se instala.

- index(store): enumera dirs `<64hex>-<name>` (filtra bootstrap.json/.bootstrap-tmp), of_tree
  cada uno. diff(src,dst): faltantes en cada lado + conflictos (mismo dir, otro contenido).
- sync(src,dst): copia los que faltan (verificados), idempotente, no sobrescribe conflictos.
  push = sync(local,remoto); pull = sync(remoto,local).
- Transporte = filesystem (el remoto es una ruta a otro store, p.ej. sshfs); SSH/red queda
  como envoltorio ortogonal posterior. Endurecimiento: anclar of_tree a una raíz firmada
  (bootstrap.json / atestación D) vs un origen plenamente malicioso.

5 tests unitarios (forma de dir, índice, sync idempotente, rechazo de transferencia corrupta,
conflicto-no-sobrescribe) + validado E2E con el CLI sobre artefactos reales (push/pull/status,
idempotencia, bytes idénticos, tamper→conflicto, conflicto no sobrescrito exit≠0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:35:14 -04:00
sergioandClaude Opus 4.8 f7ea593e2d recipes: arje-zero-attest/arje-packager a 06184e43 (Cargo.lock → build --locked, Etapa D #2)
Bump del commit pinneado de c78a0ada → 06184e43 (rama tawasuyu selfhost/arje-zero-attest-
lockfile = c78a0ada + Cargo.lock del workspace force-committeado). Sin lock, tawasuyu lo
gitignora y `cargo vendor` corre SIN --locked ⇒ las deps derivan ⇒ el gated arje-zero (y el
of_tree del producto atestado) no es reproducible. Con el lock, el build es
`cargo rustc --release --locked --offline` ⇒ bit-reproducible. Espeja el patrón del núcleo
(arje-zero.toml @ selfhost/arje-zero-lockfile 9967b02c).

Lock regenerado sobre c78a0ada reusando los pins de main (1 línea de diff ⇒ versiones
MSRV ≤ 1.91.1 del sandbox preservadas). Rebuild --locked validado en host: compila limpio
(418 crates, sin error MSRV), sella arje-zero-attest d1a6f5c7 + arje-packager 2267b9b0;
producto atestado d35a9c09 BOOTEA en QEMU (gate anclado a rootkey soberana, 3 ✓, SSH OK).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:54:34 -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 0d11d68b44 atestación de integridad al arranque — mitad de hammer (I4 / Etapa D)
Por la regla de oro del plan arje↔hammer (PLAN-ATESTACION-Y-HAMMER.md §B.1):
hammer es dueño del expected_hash + TrustStore; arje del gate al boot. Esta
es la mitad de hammer — PRODUCIR y auto-verificar el manifiesto de hashes
esperados que el gate A2 de arje consumirá ("el expected_hash de un .swm ES
el BLAKE3 que arje atesta").

- hammer-core: ArtifactHash::of_file = BLAKE3 CRUDO del fichero (sin framing),
  el mismo que computa arje-cas::blake3_of ⇒ casa con quien recompute el hash.
- hammer-bootstrap: el producto emite /ente/attest.json con el BLAKE3 esperado
  de los binarios críticos (ATTEST_PATHS: arje-zero PID1, hammerd, busybox,
  sshd, netup, coreutils). Fichero APARTE de la seed card ⇒ no toca el schema
  de card-core y el producto bootea igual con el arje-zero actual (ignora A2).
  verify_attestation() recomputa y compara (ok/diverge/falta). product hash v3.
- CLI: `hammer attest --rootfs <dir>` — el gate de integridad hecho hoy por
  hammer ("reproducir, no confiar"); exit≠0 si algo diverge/falta.
- 37 tests verde (manifiesto + verify + tamper + missing).

Validado: el producto emite attest.json con los 6 binarios críticos; `hammer
attest` ✓ los 6; alterar 1 byte de coreutils ⇒ "✗ DIVERGE" exit 1; el producto
con attest.json sigue booteando + SSH (arje ignora el fichero). El gate al boot
(A2) es la mitad de tawasuyu/arje-zero (cross-repo, fuera de este commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:45:14 -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
sergioandClaude Opus 4.8 7baaf4afc5 hammer-bootstrap: capa de servicios (sshd como producto) — Etapa C Paso 2
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>
2026-06-20 17:31:20 -04:00
sergioandClaude Opus 4.8 3dcc0760fd scripts/ssh-e2e-test: spike E2E de sshd en QEMU (Etapa C, Paso 1 verde)
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>
2026-06-20 17:23:06 -04:00
sergioandClaude Opus 4.8 c8eebe12b0 recipes/openssh: sshd/ssh from-source, estático musl (Etapa C pieza 6)
OpenSSH 10.3p1 desde fuente — LA excepción C del userland Rust (no hay
servidor SSH Rust de producción). Provee sshd+ssh+ssh-keygen+scp/sftp.
deps.build = zlib + openssl (libcrypto.a/libz.a estáticas), zig 0.13.0.

Binarios 100% estáticos musl. Tres gotchas zig-cc/-static resueltos:
- -pie de OpenSSH ANULA -static (sale dinámico) -> --without-hardening
  desactiva el PIE automático del "toolchain hardening".
- zig cc -static -lz, con .a y .so en /usr/lib, PREFIERE el libz.so de
  Alpine (NEEDED libz.so.1) -> sed CHANNELLIBS a -l:libz.a/-l:libcrypto.a
  (archivo estático exacto).
- zig cc NATIVO (sin -target) -static deja libc.so dinámico igual ->
  CC con -target x86_64-linux-musl usa el musl embebido de zig.

Validado en host: todos los binarios statically linked y corren
(ssh -V, sshd -V, ssh-keygen genera ed25519 con cripto real). Handshake
loopback en bwrap llega a preauth (accept TCP + IPC privsep monitor/child
+ parse hostkey OK); sólo lo frena chroot("/var/empty") por falta de
CAP_SYS_CHROOT en el userns anidado -> el E2E completo va a la card de
servicio in-VM (root real). Runtime: usuario sshd + /var/empty + host
keys via ssh-keygen -A.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:59:29 -04:00
sergioandClaude Opus 4.8 405ef5930a recipes/netup: receta Cargo de netup (Etapa C pieza 5)
Construye `-p netup` del workspace hammer (commit 6886f81) con deps vendoreadas
--offline, static musl vía zig cc. A diferencia de uutils/findutils/ripgrep
(adopción de binario maduro), netup es código hammer-propio (patrón hammerd).
Construye+sella: b3:9189d86…-netup, ELF static sin interpreter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:21:15 -04:00
sergioandClaude Opus 4.8 6886f810bd crates/netup: red mínima Rust-nativa (Etapa C pieza 5)
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>
2026-06-20 16:20:22 -04:00
sergioandClaude Opus 4.8 50960d3c38 Etapa E1: imagen de disco auto-booteable (GRUB BIOS), sin -kernel
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>
2026-06-20 13:29:47 -04:00
sergioandClaude Opus 4.8 ffaccb9a98 recipes/ripgrep: rg Rust-nativo (Etapa C pieza 4)
ripgrep 14.1.1 (tag, commit 4649aa97) como receta Cargo, mismo patrón que
uutils/findutils/diffutils. Es su propio workspace ⇒ sin patch [workspace].
Estático musl, zig cc sólo linker; MSRV 1.72 ≤ 1.91.1.

ripgrep-no-jemalloc.patch: ripgrep mete jemallocator incondicional en musl64,
pero jemalloc-sys compila C (autotools) y embebe rutas de OUT_DIR ⇒ rompería la
reproducibilidad bit-a-bit del lab. El patch lo quita (dep Cargo.toml + stanzas
Cargo.lock + global_allocator en main.rs) ⇒ build 100% Rust, hermético, con el
allocator de musl. PCRE2 ya es opt-in ⇒ regex puro, sin C.

Construye+sella+corre: ELF static sin interpreter, `ripgrep 14.1.1
features:-pcre2`, búsqueda recursiva/glob OK. `rg` no es flag-compatible con
POSIX grep ⇒ no se symlinkea a grep; convive con el grep interino.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:16:45 -04:00
sergioandClaude Opus 4.8 4dfb0ff127 Etapa B3: /store y /var/lib/hammer en particiones dedicadas (GPT)
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>
2026-06-20 13:05:35 -04:00
sergioandClaude Opus 4.8 462e0275d3 Etapa B (B1+B2): boot desde disco real ext4, sin switch_root
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>
2026-06-20 10:42:40 -04:00
sergioandClaude Opus 4.8 761b074d14 recipes/diffutils: diff/cmp Rust-nativo (Etapa C pieza 3)
uutils/diffutils v0.5.0 (--bin diffutils multicall, despacha por argv[0]; symlinks
diff/cmp). Construye+sella+corre: ELF estático, diff -u unified con rc 0/1, cmp
reporta byte/línea. Mismo gotcha workspace que findutils → diffutils-workspace.patch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:53:08 -04:00
sergioandClaude Opus 4.8 7bd326170d recipes/findutils-xargs: xargs Rust-nativo (Etapa C pieza 2b)
uutils/findutils 0.9.1 --bin xargs (mismo crate/commit/patch que findutils.toml).
Construye+sella+corre: xargs 0.9.1, ELF estático, -n1/-I{}/pipe OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 06:05:01 -04:00
sergioandClaude Opus 4.8 c2c84a96ac recipes/findutils: find Rust-nativo (Etapa C pieza 2)
uutils/findutils 0.9.1 (tag 17d852c4, --bin find, static musl). Construye+sella
+corre: find (Rust) 0.9.1, ELF estático sin interpreter, -name/-type/-maxdepth OK.
onig_sys (C vía cc-rs) linkea con el wrapper zig-cc.

Gotcha general de toda receta Cargo de un crate suelto: hammer copia la fuente a
work/sources/ dentro del repo hammer (un workspace), así cargo vendor la absorbe y
aborta. Fix: findutils-workspace.patch inyecta un [workspace] vacío. coreutils lo
esquiva por ser ya raíz de workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:44:48 -04:00
sergioandClaude Opus 4.8 cb9cfd9df4 docs(linux): veredicto formal — kernel al gueto gcc (zig-cc x86 kbuild)
zig 0.13 compila los host-tools del kernel (wrapper que traduce el depfile
-Wp,-MMD,PATH → -MMD -MF PATH; cache de zig deshabilitado), pero el target
choca con la traducción de flags x86 de zig cc (-mtune=generic, -march=x86-64).
gcc se retiene SOLO para {kernel, cmake}; el resto del userland C es gcc-free
vía zig 0.13. Futuro intento limpio: make LLVM=1 con el clang que zig empaqueta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:44:41 -04:00
sergioandClaude Opus 4.8 7bf49fb960 build: zig_version por-receta — 5 víctimas C dropean gcc (zig 0.13)
Mata gcc para 5 de las 7 recetas que lo forzaban, vía una escotilla nueva:

- hammer-core/hammer-build: campo `[build].zig_version` por receta. Cuando se
  fija, el lab resuelve ese zig (hermano del por defecto, `zig-x86_64-linux-<v>`)
  en vez del global, y entra al hash SÓLO si está presente (baseline 9adefb82
  intacto). `effective_zig_dir` lo aplica en ensure_layout + Sandbox.

- Causa: BISECT con oráculo flex (reproducido sólo vía lab: musl DINÁMICO) — el
  miscompile es una REGRESIÓN de zig 0.14; 0.13.0 compila limpio, 0.14/0.15/0.16
  fallan. Es C/musl-dinámico, NO afecta C++.

- Flip a zig_version="0.13.0" (quitando CC=gcc): flex, openssl, elfutils,
  binutils, python3. Verificados: `as` 2.45.1 corre, python3 3.12.10 corre
  (deepfreeze OK), libcrypto/libelf sellan. Todas son tools (no inputs del 4/4).

cmake queda en gcc: su segfault es C++ (libc++/musl), bug distinto que 0.13 NO
arregla (ni con -static). El kernel queda pendiente de verificar.

Tests: hammer-core/hammer-build verdes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:20:17 -04:00
sergioandClaude Opus 4.8 54ccc130dd recipes/uutils: coreutils Rust-nativo (Etapa C pieza 1)
Primera pieza del userland Rust-nativo (Etapa C): adopta uutils/coreutils 0.9.0
(MIT) como receta Cargo — el patrón de hammerd/arje-zero, no recompila C ni se
escribe de cero. Multicall static-musl (12 MB) + 79 symlinks por applet
(ls/cp/cat…), drop-in de busybox/GNU coreutils.

- Esquiva el miscompile de zig por construcción: codegen rustc/LLVM, zig cc sólo
  como linker.
- `--bin coreutils` (no `-p`): el paquete tiene lib+bin y `cargo rustc -- flags`
  exige un único target (gotcha vs arje-zero).
- Install custom (sólo esa fase; configure/compile siguen auto-Cargo) crea los
  symlinks con `coreutils --list`.

Construido + sellado + verificado funcional (ls/cat/echo vía dispatch argv0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:12:12 -04:00
sergioandClaude Opus 4.8 5f12692ccf docs: sincronizar roadmap con los frentes cerrados + Etapa A
El roadmap (SDD 10, "Estado actual") estaba detrás de la realidad ya verificada
in-VM. Sincroniza:

- Auto-alojamiento puro (variante b): 🚧 CERRADO (5 swaps in-VM
  ✓ REPRODUCIBLE, of_tree 9adefb82; binutils 2.45.1 añadido).
- Frente rust/llvm:  CERRADO (mrustc→1.91.1, auto-consistencia in-VM 7fa6cb4e).
  Capstone: los 6 swaps juntos in-VM ✓ REPRODUCIBLE.
- Frente kernel-from-source:  CERRADO (6.16.12 hammer-built bootea + rebuild
  in-VM reproducible; build-deps de-Alpinizados).
- Etapa A:  CERRADA (bootstrap all + manifest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:12:12 -04:00
sergioandClaude Opus 4.8 98f0b2d228 bootstrap: orquestador all + export del manifiesto (Etapa A)
Cierra la Etapa A del camino a la distro (SDD 11 §5):

- `hammer_bootstrap::all(seed, recipes_dir, base_cfg, store) -> AllReport`
  encadena stage0→stage1→stage2 en una corrida del host y deja el
  `bootstrap.json` poblado. El veredicto ✓ REPRODUCIBLE lo sigue sellando el
  rebuild in-VM (selfhost-verify.sh); `all` ancla la referencia para comparar.
- CLI: `hammer bootstrap all --url … --sha256 … --version …` y
  `hammer bootstrap manifest` (imprime el log de transparencia, §4).
- Refactor: `parse_seed_kind` factoriza el match de `--seed` (estaba duplicado
  en 4 sitios del despacho).
- hammer-build: arregla el test stale `detect_cargo` — la fase Cargo evolucionó
  a `RF=…`+`-C link-self-contained=no` condicional, pero la aserción esperaba la
  forma vieja `RUSTFLAGS="-C linker=…`. Restaura el workspace en verde (43/43).

Tests: cargo test --workspace verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:11:56 -04:00