Commit Graph
98 Commits
Author SHA1 Message Date
sergioandClaude Opus 4.8 673bdde69f Etapa G: mirada-compositor CONSTRUIDO — stack de sesión gráfica COMPLETO
mirada-compositor sellado (store/d8fd9ca…): el compositor Wayland teselante sobre smithay 0.7,
que maneja DRM/GBM/EGL/libseat/libinput directo y hospeda a mirada-greeter como cliente.
Compila ~255 crates incl. smithay para musl; PIE dinámico.

Cierra la cadena de sesión gráfica de tawasuyu sobre hammer: Mesa(iris) + wayland + stack de
entrada + compositor + greeter, todo sellado.

smithay (default features) enlaza por -sys: drm-sys→libdrm, gbm-sys→mesa, libseat-sys→seatd,
input-sys→libinput, libudev-sys→libudev-zero, + xkbcommon→libxkbcommon y pixman (link directo).
Dos libs nuevas para cerrar el link:
- libxkbcommon 1.7.0 (meson, sin x11/wayland/docs/tools): smithay la enlaza, no sólo dlopen.
- pixman 0.44.2 (meson, sin tests/demos): composición CPU de smithay.

foreign-av NO enlaza ffmpeg (spawnea el binario en runtime) ⇒ no hizo falta construir ffmpeg.
NEEDED del binario: libgbm/libseat/libudev/libinput/libpixman-1/libxkbcommon + musl (libdrm/EGL/
wayland se dlopen-ean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:06:41 -04:00
sergioandClaude Opus 4.8 11386c0a59 Etapa G: mirada-greeter CONSTRUIDO — primera app gráfica tawasuyu sobre el stack
mirada-greeter sellado (store/41fdd015…): el greeter/login de mirada (runtime Elm Llimphi
sobre winit + wgpu) compila ENTERO para musl — 314 crates incl. wgpu/winit/naga/smithay/
llimphi-* — y enlaza dinámico. Valida la cadena completa: stack gráfico (wayland+mesa) →
app GUI tawasuyu real.

Binario: PIE dinámico, DT_NEEDED = sólo libpam.so.0 + musl; EGL/GLES/GBM/wayland/iris se
dlopen-ean en runtime (wgpu/winit), así que el rootfs debe tenerlas (mesa+wayland ya selladas).

Dos blockers resueltos en el camino:
- atspi/accesskit (a11y, vía accesskit_winit): zbus-lockstep-macros #[validate] PANICA
  («File has no extension.») al escanear xml/ de atspi-common y hacer .extension().expect()
  sobre el SUBDIR xml/schemas/ (lib.rs:147). Fix: fase compile custom (réplica de la fase
  Cargo de hammer) que setea LOCKSTEP_XML_PATH a un dir con sólo los .xml.
- PAM: auth-core → pam/pam-sys hace bindgen sobre security/pam_appl.h ⇒ nueva receta
  linux-pam 1.6.1 (libpam.so + headers; build limpio en musl con gcc de Alpine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:45:48 -04:00
sergioandClaude Opus 4.8 7131cd4269 Etapa G: MESA iris-only CONSTRUIDA (24.0.9, sin LLVM) — stack gráfico COMPLETO
Mesa sellada: store/607a513c…-mesa con dri/iris_dri.so + libEGL/libgbm/libGLESv2/
libglapi + egl/gbm/glesv2.pc. El stack gráfico para el escritorio tawasuyu (Iris Xe)
queda completo: libdrm✓ wayland✓ wayland-protocols✓ seatd✓ mesa✓.

Decisiones/fixes clave:
- PIN 24.0.9 (no 26.1.1): desde Mesa 24.1 iris REQUIERE intel-clc (kernels OpenCL-C
  internos) ⇒ libclc + clang/LLVM, lo que rompe el plan iris-sin-LLVM. Bisección: 24.0.9
  es el último donde with_clc=FALSE con iris-only sin Vulkan/rusticl. (Decisión del usuario:
  pinear Mesa viejo en vez de construir LLVM.)
- recorte iris-only: -Dllvm=disabled -Dintel-clc=disabled, sin Vulkan/glx/rusticl/codecs/tools.
- packaging (módulo Python puro): el chequeo de versión de mako usa packaging.version y
  Python 3.12 ya no trae distutils ⇒ sin packaging fallaba con el engañoso «mako required».
- PYTHONPATH al site-packages en configure Y compile (el codegen mako corre en ninja).
- libffi en deps: lo pide wayland-client.pc (Requires: libffi) al resolver cflags.
- mesa-version-script-comma.patch: el target DRI emitía «-Wl,--version-script dri.sym»
  (token separado) que zig cc reordena ⇒ «cannot find version script --end-group» al ligar
  libgallium_dri.so. La forma coma «-Wl,--version-script,dri.sym» (un token) lo arregla.

Runtime pendiente (ensamblado de rootfs): iris_dri.so/libEGL necesitan libz.so.1, libzstd.so.1
(de la base), libdrm.so.2, wayland .so. mirada-greeter (Cargo, link dinámico a Mesa) ya desbloqueado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 02:13:23 -04:00
sergioandClaude Opus 4.8 6fb7fd36d0 Etapa G: wayland + wayland-protocols CONSTRUIDAS (scanner desbloqueado)
Root cause real del bloqueo de wayland: NO era bug del wayland-scanner ni del XML
(ambos verificados OK). En el sandbox el scanner in-tree se compila ENLAZADO DINÁMICO
contra la musl (zig cc nativo no produce estático acá), y bajo musl-dinámico
`freopen(@OUTPUT@,"w",stdout)` queda roto por la copy-relocation de `stdout`: trunca el
archivo de salida pero manda el header generado a stdout → @OUTPUT@ vacío → `WL_SHM_FORMAT_*`
/ `wl_*` «undeclared» al compilar libwayland.

Fix: wayland-scanner-dup2-output.patch redirige la salida con `dup2(fd, STDOUT_FILENO)`
(nivel descriptor) en vez de `freopen` → robusto al modo de enlace. Verificado byte-a-byte:
salida idéntica a la del scanner estático (217484 B al archivo).

Segundo blocker (libwayland-server.so): libffi.a era no-PIC → `R_X86_64_PC32 ... recompile
with -fPIC` al ligarlo dentro de una .so. libffi ahora con --with-pic (superset; sigue
sirviendo para enlace estático). Mesa necesitará lo mismo en zlib/zstd/expat.

Restaura recipes/samurai.toml (lo necesita el stack adaptado de recipes/: libdrm/seatd/
wayland; las otras build-deps —meson/pkgconf/python3/expat/libffi— ya viven en recipes/).

Sellados: wayland (libwayland-{client,server,cursor,egl}.so + scanner + .pc),
wayland-protocols (XML + .pc). Queda mesa (iris-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:52:16 -04:00
sergioandClaude Opus 4.8 93e3ea28d2 Etapa G: wayland root cause CONFIRMADO — scanner musl lee input vacío
Verificado que la fuente extraída protocol/wayland.xml está completa (165075 B) y
expat OK ⇒ el wayland-scanner compilado static-musl recibe el XML completo pero
emite headers vacíos y sale 0 (bug de runtime, falla con zig default y 0.13.0). FIX
recomendado: construir el scanner NATIVO (build-time only, no se shippea) y apuntar
libwayland al scanner externo; libwayland-{client,server} sí con musl/zig.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:14:03 -04:00
sergioandClaude Opus 4.8 060e24e564 Etapa G: debug wayland-scanner — genera headers vacíos (input vacío a expat)
El build de libwayland falla: wayland-scanner produce headers de protocolo vacíos
→ wl_*/WL_SHM_FORMAT_* undeclared. Diagnóstico documentado en la receta y en
docs/14-mesa-stack.md §Estado: el scanner sale 0 sin emitir; por stdin expat dice
'no element found at line 1 col 0' (input vacío). Descartado samurai-ordering (-j1
falla igual) y libexpat (estático, sin símbolos glibc-only). Resultados
inconsistentes entre corridas (wayland.xml 165KB vs vacío) → sospecha de extracción
.tar.xz no determinista, lectura de input miscompilada, o estado tocado por el farm.
Siguiente: corrida aislada que verifique el tamaño del wayland.xml extraído.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:12:14 -04:00
sergio 66ed94d4e0 Etapa G: corrige nota wayland (no es libexpat — es estático en hammer) 2026-06-26 23:59:37 -04:00
sergioandClaude Opus 4.8 99bbeac54a Etapa G: stack Mesa — 4/7 construidas (samurai, meson, libdrm, seatd)
Adapta las recetas importadas a fases hammer (meson/samurai directos, link=dynamic)
y construye+sella la base del stack gráfico para el escritorio tawasuyu:

- samurai: zig 0.13.0 (el default miscompila musl → reallocarray falla en runtime)
- meson:   Python puro, copia mesonbuild/+wrapper (sin wheel/gpep517 que hammer no tiene)
- libdrm:  core-only (GPU helpers disabled) → sin libpciaccess; iris usa libdrm core
- seatd:   libseat.so.1, -Dwerror=false (CMSG_NXTHDR de musl dispara -Wsign-compare)

Pendiente (docs/14-mesa-stack.md §Estado): wayland (los headers de wayland-scanner
no aparecen, prob. libexpat.so en el scanner), wayland-protocols (bloqueada por
wayland) y mesa (la grande, recortada iris-only). meson validado con zig cc sobre
proyectos meson reales (libdrm, seatd).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 23:58:37 -04:00
sergioandClaude Opus 4.8 00339b625e Etapa G: cola del stack gráfico Mesa (iris) para el escritorio tawasuyu
Importa de Alpine aports (con parches musl) las recetas del stack que el greeter
real de mirada necesita en el rootfs de producto: libdrm 2.4.134, wayland 1.25.0,
wayland-protocols 1.48, seatd, mesa 26.1.1, + build-tools meson 1.11.1 y samurai
(ninja). Quedan en recipes/incoming/ como puntos de partida: traen los parches
musl de Alpine pero usan helpers de abuild — falta adaptarlas a fases hammer
(meson/samurai directos, link=dynamic), como elfutils.

docs/14-mesa-stack.md fija el plan: iris-only ⇒ SIN LLVM/Vulkan/X11 (el driver
gallium iris no necesita LLVM), lo que recorta clang/llvm/libclc/spirv/bindgen.
DAG de build (hojas→raíz) + estado por receta + el lado tawasuyu (recetas Cargo
arje-splash/net-bring-up estáticas ya construibles; mirada-greeter dinámico,
bloqueado por mesa). Contraparte de la seed arje-tawasuyu del monorepo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 23:41:34 -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 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 d73e20a394 instalar DESDE el live: hammer-install in-live + payload de disco (refinamiento #1)
Cierra el lazo ISO live -> disco instalado -> bootea solo. iso-image.sh
INSTALLER=1 bundlea kernel + GRUB MBR (boot/core.img + modulos) en el initramfs
bajo /usr/lib/hammer/install/ + inyecta /usr/bin/hammer-install. hammer-install
corre dentro del live como root real (busybox fdisk/mke2fs/mount/dd + uutils cp):
particiona MBR (/,/store,/var/lib/hammer), formatea, copia la propia raiz del
live (autoinstalador), escribe /boot+wrapper, instala GRUB con 2 dd (boot->MBR,
core->hueco post-MBR; punteros default 1/2 ya valen en MBR contiguo, sin parcheo).
AUTO_INSTALL=<dev> = /init desatendido (install+poweroff). iso-install-test.sh
valida E2E: ISO+disco blanco -> HAMMER-INSTALL-OK -> boot del disco solo ->
GRUB->kernel->arje-zero->sshd, particiones dedicadas montadas.

GOTCHAS: busybox fdisk CHS-alinea a 63 (hueco 62 < core 281 sectores) -> pisa el
FS de p1 -> grub>; fix sectores explicitos (p1@2048). Copiar top-level de /
enumerado, no lista fija, o se escapa /ente/seed.card.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:50:07 -04:00
sergioandClaude Opus 4.8 2976f3e5fd E5 medio live: ISO El Torito booteable con xorriso de hammer (primer corte)
scripts/iso-image.sh arma un ISO 9660/El Torito isohybrid usando el xorriso
construido por hammer (recipes/xorriso.toml, dogfooding): GRUB core El Torito
(grub-mkimage -O i386-pc-eltorito + iso9660) carga kernel + initramfs del ISO;
el rootfs del producto va como cpio.gz (live de RAM). scripts/iso-boot-test.sh
valida E2E in-VM: SeaBIOS->GRUB 2.14->Linux 6.16.12->/init (marcador
HAMMER-ISO-LIVE-OK)->arje-zero PID1->hammerd->netup(DHCP)->sshd escuchando.
El producto entero corre desde el medio live, sin tocar disco. ISO 113M.
SDD 13 E5 marcado primer corte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:25:34 -04:00
sergioandClaude Opus 4.8 a22552e28f hammer-upgrade: GC de generaciones huérfanas — 'hammer upgrade prune' (E4 hardening)
Tras un rollback las generaciones más nuevas quedan inalcanzables (no hay redo).
prune(state, keep) borra esas huérfanas (la cadena viva = current+ancestros vía
parent siempre se conserva); --keep N recorta además la profundidad de rollback.
live_chain() expuesto. 2 tests nuevos + paso prune en upgrade-e2e-test.sh (3->1
gens, árbol vivo intacto).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:49:18 -04:00
sergioandClaude Opus 4.8 d23946fa3d hammer-upgrade: upgrades atómicos con generaciones y rollback (Etapa E4, primer corte)
Crate hammer-upgrade + CLI 'hammer upgrade apply|rollback|status'. Aplica un
árbol Stage1/producto del store sobre el FHS vivo dejando una generación
(manifest + backup de bytes previos), atómico fichero-a-fichero con 'current'
como commit-point. Rollback restaura EXACTO al árbol anterior (o pre-upgrades).
Cada cambio al journal (HammerHydrate, replay-able). Verificación opcional del
of_tree esperado (índice de mirror E3 / release firmada). Maneja ficheros +
symlinks. 7 tests unitarios + scripts/upgrade-e2e-test.sh (apply v1->v2->
rollback->rollback contra el binario real). SDD 13 actualizado.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:46:14 -04:00
sergioandClaude Opus 4.8 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 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 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 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
sergioandClaude Opus 4.8 d2e0d9b628 docs: runbook del cierre del auto-alojamiento (variante b) + cross-refs
Toda la variante (b) del SDD 11 §7.2 vivía sólo en scripts/rust-frontier/README
y notas dispersas; el SDD §7.2 y el runbook §8c quedaban en "Pendiente:
linux-headers → bwrap → rust/llvm". Nuevo runbook docs/runbooks/
self-hosting-toolchain.md documenta el end-state:

- las dos categorías de swap y los dos anclajes de of_tree (9adefb82 rustc
  Alpine / 7fa6cb4e hammer-rust auto-consistente);
- tabla de las piezas from-source (make/busybox/coreutils/bwrap/linux-headers
  /rust/binutils + patch/m4/pkgconf) con hash, flag y si están en-camino;
- las 3 corridas verify (5-swap, rust, capstone 6-swap) con comandos y
  resultados ✓ REPRODUCIBLE in-VM;
- el mecanismo --swap (archivo/directorio/rust-overlay);
- gotchas reusables (zig miscompila→gcc, musl 256 TLS keys, CARGO_BUILD_JOBS=1,
  zsh :u / word-splitting);
- binutils inerte verificado por bisección.

SDD §7.2 y runbook §8c ahora marcan variante (b) cerrada y enlazan el runbook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:47:13 -04:00
sergioandClaude Opus 4.8 6c8a307997 selfhost-verify: herramientas de toolchain extra desde fuente — patch, m4, pkgconf
Más de-Alpinización del builder (variante b, SDD 11 §7.2b): recetas para construir desde fuente
tres build-tools del toolchain que hoy vienen de Alpine.

- recipes/patch.toml (GNU patch 2.8): lo invoca apply_patches (hammer-build/fetch.rs) cuando una
  receta trae source.patches, p.ej. el overlay de linux-headers.
- recipes/m4.toml (GNU m4 1.4.20): base de la cadena autotools.
- recipes/pkgconf.toml (pkgconf 2.5.1): lee los .pc que materialize_build_deps deja en el sandbox.

Las 3 estáticas musl con zig cc (AutoconfReady), validadas: corren, versión correcta y funcionales
(patch aplica, m4 expande, pkgconf resuelve). El 4/4 mínimo no las invoca ⇒ sin flag SWAP_* dedicado
(swapeables con el escape SWAPS="name=hash:rel"); avanzan "builder reconstruible al completo".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 05:40:53 -04:00
sergioandClaude Opus 4.8 a774552ead selfhost-verify: pieza 5 (coreutils swap) — SWAP_COREUTILS=1, musl+busybox byte-idénticos en host
Quinta pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): GNU coreutils 9.8
(cp/mkdir/ln/chmod/mv/install…), que el `make install` de musl y busybox invocan.

- recipes/coreutils.toml: empaquetada multicall (--enable-single-binary), mismo layout que el
  paquete de Alpine (un /bin/coreutils + ~100 symlinks que despachan por argv[0]). Estática musl
  con zig cc, vainilla sin patches (es tool del toolchain, no input compilado del 4/4).
- scripts/selfhost-verify.sh: SWAP_COREUTILS=1 construye la receta y monta el único binario sobre
  /toolchain/bin/coreutils — los symlinks del toolchain (cp/mkdir/install/…) lo siguen, un solo --swap.

Bisección host fuerte: con hammer-coreutils pisando el de Alpine (trap-restore en .dev-fs), musl Y
busybox rebuildearon BYTE-IDÉNTICO (57b66a2e, 56664d70). Pendiente: corrida in-VM acumulando swaps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:23:28 -04:00
SergioandClaude Opus 4.8 a23630c50d feat(arje-link): transporte arje-bus → bus de agente, cierra B.2 end-to-end
hammerd::arje_link se suscribe al bus del init (ENTE_BUS_SOCK), relee el frame
postcard de arje-bus con un mirror mínimo de suscriptor (sin arrastrar el
crate-graph de arje ⇒ hammer sigue standalone) y reenvía cada BusEvent →
crashes → Event::Crashed → agent.sock. Wire verificado byte-a-byte contra
arje-bus real (ulid string, frame Subscribe=[00,01,00,0d]); 2 tests de round-trip
local + frame. Se lanza en thread si ENTE_BUS_SOCK está definido (no-op si no).
Roadmap B.2 marcado  (resta sólo el smoke contra init vivo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:30:04 +00:00
SergioandClaude Opus 4.8 f6b337f6cb feat(hydrate): hidratación dinámica real con patchelf (LinkMode::Dynamic)
Deja de ser un error "pendiente": hydrate(.., DynamicSpec{interpreter,rpath})
detecta ELF por magic, copia los que necesitan parcheo (no hardlink: patchelf
mutaría el store) y aplica --set-interpreter/--set-rpath atómicamente
(copy→patch→rename); no-ELF y spec vacío siguen hardlinkeando. ensure_patchelf
falla limpio antes de tocar el FHS si falta la herramienta. HydrateReport.patched
cuenta los reescritos. Callers (bus/cli/bootstrap/e2e) pasan None=estático.
4 tests nuevos (incl. camino de error sin patchelf y real gated). Doc §4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:23:12 +00:00
SergioandClaude Opus 4.8 8a5e75344a feat(swm): init_rule se materializa a /etc/hammer/init.d/{service}.rule
Deja de ser un no-op "pendiente fase 5": apply::apply_init_rule escribe una
regla TOML por servicio (service/action/command); enable/start/restart la
escriben, disable/stop la retiran (idempotente), con guardas de nombre y acción.
CLI y orchestrator la aplican (rebaseando con prefix/overlay). Es el contrato
on-disk que el init (arje) lee para supervisar. 3 tests + doc del formato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:17:56 +00:00
SergioandClaude Opus 4.8 0f42c38106 feat(swm): source_patch modela tarballs además de git (Fase 4)
Mutation::SourcePatch y RecipeInline ganan campos repo/commit XOR tarball/sha256
(Option, serde-default ⇒ compat con .swm git existentes), resueltos por
swm::swm_source_kind con la misma regla que recipe::Source::kind. swm_bridge
sintetiza la receta según el modo; hammer export reconstruye fuentes tarball
como source_patch en vez de caer a file_drop (provenance fina recuperada).
Prompt del traductor y roadmap actualizados. Tests nuevos para tarball + XOR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:06:35 +00:00
SergioandClaude Opus 4.8 7c099afe4f feat(bus): B.2 sink del CRASHED — hammerd::crashes traduce eventos de arje
Nuevo módulo hammerd::crashes: traduce la señal de ciclo de vida normalizada
(fuente = arje-bus BusEvent) a proto::Event::Crashed y la bombea al EventBus →
agent.sock (capa de IA). Traducción + bucle de bombeo desacoplados del
transporte y testeados (3 tests). Resta sólo el adaptador de transporte
arje-bus ↔ hammerd. Roadmap B.2 actualizado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:51:43 +00:00
sergioandClaude Opus 4.8 e9f535a1bb selfhost-verify: pieza 4 (bwrap swap) + materialización de build-deps en el lab
Cuarta pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): bubblewrap, el
sandbox del propio lab. Binario estático ⇒ swap de archivo sobre /toolchain/usr/bin/bwrap.
Como herramienta del toolchain (no input del 4/4) no necesita casar byte-a-byte con Alpine,
sólo aislar igual.

Tres piezas:
- recipes/libcap.toml (2.78): dep obligatoria de bwrap; el toolchain Alpine no trae el -dev
  (libcap.a / sys/capability.h). Build estático musl con zig cc, sin patches (es tool, no input).
- materialización de build-deps (hammer-build): deps.build ahora se CONSTRUYE recursivamente
  (build() llama build() por cada dep) y cada artefacto sellado se apila como capa --overlay-src
  bajo el rootfs del sandbox, dejando usr/{include,lib,lib/pkgconfig} en /usr. pkgconf y zig cc
  las hallan sin plumbing de flags. Recetas sin deps: sandbox byte-igual (baseline intacto).
  Tests nuevos: no_deps_emits_single_overlay_src, deps_stack_as_overlay_layers_under_rootfs.
- recipes/bwrap.toml (0.11.0): el toolchain no trae meson/ninja/python, así que bypaseamos meson
  compilando los 4 .c de bubblewrap directo con zig cc (+config.h trivial). deps.build=["libcap"].

Validación host fuerte: hammer-bwrap es estático, corre --version y sandboxea, y musl rebuildeó
BYTE-IDÉNTICO usándolo de sandbox (bisección). Expuesto con SWAP_BWRAP=1. Tests verdes.
Pendiente: corrida in-VM acumulando swaps para el sello ✓ REPRODUCIBLE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:05:55 -04:00
sergioandClaude Opus 4.8 b05badee23 selfhost-verify: pieza 3 (linux-headers swap) — SWAP_LINUX_HEADERS=1, byte-idéntico a Alpine en host
Tercera pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): los headers
UAPI del kernel 6.16.12 que musl/busybox #include, hoy tomados del paquete linux-headers
de Alpine.

- recipes/linux-headers.toml: `make headers` (no `headers_install` — su rsync final falta
  en el toolchain hermético) + unifdef vía HOSTCC=zig cc; .tar.gz (busybox-tar lo
  descomprime sin xz). Sella los 13 subdirs kernel-owned de usr/include.
- assemble_builder: el swap ahora soporta rel_path de DIRECTORIO (reemplaza el árbol
  entero: remove + copy), no sólo binarios. Cubierto por test nuevo
  (builder_rootfs_swaps_toolchain_header_tree_from_source, incl. borrado de huérfanos).
- recipes/linux-headers-alpine-compat.patch: el paquete de Alpine no es el `make headers`
  crudo. Aporta content-pinned 5 archivos que difieren de la salida vainilla; 3 son
  REQUERIDOS (scsi/{scsi,scsi_ioctl,sg}.h — legacy userspace, NO UAPI) porque el applet
  `eject` de busybox los incluye y sin ellos no compila. install los pisa sobre /out y
  limpia el junk (.cmd/Makefile/headers_check.pl/drm) que `cp -a` arrastra.
- scripts/selfhost-verify.sh: SWAP_LINUX_HEADERS=1 construye la receta y emite un --swap
  por subdir kernel-owned (auto-derivado del artefacto sellado).

Criterio de éxito fuerte: `diff -r` del header-tree de hammer contra el de Alpine = VACÍO
⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ reproducibilidad por construcción.
Ensamblado end-to-end en host OK (13 swaps, musl bits/sys intactos). 133 tests verdes.
Pendiente: corrida in-VM para el sello ✓ REPRODUCIBLE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:35:01 -04:00
sergioandClaude Opus 4.8 26b10bd757 selfhost-verify: variante (b) ✓ REPRODUCIBLE in-VM con make+busybox swaps
KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.sh en libre:
la VM reconstruyó los 4/4 con el /toolchain hammerizado (make fbad44ac… +
busybox 56664d70… pisando los de Alpine, busybox compilándose a sí mismo con
hammer-busybox de shell). of_tree(stage1')==EXPECT_REF 9adefb82… ⇒
✓ REPRODUCIBLE: stage1' == stage1, DRIVER_RC=0 (~54 min, arje-zero cu=1 in-VM).

La procedencia del builder deja de ser "todo Alpine" y el auto-alojamiento
sigue bit-a-bit. Siguiente pieza: linux-headers → bwrap → rust/llvm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:51:50 -04:00
sergioandClaude Opus 4.8 0b97a85356 selfhost-verify: pieza 2 (busybox swap) — SWAP_BUSYBOX=1, validada en host
Variante (b) pieza 2: el sh+coreutils que el build usa del /toolchain. El /toolchain
de Alpine es mixto — busybox para sh/sed/grep/awk/tar/find (symlinks → /bin/busybox),
GNU coreutils para cp/mkdir/install. El swap monta el busybox estático de hammer
sobre /toolchain/bin/busybox: los applets busybox-backed pasan a usarlo, coreutils
GNU queda intacto. Sin código nuevo — el mecanismo --swap ya soporta rel_path
(--swap busybox=<hash>:bin/busybox).

Validado en host (sin VM): con make+busybox de hammer swapeados, los 4/4 componentes
construyen — incl. busybox compilándose a sí mismo con hammer-busybox de shell — y el
of_tree del rootfs ensamblado da 9adefb82 (el baseline determinista). Las piezas 1 y 2
son reproducibilidad-neutrales: el toolchain es intercambiable sin perturbar el byte-output.

selfhost-verify.sh: SWAP_BUSYBOX=1 (paralelo a SWAP_MAKE). Docs 10/11 actualizadas.
Pendiente: verify in-VM con los swaps para el ✓ REPRODUCIBLE end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 06:40:59 -04:00
sergioandClaude Opus 4.8 004a895829 selfhost-verify: fix CONFIRMADO + re-base EXPECT_REF a 9adefb82 (determinista)
Verificado: arje-zero con CARGO_BUILD_JOBS=1 y TODAS las CPUs del host sale
byte-idéntico al build serial ⇒ el jobserver serializa el backend paralelo de
rustc/LLVM (ThinLTO) independientemente del nº de CPUs. La pieza que faltaba para
la reproducibilidad host↔VM (codegen-units=1 no bastaba).

- EXPECT_REF re-baseado: 0039b2b9 (build paralelo no-fiable) → 9adefb82 (serial,
  determinista, = lo que produce la VM con 1 vCPU).
- Baseline del host refrescado: store/ re-sellado con el arje-zero determinista.
- docs/10-roadmap.md: causa raíz documentada (make inocente; arje-zero/ThinLTO).

Pendiente: re-correr el verify in-VM con el fix para cerrar el ✓ REPRODUCIBLE y
validar de paso la pieza 1 (swap del make).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 05:05:25 -04:00
sergioandClaude Opus 4.8 42c7f3b21d docs(roadmap): make-swap in-VM dio DIVERGENTE, pero el make es inocente
Stage 2 in-VM con SWAP_MAKE=1 dio of_tree(stage1')=9adefb82 ≠ 0039b2b9. El
diagnóstico en host exonera al make: musl+busybox construidos con hammer-make
salen byte-idénticos al baseline (diff -r limpio) y el ensamblado completo da
of_tree EXACTO 0039b2b9. La divergencia es no-determinismo in-VM (coincidió con
swap thrashing: 97 min wall para ~27 min compute), no el swap del toolchain.

Pendiente: re-correr el verify sin swap (control) para aislar la flakiness in-VM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:39:10 -04:00
sergioandClaude Opus 4.8 7bc2ee6019 builder: --swap del toolchain — montar make hammer sobre Alpine (variante b, pieza 1)
Segundo paso del auto-alojamiento *puro* (SDD 11 §7.2b): tras sellar GNU make
4.4.1 desde fuente (commit 749ea9e), ahora el builder puede *usarlo*. El swap
reemplaza una a una las piezas que el builder toma de Alpine por recetas hammer,
con Stage 2 reverificando que el byte-output no cambia.

- `BuilderSpec.swaps: Vec<ToolchainSwap{name,artifact,rel_path}>`: monta el binario
  sellado sobre el path Alpine en /toolchain (estático musl ⇒ sin shim del loader).
- `swaps_digest` (ordenado por nombre) entra al hash lógico del builder ⇒ la
  procedencia deja de ser "todo Alpine" y se vuelve auditable para el log de
  transparencia. Vacío ⇒ digest "" (compat hacia atrás: builder pura-Alpine
  conserva su hash previo).
- CLI: `hammer bootstrap builder --swap make=<hash>[:rel_path]` (repetible;
  rel_path por defecto usr/bin/<name>).
- selfhost-verify.sh: opt-in `SWAP_MAKE=1` (construye recipes/make.toml y lo
  swapea) + `SWAPS="name=hash …"` para swaps extra. Default off ⇒ corrida
  pura-Alpine idéntica a la baseline conocida-buena.

Validado en el host contra el store real: pura `b3:8a370f5d…` vs swapped
`b3:e7e2282c…`, y /toolchain/usr/bin/make queda hardlinkeado al artefacto
fbad44ac… (ELF estático, no el dinámico de Alpine). 3 tests nuevos
(swap aplica, hash cambia, error si falta el binario). 37 tests verdes.

Pendiente: correr Stage 2 in-VM con el swap y confirmar que of_tree(stage1') == ref
(el make hammer compila los 4/4 idéntico al de Alpine ⇒ toolchain intercambiable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:38:04 -04:00
sergioandClaude Opus 4.8 749ea9e797 recipes: GNU make 4.4.1 — pieza 1 del toolchain hammer-from-source (variante b)
Arranca el auto-alojamiento *puro* (SDD 11 §7.2b): reemplazar una a una las
piezas que el builder toma de Alpine (/toolchain) por recetas hammer desde
fuente, con Stage 2 reverificando cada paso.

make es la pieza base de toda receta autotools. Build estático musl con zig cc
(mismo camino que grep: tarball release con configure → AutoconfReady), sellado
b3:fbad44ac… y reproducible bit-a-bit (dos builds en stores distintos ⇒ árbol
idéntico). Pendiente: swap al /toolchain del builder + Stage 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:55:30 -04:00
sergioandClaude Opus 4.8 44e04ca4a0 docs: Stage 2 — auto-alojamiento bit-a-bit confirmado in-VM (✓ REPRODUCIBLE)
Cierra la deriva entre los docs y lo ya probado/commiteado (818c157):
el rebuild in-rootfs corrió end-to-end (host↔VM) y of_tree(stage1')=
0039b2b9… igualó la referencia ⇒ ✓ REPRODUCIBLE.

- roadmap §track posterior: Stage 2 ☐→; "Siguiente" reapunta al
  auto-alojamiento puro (variante b) + ítems Stage 1 (bus único, atestación).
- SDD 11 §5/CLI: marcadores stage2 ◑→; §7 (intro/7.4/8) el rebuild in-VM
  deja de ser "lo que falta" y queda la variante (b) como corte pleno.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:01:19 -04:00
sergioandClaude Opus 4.8 818c157596 runbook: ✓ REPRODUCIBLE verificado end-to-end in-VM contra baseline 0039b2b9 (cu=1)
Corrida KVM=1 MEM=24576 ./scripts/selfhost-verify.sh en el host (libre/cachyos):
la VM reconstruyó los 4/4 con el toolchain de adentro (arje-zero cu=1 compiló en
27m32s in-VM), of_tree(stage1')=b3:0039b2b9… igualó la referencia ⇒
✓ REPRODUCIBLE: stage1' == stage1 (auto-alojado bit a bit), DRIVER_RC=0.

Cierra el hunt de codegen-units (commit 4c9bcc0): la reproducibilidad de arje-zero
queda confirmada en el bucle completo host↔VM, no sólo host↔host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 08:52:40 -04:00
sergioandClaude Opus 4.8 4c9bcc016b sandbox: CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 — arje-zero reproducible (Stage 2 no-det #3)
El rebuild in-VM daba ✗ DIVERGENTE (of_tree host 984e002f ≠ VM 94b93585) pese a
que los 4 componentes sellaban bajo el mismo key of_inputs. Bisección por componente
en el host: musl, busybox y hammerd reconstruyen byte-idéntico, y la ensambladura del
rootfs (of_tree) es determinista e independiente del entorno. El culpable era arje-zero:
dos builds del MISMO host daban binarios distintos (Δ ~9.6 KB por .text/.rodata/.eh_frame/
.gcc_except_table) — firma del codegen paralelo de rustc.

Raíz: el workspace de hammer pinea [profile.release] codegen-units=1 (por eso hammerd
reproducía), pero el monorepo tawasuyu no declara perfil → cargo default codegen-units=16,
cuyo reparto del crate en N objetos varía build-a-build.

Fix: el sandbox impone codegen-units=1 para TODAS las crates Cargo (el var de cargo gana
sobre el profile del repo fuente). El lab elimina el no-determinismo en vez de confiar en
upstream (SDD 09 §2). Verificado: dos builds cu=1 de arje-zero → byte-idénticos.

Nueva referencia reproducible 4/4: of_tree(stage1)=b3:0039b2b9… (reemplaza 198f209f… de
cu=16). Actualizados EXPECT_REF (selfhost-verify.sh) y runbook §8b/§8c.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 04:31:51 -04:00
SergioandClaude Opus 4.8 7025f93bcd scripts: tarea selfhost-verify end-to-end (correr el 4/4 in-VM en KVM)
El veredicto pleno 4/4 in-VM pide KVM + RAM holgada; el host de dev (7.6 GB, sin KVM)
colgó por presión de RAM bajo TCG a los ~17 min del make de musl. Esta tarea traslada
la verificación a una máquina capaz (laptop) en un solo comando.

- scripts/selfhost-verify.sh: pipeline completo — stage0 → stage1 baseline → stage2
  (ancla la ref of_tree) → inyecta overlay.ko/e1000.ko del kernel local al toolchain →
  ensambla el builder con la ref embebida → empaqueta (cpio --owner=root:root) →
  bootea + driver no-interactivo → veredicto. KVM auto-detect; PRESEED=hammerd para el
  camino barato (preseed C+arje, sólo hammerd in-VM). Cross-check entre máquinas:
  compara su of_tree(stage1) contra EXPECT_REF (198f209f… conocida-buena del dev).
- scripts/drive-rebuild.py: driver no-interactivo parametrizado (env: BUILDER_CPIO,
  KERNEL, MEM, CPU, KVM, NET, DEADLINE, LOG). Bootea, espera la shell de arje-zero,
  manda rebuild-stage1 y sale 0 si REPRODUCIBLE / 1 si DIVERGENTE. (Versión de repo del
  driver ad-hoc que vivía en work/.)
- scripts/boot-builder-vm.sh: añade la NIC e1000 (NET=1 por defecto) — el vendoring Rust
  necesita red.
- runbook §8c: documenta la tarea y el muro de RAM del host de dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:57:07 +00:00
SergioandClaude Opus 4.8 9e3d6862ca docs: runbook §8c — store refrescado a baseline 4/4 + builder listo
Refresca los 4 componentes (musl, busybox, hammerd, arje-zero — éste re-vendorea el
monorepo) con el toolchain baseline en un store limpio y lo promueve a ./store (el
nativo queda en store-native-bak, gitignored). arje-zero baseline difiere del nativo
⇒ el fix -mcpu=baseline alcanza también al monorepo. Referencia CPU-independiente
full-4/4: of_tree(stage1)=
b3:198f209f5e2c9d3a37411823b2ea42a09074d4e482f044278958b1b86f232dbb (rootfs key
b3:2bd7c91b…, idéntico al del preseed-nativo: el cambio de bytes de arje no movió su
key of_inputs — la escotilla C.2 de nuevo).

Builder re-ensamblado con ese toolchain + hammer baseline + --ref-content 198f209f
(embebida en /etc/hammer/rebuild.env) y repackado a work/builder.cpio.gz (415 MB,
27027 entradas, --owner=root:root). Queda listo para bootear: adentro rebuild-stage1
debe reproducir 198f209f ⇒ ✓ REPRODUCIBLE cerraría el auto-alojamiento 4/4 bit a bit.

.gitignore: ignora /store-*/ (stores scratch/backup) — evita commitear el store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:00:11 +00:00